Repository: webyang-male/vue3-mallManage
Branch: main
Commit: 4e81ca0a1a0e
Files: 28
Total size: 31.2 KB
Directory structure:
gitextract_4pghmfdd/
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── package.json
├── src/
│ ├── App.vue
│ ├── api/
│ │ └── manager.js
│ ├── axios.js
│ ├── components/
│ │ ├── FormDrawer.vue
│ │ └── HelloWorld.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
│ │ ├── goods/
│ │ │ └── list.vue
│ │ ├── index.vue
│ │ └── login.vue
│ ├── permission.js
│ ├── router/
│ │ └── index.js
│ └── store/
│ └── index.js
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
u.sh
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 是大赵同学鸭
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
### 项目简介:
`Vue3` + `ElementPlus` + `Vite`实战开发商城后台管理系统。
### 功能简介:
用户多权限管理、商品多规格实现、订单发货、导出订单、图库模块、分销模块、分享海报等。
### 采用技术
- `Vue3.2 + Vue-router4 + Vuex4 + Vite2 + Vueuse`
- `ElementPlus`
- `Naive-ui`
- `Windicss`
### 使用简介
#### 安装依赖
```bash
yarn or yarn install
```
#### 项目运行
```bash
yarn dev
```
#### 项目打包
```bash
yarn build
```
### 项目效果


### 

### 其他
感谢楚绵 (靓仔)的精品课程,受益匪浅。
如果项目大家觉得比较好,请留一个🌟。
================================================
FILE: index.html
================================================
Vue3 编程商城后台
================================================
FILE: package.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": "^9.3.0",
"@vueuse/integrations": "^8.4.1",
"axios": "^0.27.2",
"element-plus": "^2.1.11",
"jquery": "2.2.0",
"nprogress": "^0.2.0",
"universal-cookie": "^4.0.4",
"vue": "^3.2.25",
"vue-router": "^4.0.15",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"naive-ui": "^2.33.3",
"vite": "^2.9.7",
"vite-plugin-windicss": "^1.8.4",
"windicss": "^3.5.1"
}
}
================================================
FILE: src/App.vue
================================================
================================================
FILE: src/api/manager.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)
}
================================================
FILE: src/axios.js
================================================
import axios from "axios"
import { toast } from '~/composables/util'
import { getToken } from '~/composables/auth'
import store from "./store"
const service = axios.create({
baseURL:"/api"
})
// 添加请求拦截器
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,请先登录!"){
store.dispatch("logout").finally(()=>location.reload())
}
toast(msg,"error")
return Promise.reject(error);
})
export default service
================================================
FILE: src/components/FormDrawer.vue
================================================
================================================
FILE: src/components/HelloWorld.vue
================================================
{{ msg }}
Recommended IDE setup:
VS Code
+
Volar
Vite Documentation
|
Vue 3 Documentation
count is: {{ count }}
Edit
components/HelloWorld.vue to test hot module replacement.
================================================
FILE: src/composables/auth.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)
}
================================================
FILE: src/composables/useManager.js
================================================
import { ref, reactive } from 'vue'
import { logout, updatepassword } from "~/api/manager"
import { showModal, toast } from "~/composables/util"
import { useRouter } from "vue-router"
import { useStore } from "vuex"
export function useRepassword() {
const router = useRouter()
const store = useStore()
// 修改密码
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("修改密码成功,请重新登录")
store.dispatch("logout")
// 跳转回登录页
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 store = useStore()
function handleLogout() {
showModal("是否要退出登录?").then(res => {
logout().finally(() => {
store.dispatch("logout")
// 跳转回登录页
router.push("/login")
// 提示退出登录成功
toast("退出登录成功")
})
})
}
return {
handleLogout
}
}
================================================
FILE: src/composables/useTabList.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) => {
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[index + 1] || tabs[index - 1];
if (nextTab) {
a = nextTab.path;
}
}
});
}
activeTab.value = a;
tabList.value = tabList.value.filter((tab) => tab.path != t);
cookie.set("tabList", tabList.value);
};
//定时清除cookie
const time = 2 * 60 * 60 * 1000;
setInterval(() => {
const key = cookie.get("tabList");
if (key) {
cookie.remove(key);
}
}, time);
const handleClose = (c) => {
console.log(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,
};
}
================================================
FILE: src/composables/util.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(
content,
title,
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type,
}
)
}
================================================
FILE: src/layouts/admin.vue
================================================
================================================
FILE: src/layouts/components/FHeader.vue
================================================
================================================
FILE: src/layouts/components/FMenu.vue
================================================
================================================
FILE: src/layouts/components/FTagList.vue
================================================
================================================
FILE: src/main.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 store from "./store";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
const app = createApp(App);
app.use(store);
app.use(router);
app.use(ElementPlus);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
import "virtual:windi.css";
import "./permission";
import "nprogress/nprogress.css";
app.mount("#app");
================================================
FILE: src/pages/404.vue
================================================
================================================
FILE: src/pages/category/list.vue
================================================
分类列表
================================================
FILE: src/pages/goods/list.vue
================================================
商品管理
================================================
FILE: src/pages/index.vue
================================================
后台首页
{{ $store.state.user.username }}
================================================
FILE: src/pages/login.vue
================================================
欢迎光临 🎓
此站点是《vue3 + vite实战商城后台开发》的演示地址
欢迎回来
账号密码登录
登 录
================================================
FILE: src/permission.js
================================================
import { router, addRoutes } from "~/router";
import { getToken } from "~/composables/auth";
import { toast, showFullLoading, hideFullLoading } from "~/composables/util";
import store from "./store";
// 全局前置守卫
let hasGetInfo = false;
router.beforeEach(async (to, from, next) => {
// 显示loading
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 : "/" });
}
// 如果用户登录了,自动获取用户信息,并存储在vuex当中
let hasNewRoutes = false;
if (token && !hasGetInfo) {
let { menus } = await store.dispatch("getinfo");
hasGetInfo = true;
//动态添加路由
hasNewRoutes = addRoutes(menus);
}
// 设置页面标题
let title = (to.meta.title ? to.meta.title : "Vue3") + "-追梦编程商城后台";
document.title = title;
hasNewRoutes ? next(to.fullPath) : next();
});
// 全局后置守卫
router.afterEach((to, from) => hideFullLoading());
================================================
FILE: src/router/index.js
================================================
import { createRouter, createWebHashHistory } from "vue-router";
import Admin from "~/layouts/admin.vue";
import Index from "~/pages/index.vue";
import Login from "~/pages/login.vue";
import NotFound from "~/pages/404.vue";
import GoodList from "~/pages/goods/list.vue";
import CategoryList from "~/pages/category/list.vue";
/* const routes = [
{
path:"/",
component:Admin,
// 子路由
children:[]
},
{
path:"/login",
component:Login,
meta:{
title:"登录页"
}
},{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}] */
//默认路由,所有用户共享
const routes = [
{
path: "/",
name: "admin",
component: Admin,
},
{
path: "/login",
component: Login,
meta: {
title: "登录页",
},
},
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: NotFound,
},
];
//动态匹配添加路由
const asyncRoutes = [
{
path: "/",
name: "/",
component: Index,
meta: {
title: "后台首页",
},
},
{
path: "/goods/list",
name: "/goods/list",
component: GoodList,
meta: {
title: "商品管理",
},
},
{
path: "/category/list",
name: "/category/list",
component: CategoryList,
meta: {
title: "分类列表",
},
},
];
export const router = createRouter({
history: createWebHashHistory(),
routes,
});
//动态添加路由方法
export function addRoutes(menus) {
//是否有新的路由
let hasNewRoutes = false;
const findAndAddRoutesByMenus = (arr) => {
arr.forEach((e) => {
let item = asyncRoutes.find((o) => o.path == e.frontpath);
if (item && !router.hasRoute(item.path)) {
router.addRoute("admin", item);
hasNewRoutes = true;
}
//子路由
if (e.child && e.child.length > 0) {
findAndAddRoutesByMenus(e.child);
}
});
};
findAndAddRoutesByMenus(menus)
// console.log("获取路由",router.getRoutes());
return hasNewRoutes;
}
================================================
FILE: src/store/index.js
================================================
import { createStore } from "vuex";
import { login, getinfo } from "~/api/manager";
import { setToken, removeToken } from "~/composables/auth";
const store = createStore({
state() {
return {
// 用户信息
user: {},
// 侧边宽度
asideWidth: "250px",
menus: [],
ruleNames: [],
};
},
mutations: {
// 记录用户信息
SET_USERINFO(state, user) {
state.user = user;
},
// 展开/缩起侧边
handleAsideWidth(state) {
state.asideWidth = state.asideWidth == "250px" ? "64px" : "250px";
},
SET_MENUS(state, menu) {
state.menus = menu;
},
SET_RULENAMES(state, ruleNames) {
state.ruleNames = ruleNames;
},
},
actions: {
// 登录
login({ commit }, { username, password }) {
return new Promise((resolve, reject) => {
login(username, password)
.then((res) => {
setToken(res.token);
resolve(res);
})
.catch((err) => reject(err));
});
},
// 获取当前登录用户信息
getinfo({ commit }) {
return new Promise((resolve, reject) => {
getinfo()
.then((res) => {
console.log(res);
commit("SET_USERINFO", res);
commit("SET_MENUS", res.menus);
commit("SET_RULENAMES", res.ruleNames);
resolve(res);
})
.catch((err) => reject(err));
});
},
// 退出登录
logout({ commit }) {
// 移除cookie里的token
removeToken();
// 清除当前用户状态 vuex
commit("SET_USERINFO", {});
},
},
});
export default store;
================================================
FILE: vite.config.js
================================================
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import WindiCSS from 'vite-plugin-windicss'
import path from "path"
// https://vitejs.dev/config/
export default defineConfig({
resolve:{
// src目录别名
alias:{
"~":path.resolve(__dirname,"src"),
}
},
server:{
proxy:{
'/api': {
target: 'http://ceshi13.dishait.cn',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
},
plugins: [vue(),WindiCSS()]
})