简约易用的 Vue3 后台管理系统。
```基于帝莎编程改写的
前端源码
## 文件夹结构
```
├── .gitignore
├── .vscode/
├── README.md
├── index.html
├── node_modules/
├── package-lock.json
├── package.json
├── public/
├── src/
│ ├── App.vue
│ ├── api/
│ │ ├── index.js
│ │ ├── manager.js
│ │ └── user.js
│ ├── assets/
│ │ └── logo.png
│ ├── axios.js
│ ├── components/
│ │ ├── CountTo.vue
│ │ ├── FormDrawer.vue
│ │ ├── IndexCard.vue
│ │ ├── IndexChart.vue
│ │ └── IndexNavs.vue
│ ├── composables/
│ │ ├── auth.js
│ │ ├── useManager.js
│ │ ├── useTabList.js
│ │ └── util.js
│ ├── layouts/
│ │ ├── admin.vue
│ │ └── components/
│ │ ├── FHeader.vue
│ │ ├── FMenu.vue
│ │ └── FTagList.vue
│ ├── main.js
│ ├── pages/
│ │ ├── 404.vue
│ │ ├── category/
│ │ │ └── list.vue
│ │ ├── comment/
│ │ │ └── list.vue
│ │ ├── coupon/
│ │ │ └── list.vue
│ │ ├── goods/
│ │ │ └── list.vue
│ │ ├── image/
│ │ │ └── list.vue
│ │ ├── index.vue
│ │ ├── login.vue
│ │ ├── notice/
│ │ │ └── list.vue
│ │ ├── order/
│ │ │ └── list.vue
│ │ ├── setting/
│ │ │ └── base.vue
│ │ └── user/
│ │ └── list.vue
│ ├── permission.js
│ ├── router/
│ │ └── index.js
│ └── store/
│ └── index.js
├── vite.config.js
└── 课时50.店铺和交易提示组件开发和交互汇总源码.md
```
====================== 课时50.店铺和交易提示组件开发和交互\.gitignore ======================
## 课时50.店铺和交易提示组件开发和交互\.gitignore
```# 内容未读取(排除)
```
====================== 课时50.店铺和交易提示组件开发和交互\index.html ======================
## 课时50.店铺和交易提示组件开发和交互\index.html
```html
Vite App
```
====================== 课时50.店铺和交易提示组件开发和交互\package-lock.json ======================
## 课时50.店铺和交易提示组件开发和交互\package-lock.json
```# 内容未读取(排除)
```
====================== 课时50.店铺和交易提示组件开发和交互\package.json ======================
## 课时50.店铺和交易提示组件开发和交互\package.json
```json
{
"name": "shop-admin",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^1.1.4",
"@vueuse/core": "^8.4.2",
"@vueuse/integrations": "^8.4.1",
"axios": "^0.27.2",
"echarts": "^5.3.2",
"element-plus": "^2.1.11",
"gsap": "^3.10.4",
"nprogress": "^0.2.0",
"pinia": "^2.2.4",
"universal-cookie": "^4.0.4",
"vue": "^3.2.25",
"vue-router": "^4.0.15"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"mockjs": "^1.1.0",
"vite": "^2.9.7",
"vite-plugin-windicss": "^1.8.4",
"windicss": "^3.5.1"
}
}
```
====================== 课时50.店铺和交易提示组件开发和交互\README.md ======================
## 课时50.店铺和交易提示组件开发和交互\README.md
```md
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `
body{
@apply bg-gray-100;
}
#nprogress .bar{
background-color: #f4f4f4!important;
height: 3px!important;
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\axios.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\axios.js
```js
import axios from "axios"
import { toast } from '~/composables/util'
import { getToken } from '~/composables/auth'
import { useUserStore } from '~/store' // 导入 Pinia store
const service = axios.create({
baseURL: 'http://localhost:3000/api', // 指向后端服务器
timeout: 8000,
});
// 添加请求拦截器
service.interceptors.request.use(function (config) {
// 往header头自动添加token
const token = getToken()
if (token) {
config.headers["token"] = token
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response.data.data;
}, function (error) {
const msg = error.response.data.msg || "请求失败"
if (msg == "非法token,请先登录!") {
const userStore = useUserStore() // 使用 Pinia store
userStore.logoutUser().finally(() => location.reload())
}
toast(msg, "error")
return Promise.reject(error);
})
export default service
```
====================== 课时50.店铺和交易提示组件开发和交互\src\main.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\main.js
```js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import { router } from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { createPinia } from 'pinia'
// import './mock' // 同步引入
import 'virtual:windi.css'
import 'nprogress/nprogress.css'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.use(ElementPlus)
// 全局注册 Element Plus 图标组件
for (const of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 确保在初始化 Pinia 和其他插件之后再导入权限控制
import "./permission"
app.mount('#app')
```
====================== 课时50.店铺和交易提示组件开发和交互\src\permission.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\permission.js
```js
import { router, addRoutes } from "~/router"
import { getToken } from "~/composables/auth"
import {
toast,
showFullLoading,
hideFullLoading
} from "~/composables/util"
import { useUserStore } from '~/store'
let hasGetInfo = false
router.beforeEach(async (to, from, next) => {
showFullLoading()
const token = getToken()
if (!token && to.path !== "/login") {
toast("请先登录", "error")
return next({ path: "/login" })
}
if (token && to.path === "/login") {
toast("请勿重复登录", "error")
return next({ path: from.path ? from.path : "/" })
}
const userStore = useUserStore()
let hasNewRoutes = false
if (token && !hasGetInfo) {
let { menus } = await userStore.fetchUserInfo()
hasGetInfo = true
hasNewRoutes = addRoutes(menus)
}
let title = (to.meta.title ? to.meta.title : "")
document.title = title
hasNewRoutes ? next(to.fullPath) : next()
})
router.afterEach((to, from) => hideFullLoading())
```
====================== 课时50.店铺和交易提示组件开发和交互\src\api\index.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\api\index.js
```js
import axios from '~/axios'
export function getStatistics1(){
return axios.get("/admin/statistics1")
}
export function getStatistics2(){
return axios.get("/admin/statistics2")
}
export function getStatistics3(type){
return axios.get("/admin/statistics3?type="+type)
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\api\manager.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\api\manager.js
```js
import axios from '~/axios'
export function login(username,password){
return axios.post("/admin/login",{
username,
password
})
}
export function getinfo(){
return axios.post("/admin/getinfo")
}
export function logout(){
return axios.post("/admin/logout")
}
export function updatepassword(data){
return axios.post("/admin/updatepassword",data)
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\api\user.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\api\user.js
```js
// src/api/user.js
import axios from '~/axios';
export function uploadAvatar(data) {
return axios.post('/admin/upload', data, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
export function updateUserInfo(data) {
return axios.post('/admin/updateuserinfo', data);
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\assets\logo.png ======================
## 课时50.店铺和交易提示组件开发和交互\src\assets\logo.png
```# 内容未读取(排除)
```
====================== 课时50.店铺和交易提示组件开发和交互\src\components\CountTo.vue ======================
## 课时50.店铺和交易提示组件开发和交互\src\components\CountTo.vue
```vue
{{ d.num.toFixed(0) }}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\components\FormDrawer.vue ======================
## 课时50.店铺和交易提示组件开发和交互\src\components\FormDrawer.vue
```vue
{{ confirmText }}
取消
.formDrawer{
width: 100%;
height: 100%;
position: relative;
@apply flex flex-col;
}
.formDrawer .body{
flex: 1;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 50px;
overflow-y: auto;
}
.formDrawer .actions{
height: 50px;
@apply mt-auto flex items-center;
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\components\IndexCard.vue ======================
## 课时50.店铺和交易提示组件开发和交互\src\components\IndexCard.vue
```vue
{{ title }}
{{ tip }}
{{ item.value }}
{{ item.label }}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\components\IndexChart.vue ======================
## 课时50.店铺和交易提示组件开发和交互\src\components\IndexChart.vue
```vue
订单统计
{{ item.text }}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\components\IndexNavs.vue ======================
## 课时50.店铺和交易提示组件开发和交互\src\components\IndexNavs.vue
```vue
{{ item.title }}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\composables\auth.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\composables\auth.js
```js
import { useCookies } from '@vueuse/integrations/useCookies'
const TokenKey = "admin-token"
const cookie = useCookies()
// 获取token
export function getToken(){
return cookie.get(TokenKey)
}
// 设置token
export function setToken(token){
return cookie.set(TokenKey,token)
}
// 清除token
export function removeToken(){
return cookie.remove(TokenKey)
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\composables\useManager.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\composables\useManager.js
```js
import { ref, reactive } from 'vue'
import { logout, updatepassword } from "~/api/manager"
import { showModal, toast } from "~/composables/util"
import { useRouter } from "vue-router"
import { useUserStore } from '~/store' // 导入 Pinia store
export function useRepassword() {
const router = useRouter()
const userStore = useUserStore() // 使用 Pinia store
// 修改密码
const formDrawerRef = ref(null)
const form = reactive({
oldpassword: "",
password: "",
repassword: ""
})
const rules = {
oldpassword: [
{
required: true,
message: '旧密码不能为空',
trigger: 'blur'
},
],
password: [
{
required: true,
message: '新密码不能为空',
trigger: 'blur'
},
],
repassword: [
{
required: true,
message: '确认密码不能为空',
trigger: 'blur'
},
]
}
const formRef = ref(null)
const onSubmit = () => {
formRef.value.validate((valid) => {
if (!valid) {
return false
}
formDrawerRef.value.showLoading()
updatepassword(form)
.then(res => {
toast("修改密码成功,请重新登录")
userStore.logoutUser() // 使用 Pinia store 的 action
// 跳转回登录页
router.push("/login")
})
.finally(() => {
formDrawerRef.value.hideLoading()
})
})
}
const openRePasswordForm = () => formDrawerRef.value.open()
return {
formDrawerRef,
form,
rules,
formRef,
onSubmit,
openRePasswordForm
}
}
export function useLogout() {
const router = useRouter()
const userStore = useUserStore() // 使用 Pinia store
function handleLogout() {
showModal("是否要退出登录?").then(res => {
logout().finally(() => {
userStore.logoutUser() // 使用 Pinia store 的 action
// 跳转回登录页
router.push("/login")
// 提示退出登录成功
toast("退出登录成功")
})
})
}
return {
handleLogout
}
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\composables\useTabList.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\composables\useTabList.js
```js
import { ref } from 'vue'
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useCookies } from '@vueuse/integrations/useCookies'
import { router } from '~/router';
export function useTabList() {
const route = useRoute()
const cookie = useCookies()
const activeTab = ref(route.path)
const tabList = ref([
{
title: '后台首页',
path: "/"
},
])
// 添加标签导航
function addTab(tab) {
let noTab = tabList.value.findIndex(t => t.path == tab.path) == -1
if (noTab) {
tabList.value.push(tab)
}
cookie.set("tabList", tabList.value)
}
// 初始化标签导航列表
function initTabList() {
let tbs = cookie.get("tabList")
if (tbs) {
tabList.value = tbs
}
}
initTabList()
onBeforeRouteUpdate((to, from) => {
activeTab.value = to.path
addTab({
title: to.meta.title,
path: to.path
})
})
const changeTab = (t) => {
console.log(route.path);
activeTab.value = t
router.push(t)
}
const removeTab = (t) => {
let tabs = tabList.value
let a = activeTab.value
if (a == t) {
tabs.forEach((tab, index) => {
if (tab.path == t) {
const nextTab = tabs || tabs
if (nextTab) {
a = nextTab.path
}
}
})
}
activeTab.value = a
tabList.value = tabList.value.filter(tab => tab.path != t)
cookie.set("tabList", tabList.value)
}
const handleClose = (c) => {
if (c == "clearAll") {
// 切换回首页
activeTab.value = "/"
// 过滤只剩下首页
tabList.value = [{
title: '后台首页',
path: "/"
}]
} else if (c == "clearOther") {
// 过滤只剩下首页和当前激活
tabList.value = tabList.value.filter(tab => tab.path == "/" || tab.path == activeTab.value)
}
cookie.set("tabList", tabList.value)
}
return {
activeTab,
tabList,
changeTab,
removeTab,
handleClose
}
}
```
====================== 课时50.店铺和交易提示组件开发和交互\src\composables\util.js ======================
## 课时50.店铺和交易提示组件开发和交互\src\composables\util.js
```js
import { ElNotification,ElMessageBox } from 'element-plus'
import nprogress from 'nprogress'
// 消息提示
export function toast(message,type = "success",dangerouslyUseHTMLString = false){
ElNotification({
message,
type,
dangerouslyUseHTMLString,
duration:3000
})
}
// 显示全屏loading
export function showFullLoading(){
nprogress.start()
}
// 隐藏全屏loading
export function hideFullLoading(){
nprogress.done()
}
export function showModal(content = "提示内容",type = "warning",title = ""){
return ElMessageBox.confirm(
页:
[1]