更新路由配置,修改店铺信息展示,登录拦截

This commit is contained in:
YeMingfei666 2025-02-08 15:52:52 +08:00
parent 16cd74fdd6
commit 6d3a3e7f91
35 changed files with 551 additions and 225 deletions

View File

@ -6,7 +6,9 @@ VITE_APP_BASE_API=/dev-api
# 接口地址
VITE_APP_API_URL=https://admintestpapi.sxczgkj.cn/ # 线上
# VITE_APP_API_URL=https://admintestpapi.sxczgkj.cn/ # 线上
VITE_APP_API_URL=https://cashieradmin.sxczgkj.cn/ # 正式
# VITE_APP_API_URL=https://api.youlai.tech # 线上
# VITE_APP_API_URL=http://localhost:8989 # 本地

View File

@ -5,18 +5,10 @@ const AUTH_BASE_URL = "/api/v1/auth";
const AuthAPI = {
/** 登录接口*/
login(data: LoginFormData) {
const formData = new FormData();
formData.append("username", data.username);
formData.append("password", data.password);
formData.append("captchaKey", data.captchaKey);
formData.append("captchaCode", data.captchaCode);
return request<any, LoginResult>({
url: `${AUTH_BASE_URL}/login`,
url: `auth/login`,
method: "post",
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
data: data,
});
},
@ -35,7 +27,7 @@ const AuthAPI = {
/** 注销登录接口 */
logout() {
return request({
url: `${AUTH_BASE_URL}/logout`,
url: `auth/logout`,
method: "delete",
});
},
@ -66,13 +58,7 @@ export interface LoginFormData {
/** 登录响应 */
export interface LoginResult {
/** 访问令牌 */
accessToken: string;
/** 刷新令牌 */
refreshToken: string;
/** 令牌类型 */
tokenType: string;
/** 过期时间(秒) */
expiresIn: number;
token: string;
}
/** 验证码信息 */

View File

@ -12,7 +12,7 @@ const MenuAPI = {
*/
getRoutes() {
return request<any, RouteVO[]>({
url: `${MENU_BASE_URL}/routes`,
url: `api/menus/build`,
method: "get",
});
},

View File

@ -8,9 +8,9 @@ const UserAPI = {
*
* @returns
*/
getInfo() {
getInfo(id: number) {
return request<any, UserInfo>({
url: `${USER_BASE_URL}/me`,
url: `api/tbShopInfo/` + id,
method: "get",
});
},
@ -220,17 +220,23 @@ export interface UserInfo {
/** 用户名 */
username?: string;
/** 称 */
nickname?: string;
/** 店铺名称 */
shopName?: string;
/** 头像URL */
avatar?: string;
coverImg?: string;
/** 角色 */
roles: string[];
/** 权限 */
perms: string[];
/** 店铺id */
shopId: number;
/** 店铺logo */
logo: string;
}
/**

View File

@ -1,8 +1,8 @@
<template>
<el-dropdown trigger="click">
<div class="flex-center h100% p13px">
<img :src="userStore.userInfo.avatar" class="rounded-full mr-10px w24px h24px" />
<span>{{ userStore.userInfo.username }}</span>
<img :src="userStore.userInfo.logo" class="rounded-full mr-10px w40px h40px" />
<span class="title">{{ userStore.userInfo.shopName }}</span>
<el-icon><CaretBottom /></el-icon>
</div>
<template #dropdown>
@ -58,3 +58,9 @@ function logout() {
</script>
<style lang="scss" scoped></style>
<style scoped>
.title {
font-size: 16px;
color: #5a5e66;
}
</style>

View File

@ -2,10 +2,8 @@
<div class="logo">
<transition name="el-fade-in-linear" mode="out-in">
<router-link :key="+collapse" class="wh-full flex-center" to="/">
<img :src="logo" class="w20px h20px" />
<span v-if="!collapse" class="title">
{{ defaultSettings.title }}
</span>
<img :src="userStore.userInfo.logo" class="w20px h20px" />
<span v-if="!collapse" class="title">{{ userStore.userInfo.shopName }}</span>
</router-link>
</transition>
</div>
@ -13,7 +11,9 @@
<script lang="ts" setup>
import defaultSettings from "@/settings";
import logo from "@/assets/logo.png";
import { useUserStore } from "@/store";
const userStore = useUserStore();
defineProps({
collapse: {
@ -32,9 +32,12 @@ defineProps({
.title {
flex-shrink: 0; /* 防止容器在空间不足时缩小 */
margin-left: 10px;
font-size: 14px;
font-weight: bold;
color: #333;
font-size: 16px;
color: #5a5e66;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 150px;
}
}

View File

@ -7,7 +7,7 @@
:background-color="variables['menu-background']"
:text-color="variables['menu-text']"
:active-text-color="variables['menu-active-text']"
:unique-opened="false"
:unique-opened="true"
:collapse-transition="false"
:mode="menuMode"
@open="onMenuOpen"

View File

@ -47,14 +47,14 @@ export function setupPermission() {
}
}
} else {
next();
// // 未登录,判断是否在白名单中
// if (whiteList.includes(to.path)) {
// } else {
// // 不在白名单,重定向到登录页
// redirectToLogin(to, next);
// NProgress.done();
// }
// 未登录,判断是否在白名单中
if (whiteList.includes(to.path)) {
next();
} else {
// 不在白名单,重定向到登录页
redirectToLogin(to, next);
NProgress.done();
}
}
});

View File

@ -35,7 +35,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{
path: "index",
component: () => import("@/views/data/index.vue"),
name: "index",
name: "dataStatistics",
meta: {
title: "数据统计",
affix: false,
@ -44,7 +44,7 @@ export const constantRoutes: RouteRecordRaw[] = [
},
{
path: "sales",
name: "sales",
name: "salesStatistics",
component: () => import("@/views/data/sales.vue"),
meta: {
title: "销售统计",
@ -52,6 +52,36 @@ export const constantRoutes: RouteRecordRaw[] = [
keepAlive: true,
},
},
{
path: "table",
name: "tableStatistics",
component: () => import("@/views/data/table.vue"),
meta: {
title: "桌台统计",
affix: false,
keepAlive: true,
},
},
{
path: "credit",
name: "creditStatistics",
component: () => import("@/views/data/credit.vue"),
meta: {
title: "挂账管理",
affix: false,
keepAlive: true,
},
},
{
path: "work",
name: "workStatistics",
component: () => import("@/views/data/work.vue"),
meta: {
title: "交班记录",
affix: false,
keepAlive: true,
},
},
{
path: "401",
component: () => import("@/views/error/401.vue"),
@ -64,6 +94,313 @@ export const constantRoutes: RouteRecordRaw[] = [
},
],
},
{
path: "/shop",
component: Layout,
meta: {
title: "店铺管理",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/shop/index.vue"),
name: "shopConfig",
meta: {
title: "店铺配置",
affix: false,
keepAlive: true,
},
},
{
path: "role",
component: () => import("@/views/shop/role.vue"),
name: "shopRole",
meta: {
title: "角色管理",
},
},
{
path: "staff",
component: () => import("@/views/shop/staff.vue"),
name: "shopStaff",
meta: {
title: "员工列表",
},
},
{
path: "log",
component: () => import("@/views/shop/log.vue"),
name: "shopLog",
meta: {
title: "操作日志",
},
},
],
},
{
path: "/online-shop",
component: Layout,
meta: {
title: "线上店铺",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/online-shop/index.vue"),
name: "shopDecoration",
meta: {
title: "店铺装修",
affix: false,
keepAlive: true,
},
},
{
path: "goods-group",
component: () => import("@/views/online-shop/goods-group.vue"),
name: "goodsGroup",
meta: {
title: "商品分组",
},
},
{
path: "pad",
component: () => import("@/views/online-shop/pad-setting.vue"),
name: "pad",
meta: {
title: "Pad点单设置",
},
},
],
},
{
path: "/system-setting",
component: Layout,
meta: {
title: "系统设置",
icon: "data_statistics",
},
children: [
{
path: "pay-types",
component: () => import("@/views/system-setting/pay-types.vue"),
name: "payTypes",
meta: {
title: "支付方式",
affix: false,
keepAlive: true,
},
},
],
},
{
path: "/tool",
component: Layout,
meta: {
title: "经营工具",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/tool/index.vue"),
name: "toolIndex",
meta: {
title: "代客下单",
affix: false,
keepAlive: true,
},
},
{
path: "table",
component: () => import("@/views/tool/table.vue"),
name: "table",
meta: {
title: "台桌管理",
affix: false,
keepAlive: true,
},
},
],
},
{
path: "/application",
component: Layout,
meta: {
title: "应用中心",
icon: "data_statistics",
},
children: [
{
path: "marketing",
component: () => import("@/views/application/marketing.vue"),
name: "applicationMarketing",
meta: {
title: "营销中心",
affix: false,
keepAlive: true,
},
},
{
path: "index",
component: () => import("@/views/application/index.vue"),
name: "applicationIndex",
meta: {
title: "列表管理",
affix: false,
keepAlive: true,
},
},
],
},
{
path: "/devices",
component: Layout,
meta: {
title: "设备管理",
icon: "data_statistics",
},
children: [
{
path: "printer",
component: () => import("@/views/devices/printer.vue"),
name: "devicesPrinter",
meta: {
title: "打印机",
affix: false,
keepAlive: true,
},
},
],
},
{
path: "/product",
component: Layout,
meta: {
title: "商品管理",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/product/list.vue"),
name: "productIndex",
meta: {
title: "商品列表",
affix: false,
keepAlive: true,
},
},
{
path: "unit",
component: () => import("@/views/product/unit.vue"),
name: "productUnit",
meta: {
title: "常用单位",
affix: false,
},
},
{
path: "category",
component: () => import("@/views/product/category.vue"),
name: "productCategory",
meta: {
title: "商品分类",
affix: false,
},
},
{
path: "specifications",
component: () => import("@/views/product/specifications.vue"),
name: "specifications",
meta: {
title: "商品规格",
affix: false,
},
},
],
},
{
path: "/inventory",
component: Layout,
meta: {
title: "进销存",
icon: "data_statistics",
},
children: [
{
path: "consumables",
component: () => import("@/views/inventory/consumables.vue"),
name: "consumables",
meta: {
title: "耗材列表",
affix: false,
keepAlive: true,
},
},
],
},
{
path: "/user",
component: Layout,
meta: {
title: "用户管理",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/user/index.vue"),
name: "userIndex",
meta: {
title: "用户列表",
affix: false,
keepAlive: true,
},
},
{
path: "active",
component: () => import("@/views/user/active.vue"),
name: "userActive",
meta: {
title: "活动管理",
affix: false,
},
},
],
},
{
path: "/order",
component: Layout,
meta: {
title: "订单管理",
icon: "data_statistics",
},
children: [
{
path: "index",
component: () => import("@/views/order/index.vue"),
name: "orderIndex",
meta: {
title: "订单列表",
affix: false,
keepAlive: true,
},
},
{
path: "group-purchase",
component: () => import("@/views/order/group-purchase.vue"),
name: "orderRefund",
meta: {
title: "团购订单",
affix: false,
keepAlive: true,
},
},
],
},
];
/**

View File

@ -25,10 +25,12 @@ export const usePermissionStore = defineStore("permission", () => {
return new Promise<RouteRecordRaw[]>((resolve, reject) => {
MenuAPI.getRoutes()
.then((data) => {
const dynamicRoutes = parseDynamicRoutes(data);
routes.value = [...constantRoutes, ...dynamicRoutes];
// const dynamicRoutes = parseDynamicRoutes(data);
// routes.value = [...constantRoutes, ...dynamicRoutes];
// isRoutesLoaded.value = true;
// resolve(dynamicRoutes);
isRoutesLoaded.value = true;
resolve(dynamicRoutes);
resolve(constantRoutes);
})
.catch((error) => {
reject(error);

View File

@ -9,7 +9,6 @@ import { setToken, setRefreshToken, getRefreshToken, clearToken } from "@/utils/
export const useUserStore = defineStore("user", () => {
const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo);
/**
*
*
@ -20,9 +19,10 @@ export const useUserStore = defineStore("user", () => {
return new Promise<void>((resolve, reject) => {
AuthAPI.login(LoginFormData)
.then((data) => {
const { tokenType, accessToken, refreshToken } = data;
setToken(tokenType + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
setRefreshToken(refreshToken);
Object.assign(userInfo.value, { ...data });
const { token } = data;
setToken(token); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
setRefreshToken(token);
resolve();
})
.catch((error) => {
@ -38,7 +38,7 @@ export const useUserStore = defineStore("user", () => {
*/
function getUserInfo() {
return new Promise<UserInfo>((resolve, reject) => {
UserAPI.getInfo()
UserAPI.getInfo(userInfo.value.shopId)
.then((data) => {
if (!data) {
reject("Verification failed, please Login again.");
@ -77,8 +77,8 @@ export const useUserStore = defineStore("user", () => {
return new Promise<void>((resolve, reject) => {
AuthAPI.refreshToken(refreshToken)
.then((data) => {
const { tokenType, accessToken, refreshToken } = data;
setToken(tokenType + " " + accessToken);
const { token } = data;
setToken(token);
setRefreshToken(refreshToken);
resolve();
})

View File

@ -36,26 +36,26 @@ service.interceptors.response.use(
return response;
}
const { code, data, msg } = response.data;
const { code, data, message } = response.data;
if (code === ResultEnum.SUCCESS || code === undefined || code === null) {
return data ? data : response.data;
}
ElMessage.error(msg || "系统出错");
return Promise.reject(new Error(msg || "Error"));
ElMessage.error(message || "系统出错");
return Promise.reject(new Error(message || "Error"));
},
async (error: any) => {
// 非 2xx 状态码处理 401、403、500 等
const { config, response } = error;
if (response) {
const { code, msg } = response.data;
const { code, message } = response.data;
if (code === ResultEnum.ACCESS_TOKEN_INVALID) {
// Token 过期,刷新 Token
return handleTokenRefresh(config);
} else if (code === ResultEnum.REFRESH_TOKEN_INVALID) {
return Promise.reject(new Error(msg || "Error"));
return Promise.reject(new Error(message || "Error"));
} else {
ElMessage.error(msg || "系统出错");
ElMessage.error(message || "系统出错");
}
}
return Promise.reject(error.message);

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -2,22 +2,22 @@
<div class="login" :style="'background-image:url(' + Background + ');'">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
:model="state.loginForm"
:rules="state.loginRules"
label-position="left"
label-width="0px"
class="login-form"
>
<h3 class="title">银收客后台管理</h3>
<el-form-item>
<el-radio-group v-model="loginForm.loginType">
<el-radio-group v-model="state.loginForm.loginType">
<el-radio-button value="merchant">商户</el-radio-button>
<el-radio-button value="staff">员工</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item prop="merchantName" v-if="loginForm.loginType == 'staff'">
<el-form-item prop="merchantName" v-if="state.loginForm.loginType == 'staff'">
<el-input
v-model="loginForm.merchantName"
v-model="state.loginForm.merchantName"
type="text"
auto-complete="off"
placeholder="商户号"
@ -25,7 +25,7 @@
</el-form-item>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
v-model="state.loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
@ -33,7 +33,7 @@
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
v-model="state.loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@ -43,27 +43,27 @@
<el-form-item prop="code">
<div class="code_wrap">
<el-input
v-model="loginForm.code"
v-model="state.loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter="handleLogin"
></el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" />
<img :src="state.codeUrl" @click="getCode" />
</div>
</div>
</el-form-item>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
:loading="state.loading"
size="default"
type="primary"
style="width: 100%"
@click.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-if="!state.loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
@ -76,168 +76,125 @@
</div>
</template>
<script>
<script setup>
import { encrypt } from "@/utils/rsaEncrypt";
import { getCodeImg } from "@/api/login";
import { $douyin_checkIn } from "@/api/coup/index";
import Cookies from "js-cookie";
import qs from "qs";
import Background from "@/assets/images/background_img.jpg";
export default {
name: "Login",
data() {
return {
Background: Background,
codeUrl: "",
cookiePass: "",
loginForm: {
username: "",
password: "",
rememberMe: false,
code: "",
uuid: "",
merchantName: "",
loginType: "merchant",
},
loginRules: {
username: [{ required: true, trigger: "blur", message: "用户名不能为空" }],
password: [{ required: true, trigger: "blur", message: "密码不能为空" }],
code: [{ required: true, trigger: "change", message: "验证码不能为空" }],
merchantName: [{ required: true, trigger: "change", message: "商户号不能为空" }],
},
loading: false,
redirect: undefined,
};
},
watch: {
$route: {
handler: function (route) {
const data = route.query;
if (data && data.redirect) {
this.redirect = data.redirect;
delete data.redirect;
if (JSON.stringify(data) !== "{}") {
this.redirect = this.redirect + "&" + qs.stringify(data, { indices: false });
}
}
},
immediate: true,
},
},
created() {
//
this.getCode();
// // Cookie
// this.getCookie()
// // token
// this.point()
import { useUserStore } from "@/store";
import { useRoute } from "vue-router";
import router from "@/router";
const route = useRoute();
let getinfo = localStorage.getItem("MerchantId");
let info = JSON.parse(getinfo);
if (info && info.merchantName) {
this.loginForm.merchantName = info.merchantName;
this.loginForm.username = info.username;
}
const state = reactive({
Background: Background,
codeUrl: "",
cookiePass: "",
loginForm: {
username: "",
password: "",
rememberMe: false,
code: "",
uuid: "",
merchantName: "",
loginType: "merchant",
},
methods: {
getCode() {
getCodeImg().then((res) => {
console.log(res);
this.codeUrl = res.img;
this.loginForm.uuid = res.uuid;
});
},
getCookie() {
const username = Cookies.get("username");
let password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
// cookie
this.cookiePass = password === undefined ? "" : password;
password = password === undefined ? this.loginForm.password : password;
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password,
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
code: "",
merchantName: "",
loginType: "merchant",
};
},
handleLogin() {
this.$refs.loginForm.validate((valid) => {
// const user = {
// username: this.loginForm.username,
// password: this.loginForm.password,
// rememberMe: this.loginForm.rememberMe,
// code: this.loginForm.code,
// uuid: this.loginForm.uuid,
// merchantName: this.loginForm.merchantName,
// loginType: this.loginForm.loginType
// }
// if (user.password !== this.cookiePass) {
// user.password = encrypt(user.password)
// }
if (valid) {
this.loading = true;
// if (user.rememberMe) {
// Cookies.set('username', user.username, { expires: Config.passCookieExpires })
// Cookies.set('password', user.password, { expires: Config.passCookieExpires })
// Cookies.set('rememberMe', user.rememberMe, { expires: Config.passCookieExpires })
// } else {
// Cookies.remove('username')
// Cookies.remove('password')
// Cookies.remove('rememberMe')
// }
// console.log(user);
const user = { ...this.loginForm };
console.log(user);
user.password = encrypt(user.password);
this.$store
.dispatch("Login", user)
.then(() => {
if (localStorage.getItem("shopName") != "admin") {
$douyin_checkIn({
loginName: user.username,
});
}
loginRules: {
username: [{ required: true, trigger: "blur", message: "用户名不能为空" }],
password: [{ required: true, trigger: "blur", message: "密码不能为空" }],
code: [{ required: true, trigger: "change", message: "验证码不能为空" }],
merchantName: [{ required: true, trigger: "change", message: "商户号不能为空" }],
},
loading: false,
redirect: undefined,
});
this.loading = false;
//
localStorage.setItem(
"MerchantId",
JSON.stringify({
merchantName: this.loginForm.merchantName,
username: this.loginForm.username,
})
);
this.$router.push({ path: this.redirect || "/" });
// window.location.replace = './'
})
.catch((err) => {
console.log(err);
this.loading = false;
this.getCode();
});
} else {
console.log("error submit!!");
return false;
}
});
},
point() {
const point = Cookies.get("point") !== undefined;
if (point) {
this.$notify({
title: "提示",
message: "当前登录状态已过期,请重新登录!",
type: "warning",
duration: 5000,
onMounted(() => {
//
getCode();
// // Cookie
// this.getCookie()
// // token
// this.point()
let getinfo = localStorage.getItem("MerchantId");
let info = JSON.parse(getinfo);
if (info && info.merchantName) {
state.loginForm.merchantName = info.merchantName;
state.loginForm.username = info.username;
}
});
function getCode() {
getCodeImg().then((res) => {
console.log(res);
state.codeUrl = res.img;
state.loginForm.uuid = res.uuid;
});
}
function getCookie() {
const username = Cookies.get("username");
let password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
// cookie
state.cookiePass = password === undefined ? "" : password;
password = password === undefined ? state.loginForm.password : password;
state.loginForm = {
username: username === undefined ? state.loginForm.username : username,
password: password,
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
code: "",
merchantName: "",
loginType: "merchant",
};
}
const loginForm = ref(null);
const userStore = useUserStore();
/**
* 解析 redirect 字符串 path queryParams
*
* @returns { path: string, queryParams: Record<string, string> } 解析后的 path queryParams
*/
function parseRedirect() {
const query = route.query;
const redirect = query.redirect ?? "/";
const url = new URL(redirect, window.location.origin);
const path = url.pathname;
const queryParams = {};
url.searchParams.forEach((value, key) => {
queryParams[key] = value;
});
return { path, queryParams };
}
function handleLogin() {
loginForm.value.validate((valid) => {
if (valid) {
state.loading = true;
const user = { ...state.loginForm };
user.password = encrypt(user.password);
console.log(user);
userStore
.login(user)
.then(async (res) => {
await userStore.getUserInfo();
const { path, queryParams } = parseRedirect();
router.push({ path: path, query: queryParams });
})
.catch(() => {
state.loading = false;
});
Cookies.remove("point");
}
},
},
};
} else {
console.log("error submit!!");
return false;
}
});
}
</script>
<style scoped lang="scss">
@ -287,6 +244,7 @@ export default {
.code_wrap {
display: flex;
justify-content: space-between;
width: 100%;
}
.login-code {

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

1
src/views/shop/index.vue Normal file
View File

@ -0,0 +1 @@
<template></template>

1
src/views/shop/log.vue Normal file
View File

@ -0,0 +1 @@
<template></template>

1
src/views/shop/role.vue Normal file
View File

@ -0,0 +1 @@
<template></template>

1
src/views/shop/staff.vue Normal file
View File

@ -0,0 +1 @@
<template></template>

View File

@ -0,0 +1 @@
<template></template>

5
src/views/tool/index.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div class="app-container">
11
</div>
</template>

3
src/views/tool/table.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<div class="app-container">11</div>
</template>

View File

0
src/views/user/index.vue Normal file
View File