├── babel.config.js ├── TODO.md ├── public ├── favicon.ico └── index.html ├── .travis.yml ├── src ├── assets │ └── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png ├── layout │ ├── components │ │ ├── index.js │ │ ├── Sidebar │ │ │ ├── Item.vue │ │ │ ├── FixiOSBug.js │ │ │ ├── Link.vue │ │ │ ├── index.vue │ │ │ ├── Logo.vue │ │ │ └── SidebarItem.vue │ │ ├── AppMain.vue │ │ └── Navbar.vue │ ├── mixin │ │ └── ResizeHandler.js │ └── index.vue ├── icons │ ├── svg │ │ ├── link.svg │ │ ├── example.svg │ │ ├── table.svg │ │ ├── user.svg │ │ ├── nested.svg │ │ ├── market.svg │ │ ├── eye.svg │ │ ├── feedback.svg │ │ ├── comment.svg │ │ ├── admin.svg │ │ ├── setting.svg │ │ ├── eye-open.svg │ │ ├── activity.svg │ │ ├── order.svg │ │ ├── ordersvg.svg │ │ ├── banner.svg │ │ ├── password.svg │ │ ├── brand.svg │ │ ├── operation.svg │ │ ├── tree.svg │ │ ├── afterSales.svg │ │ ├── cat.svg │ │ ├── recommends.svg │ │ ├── dashboard.svg │ │ ├── form.svg │ │ ├── coupon.svg │ │ ├── supperlier.svg │ │ └── service.svg │ ├── index.js │ └── svgo.yml ├── utils │ ├── auth.js │ ├── getPageTitle.js │ ├── logisticsCompany.js │ ├── validate.js │ ├── scroll-to.js │ ├── guid.js │ ├── request.js │ └── index.js ├── core │ └── directives │ │ ├── directives.js │ │ ├── index.js │ │ └── module │ │ └── clipboard.js ├── api │ ├── feedback.js │ ├── upload.js │ ├── app.js │ ├── brand.js │ ├── supplier.js │ ├── orderAfterService.js │ ├── cat.js │ ├── afterService.js │ ├── banner.js │ ├── refundReasons.js │ ├── recommends.js │ ├── admin.js │ ├── order.js │ ├── user.js │ ├── comment.js │ ├── catSpec.js │ ├── coupon.js │ ├── goods.js │ └── activity.js ├── config │ └── index.js ├── main.js ├── styles │ ├── mixin.scss │ ├── variables.scss │ ├── transition.scss │ ├── element-ui.scss │ ├── index.scss │ └── sidebar.scss ├── settings.js ├── store │ ├── getters.js │ ├── modules │ │ ├── settings.js │ │ ├── app.js │ │ └── user.js │ └── index.js ├── install.js ├── App.vue ├── components │ ├── Hamburger │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── Breadcrumb │ │ └── index.vue │ ├── password │ │ └── index.vue │ ├── Table │ │ └── index.vue │ ├── upload │ │ └── index.vue │ └── cropperImg │ │ └── index.vue ├── views │ ├── dashboard │ │ └── index.vue │ ├── worksheet │ │ ├── index.vue │ │ └── worksheetcom.vue │ ├── 404.vue │ ├── login │ │ └── index.vue │ └── Admin │ │ └── index.vue └── router │ └── index.js ├── jsconfig.json ├── postcss.config.js ├── .gitignore ├── README.md ├── .env.development ├── .env.staging ├── .env.production ├── package.json └── vue.config.js /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # 待完成时间列表 2 | 3 | - 陈伟完成基础框架搭建 4 | 5 | - 表格封装 田浩 6 | - tree 田浩 7 | - 进度 田浩 8 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoliancaijing/admin-vue-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoliancaijing/admin-vue-template/HEAD/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoliancaijing/admin-vue-template/HEAD/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | build/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | package-lock.json 9 | tests/**/coverage/ 10 | .history/ 11 | 12 | # Editor directories and files 13 | .idea 14 | .vscode 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | -------------------------------------------------------------------------------- /src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | vue3.0 后台管理系统模板 开箱即用,基于vue-cli、element、vuex, 七牛 演示地址: 3 | 4 | 5 | ## 技术 6 | 7 | > 当前模板基于 vue3.0、 vue-cli3.0 、element 2.23.x 、七牛的文件存储 8 | - 9 | 10 | ## 组件 11 | - 12 | ## units 说明 13 | - 14 | 15 | ## 外部插件 16 | - 17 | 18 | ## 七牛集成 图片上传,文件上传说明 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Authorization' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/core/directives/directives.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-07-31 14:58:58 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 14:59:28 6 | * @Descripttion: 所有的指令集合 7 | */ 8 | 9 | import clipboard from './module/clipboard' 10 | 11 | const directives = { 12 | clipboard, 13 | } 14 | 15 | export default directives 16 | -------------------------------------------------------------------------------- /src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /src/api/feedback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-18 20:22:23 4 | * @LastEditors: tianhao 5 | * @LastEditTime: 2020-06-18 20:22:49 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request'; 9 | export function getList(data) { 10 | return request({ 11 | url: '/adminApi/feedback', 12 | method: 'get', 13 | data, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/api/upload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-12 18:25:22 4 | * @LastEditors: 刘家辰 5 | * @LastEditTime: 2020-07-03 10:11:27 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | export function getToken(data) { 10 | return request({ 11 | url: '/adminApi/qiniu/token', 12 | method: 'get', 13 | data, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-12 18:39:19 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 11:51:04 6 | * @Descripttion: 7 | */ 8 | export const url = { 9 | qiniuUrl: process.env.VUE_APP_QN_UP_URL, //七牛上传url 10 | qiniuDownload: process.env.VUE_APP_QN_DOWNLOAD_URL, //七牛上传文件下载url qbqompd3x.bkt.clouddn.com 11 | } 12 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 模式 2 | ENV = 'production' 3 | 4 | # 基础api 地址 5 | VUE_APP_BASE_API = 'http://192.168.101.100:3000' 6 | 7 | # 七牛下载地址 8 | VUE_APP_QN_DOWNLOAD_URL = 'http://cdn.test.xiang360.com' 9 | 10 | # 七牛上传地址 11 | VUE_APP_QN_up_URL = 'https://up-z2.qiniup.com' 12 | 13 | # 优惠券h5分享地址 14 | VUE_APP_H5_API = 'http://h5.test.xiang360.com/' 15 | 16 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 17 | -------------------------------------------------------------------------------- /src/api/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: xin.chen 3 | * @Date: 2020-09-21 19:03:58 4 | * @LastEditors: xin.chen 5 | * @LastEditTime: 2020-09-21 19:08:38 6 | * @Descripttion: 7 | */ 8 | 9 | import request from '@/utils/request'; 10 | 11 | // 获取支持服务 12 | export function getWorksheet(data) { 13 | return request({ 14 | url: '/worksheetlist', 15 | method: 'get', 16 | data, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | # 模式 2 | ENV = 'production' 3 | 4 | # 接口API 地址 5 | VUE_APP_BASE_API = 'http://api.test.xiang360.com/' 6 | 7 | # 第三方地址配置 8 | # 七牛上传地址 上传地址都是同一个 9 | VUE_APP_QN_UP_URL = 'https://up-z2.qiniup.com' 10 | # 七牛下载地址 11 | VUE_APP_QN_DOWNLOAD_URL = 'http://cdn.test.xiang360.com' 12 | 13 | 14 | 15 | # 优惠券h5分享地址 16 | VUE_APP_H5_API = 'http://h5.test.xiang360.com/' 17 | 18 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 19 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 模式 2 | ENV = 'production' 3 | 4 | # 接口API 地址 5 | VUE_APP_BASE_API = 'http://api.test.xiang360.com/' 6 | 7 | # 第三方地址配置 8 | # 七牛上传地址 上传地址都是同一个 9 | VUE_APP_QN_UP_URL = 'https://up-z2.qiniup.com' 10 | # 七牛下载地址 11 | VUE_APP_QN_DOWNLOAD_URL = 'http://cdn.test.xiang360.com' 12 | 13 | 14 | 15 | # 优惠券h5分享地址 16 | VUE_APP_H5_API = 'http://h5.test.xiang360.com/' 17 | 18 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 19 | 20 | -------------------------------------------------------------------------------- /src/utils/getPageTitle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-11 12:42:39 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 15:52:21 6 | * @Descripttion: 7 | */ 8 | 9 | import defaultSettings from '@/settings' 10 | 11 | const title = defaultSettings.title || 'Admin' 12 | 13 | export function getPageTitle(pageTitle) { 14 | if (pageTitle) { 15 | return `${pageTitle} - ${title}` 16 | } 17 | return `${title}` 18 | } 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-11 12:42:56 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 12:49:58 6 | * @Descripttion: 7 | */ 8 | 9 | import Vue from 'vue' 10 | 11 | import './install' 12 | 13 | import App from './App' 14 | import store from './store' 15 | import router from './router' 16 | 17 | Vue.config.productionTip = false 18 | 19 | new Vue({ 20 | el: '#app', 21 | router, 22 | store, 23 | render: h => h(App), 24 | }) 25 | -------------------------------------------------------------------------------- /src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-11 12:42:39 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 12:21:34 6 | * @Descripttion: 项目的配置文件,主要是配置惨淡 头部logo 等 7 | */ 8 | 9 | module.exports = { 10 | /** 11 | * 项目的默认标题 12 | */ 13 | title: 'Admin', 14 | 15 | /** 16 | * @type {boolean} true | false 17 | * @description 固定顶部 18 | */ 19 | fixedHeader: true, 20 | 21 | /** 22 | * @type {boolean} true | false 23 | * @description 侧边栏是否显示logo 24 | */ 25 | sidebarLogo: true, 26 | } 27 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-20 18:55:32 4 | * @LastEditors: xin.chen 5 | * @LastEditTime: 2020-09-21 12:14:06 6 | * @Descripttion: 7 | */ 8 | 9 | const getters = { 10 | sidebar: state => state.app.sidebar, 11 | device: state => state.app.device, 12 | upLoadToken: state => state.app.upLoadToken, 13 | token: state => state.user.token, 14 | avatar: state => state.user.avatar, 15 | name: state => state.user.name, 16 | field: state => state.app.field, 17 | }; 18 | export default getters; 19 | -------------------------------------------------------------------------------- /src/core/directives/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-07-31 14:57:36 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 15:02:21 6 | * @Descripttion: 配置vue 的指令 7 | */ 8 | 9 | import directive from './directives' 10 | 11 | const importDirective = Vue => { 12 | /** 13 | * clipboard指令 v-draggable="options" 14 | * options = { 15 | * value: /在输入框中使用v-model绑定的值/, 16 | * success: /复制成功后的回调/, 17 | * error: /复制失败后的回调/ 18 | * } 19 | */ 20 | Vue.directive('clipboard', directive.clipboard) 21 | } 22 | 23 | export default importDirective 24 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/api/brand.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-16 10:10:11 4 | * @LastEditors: 刘家辰 5 | * @LastEditTime: 2020-07-03 10:10:32 6 | * @Descripttion: 品牌管理 7 | */ 8 | import request from '@/utils/request' 9 | export function getBrand(data) { 10 | return request({ 11 | url: '/adminApi/brand', 12 | method: 'get', 13 | data, 14 | }) 15 | } 16 | export function addBrand(data) { 17 | return request({ 18 | url: '/adminApi/brand', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | export function editBrand(data) { 24 | return request({ 25 | url: `/adminApi/brand/${data.id}`, 26 | method: 'put', 27 | data, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings 4 | 5 | const state = { 6 | showSettings: showSettings, 7 | fixedHeader: fixedHeader, 8 | sidebarLogo: sidebarLogo 9 | } 10 | 11 | const mutations = { 12 | CHANGE_SETTING: (state, { key, value }) => { 13 | if (state.hasOwnProperty(key)) { 14 | state[key] = value 15 | } 16 | } 17 | } 18 | 19 | const actions = { 20 | changeSetting({ commit }, data) { 21 | commit('CHANGE_SETTING', data) 22 | } 23 | } 24 | 25 | export default { 26 | namespaced: true, 27 | state, 28 | mutations, 29 | actions 30 | } 31 | 32 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device; 5 | }, 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-adminApi/issues/1135 10 | this.fixBugIniOS(); 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu; 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave; 17 | $subMenu.handleMouseleave = e => { 18 | if (this.device === 'mobile') { 19 | return; 20 | } 21 | handleMouseleave(e); 22 | }; 23 | } 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-12 17:14:55 4 | * @LastEditors: tianhao 5 | * @LastEditTime: 2020-06-12 17:58:35 6 | * @Descripttion: 7 | */ 8 | 9 | import Vue from 'vue'; 10 | import Vuex from 'vuex'; 11 | import getters from './getters'; 12 | import app from './modules/app'; 13 | import settings from './modules/settings'; 14 | import user from './modules/user'; 15 | import createPersistedState from 'vuex-persistedstate'; 16 | Vue.use(Vuex); 17 | 18 | const store = new Vuex.Store({ 19 | modules: { 20 | app, 21 | settings, 22 | user, 23 | }, 24 | getters, 25 | plugins: [ 26 | createPersistedState({ 27 | storage: window.sessionStorage, 28 | }), 29 | ], 30 | }); 31 | 32 | export default store; 33 | -------------------------------------------------------------------------------- /src/utils/logisticsCompany.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-06-23 14:51:35 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-06-23 14:54:54 6 | * @Descripttion: 7 | */ 8 | 9 | let logistiCsompany = [ 10 | { code: '1', name: '顺丰快递' }, 11 | { code: '2', name: '中通快递' }, 12 | { code: '3', name: '圆通快递' }, 13 | { code: '4', name: '韵达快递' }, 14 | { code: '5', name: '申通快递' }, 15 | { code: '6', name: '百世汇通' }, 16 | { code: '7', name: '邮政EMS' }, 17 | { code: '8', name: '联通快递' }, 18 | { code: '9', name: '天天快递' }, 19 | ] 20 | 21 | // 1.顺丰快递 22 | 23 | // 2.中通快递 24 | 25 | // 3.圆通快递 26 | 27 | // 4.韵达快递 28 | 29 | // 5.申通快递 30 | 31 | // 6.百世汇通 32 | 33 | // 7.邮政EMS 34 | 35 | // 8.联通快递 36 | 37 | // 9.天天快递 38 | 39 | export { logistiCsompany } 40 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-11 11:46:00 4 | * @LastEditTime: 2020-07-31 12:54:10 5 | * @LastEditors: wei.chen 6 | * @Description: 7 | */ 8 | 9 | /** 10 | * Created by PanJiaChen on 16/11/18. 11 | */ 12 | 13 | /** 14 | * @param {string} path 15 | * @returns {Boolean} 16 | */ 17 | export function isExternal(path) { 18 | return /^(https?:|mailto:|tel:)/.test(path) 19 | } 20 | 21 | /** 22 | * @name: 23 | * @description: 手机号码校验 24 | * 进行严谨的规则匹配 25 | * /^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$/ 26 | * 非强校验 27 | * @param {type} 28 | * @return: 29 | */ 30 | export function isPhone(val) { 31 | const phoneReg = /^[1]([3-9])[0-9]{9}$/ 32 | return phoneReg.test(val) 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText: #bfcbd9; 3 | $menuActiveText: #409eff; 4 | $subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | $navTextColor: #fff; 6 | $menuBg: #2e2f30; 7 | $menuHover: #001529; 8 | $subMenuBg: #000c17; 9 | $subMenuHover: #000c17; 10 | 11 | $sideBarWidth: 210px; 12 | 13 | // the :export directive is the magic sauce for webpack 14 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 15 | :export { 16 | menuText: $menuText; 17 | menuActiveText: $menuActiveText; 18 | subMenuActiveText: $subMenuActiveText; 19 | menuBg: $menuBg; 20 | menuHover: $menuHover; 21 | subMenuBg: $subMenuBg; 22 | subMenuHover: $subMenuHover; 23 | sideBarWidth: $sideBarWidth; 24 | navTextColor: $navTextColor; 25 | } 26 | -------------------------------------------------------------------------------- /src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/supplier.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-12 17:33:37 4 | * @LastEditors: 刘家辰 5 | * @LastEditTime: 2020-07-03 10:11:15 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | 10 | export function getList(data) { 11 | return request({ 12 | url: '/adminApi/supplier', 13 | method: 'get', 14 | data, 15 | }) 16 | } 17 | export function addSupplier(data) { 18 | return request({ 19 | url: '/adminApi/supplier', 20 | method: 'post', 21 | data, 22 | }) 23 | } 24 | export function editSupplier(data) { 25 | return request({ 26 | url: `/adminApi/supplier/${data.id}`, 27 | method: 'put', 28 | data, 29 | }) 30 | } 31 | export function getSupplierBrand(data) { 32 | return request({ 33 | url: `/adminApi/supplier/${data.id}/brand`, 34 | method: 'get', 35 | data, 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/icons/svg/market.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /src/icons/svg/feedback.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/orderAfterService.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-07-01 19:38:31 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-01 19:43:20 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | 10 | // 获取售后单列表 11 | export function getAfterServiceList(data) { 12 | return request({ 13 | url: '/adminApi/order/afterService', 14 | method: 'get', 15 | data, 16 | }) 17 | } 18 | // 获取售后单详情 19 | export function getAfterServiceInfo(data) { 20 | return request({ 21 | url: `/adminApi/order/afterService/${data.id}`, 22 | method: 'get', 23 | data, 24 | }) 25 | } 26 | // 售后申请通过 27 | export function applyForPass(data) { 28 | return request({ 29 | url: `/adminApi/order/afterService/${data.id}/pass`, 30 | method: 'put', 31 | data, 32 | }) 33 | } 34 | // 售后申请拒绝 35 | export function applyForReject(data) { 36 | return request({ 37 | url: `/adminApi/order/afterService/${data.id}/reject`, 38 | method: 'put', 39 | data, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/install.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-07-31 12:45:27 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 12:51:09 6 | * @Descripttion: 这个文件是用来安装其它第三方组件配置 7 | */ 8 | import Vue from 'vue' 9 | // 图片剪切组件 10 | import VueCropper from 'vue-cropper' 11 | // 样式 12 | import 'normalize.css/normalize.css' // css 重置样式 13 | import '@/styles/index.scss' // 全局样式 14 | 15 | // element 配置 16 | import ElementUI from 'element-ui' 17 | import 'element-ui/lib/theme-chalk/index.css' 18 | import locale from 'element-ui/lib/locale/lang/en' // lang i18n 19 | 20 | // 全局修改默认值 21 | ElementUI.Button.props.size.default = 'small' 22 | // console.log('ElementUI', ElementUI.Button.props); 23 | ElementUI.Dialog.props.destroyOnClose.default = true 24 | ElementUI.Dialog.props.customClass.default = 'dialogStyle' 25 | // console.log('ElementUI', ElementUI.Dialog); 26 | Vue.use(ElementUI) 27 | 28 | // vue 29 | 30 | Vue.use(VueCropper) 31 | 32 | // icon 配置导入 33 | import '@/icons' // icon 34 | -------------------------------------------------------------------------------- /src/icons/svg/comment.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type='file'] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | // to fixed https://github.com/ElemeFE/element/issues/2461 19 | .el-dialog { 20 | transform: none; 21 | left: 0; 22 | position: relative; 23 | margin: 0 auto; 24 | } 25 | 26 | // refine element ui upload 27 | .upload-container { 28 | .el-upload { 29 | width: 100%; 30 | 31 | .el-upload-dragger { 32 | width: 100%; 33 | height: 200px; 34 | } 35 | } 36 | } 37 | 38 | // dropdown 39 | .el-dropdown-menu { 40 | a { 41 | display: block; 42 | } 43 | } 44 | 45 | // to fix el-date-picker css style 46 | .el-range-separator { 47 | box-sizing: content-box; 48 | } 49 | 50 | .el-input { 51 | width: auto; 52 | } 53 | 54 | .el-row { 55 | margin-bottom: 10px; 56 | &:last-child { 57 | margin-bottom: 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/icons/svg/admin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/cat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-16 10:49:24 4 | * @LastEditTime: 2020-06-16 11:13:30 5 | * @LastEditors: Please set LastEditors 6 | * @Description:一二级类目管理接口 7 | */ 8 | 9 | import request from '@/utils/request'; 10 | 11 | // 获取类目管理 12 | export function getAllCat(data) { 13 | return request({ 14 | url: '/adminApi/cats', 15 | method: 'get', 16 | data, 17 | }); 18 | } 19 | // 创建类目管理 20 | export function createCat(data) { 21 | return request({ 22 | url: '/adminApi/cats', 23 | method: 'post', 24 | data, 25 | }); 26 | } 27 | // 编辑类目管理 28 | export function editCat(data) { 29 | return request({ 30 | url: `/adminApi/cats/${data.id}`, 31 | method: 'put', 32 | data, 33 | }); 34 | } 35 | // 删除类目管理 36 | export function deleteCat(data) { 37 | return request({ 38 | url: `/adminApi/cats/${data.id}`, 39 | method: 'delete', 40 | data, 41 | }); 42 | } 43 | 44 | // 禁用类目管理 45 | export function banCat(data) { 46 | return request({ 47 | url: `/adminApi/cats/${data.id}/banOrEnable`, 48 | method: 'put', 49 | data, 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /src/api/afterService.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-16 18:55:40 4 | * @LastEditTime: 2020-06-19 20:19:05 5 | * @LastEditors: tianhao 6 | * @Description:服务管理 serviceManage 7 | */ 8 | 9 | import request from '@/utils/request'; 10 | 11 | // 支持服务管理 12 | 13 | // 获取支持服务 14 | export function getService(data) { 15 | return request({ 16 | url: '/adminApi/afterService', 17 | method: 'get', 18 | data, 19 | }); 20 | } 21 | 22 | // 创建支持服务 23 | export function createService(data) { 24 | return request({ 25 | url: '/adminApi/afterService', 26 | method: 'post', 27 | data, 28 | }); 29 | } 30 | 31 | // 编辑支持服务 32 | export function editService(data) { 33 | return request({ 34 | url: `/adminApi/afterService/${data.id}`, 35 | method: 'put', 36 | data, 37 | }); 38 | } 39 | // 运费规则 40 | export function getFreight(data) { 41 | return request({ 42 | url: '/adminApi/fareConfig', 43 | method: 'get', 44 | data, 45 | }); 46 | } 47 | // 保存运费规则 48 | export function saveFreight(data) { 49 | return request({ 50 | url: '/adminApi/fareConfig', 51 | method: 'post', 52 | data, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/api/banner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-17 16:57:20 4 | * @LastEditors: hao.tian 5 | * @LastEditTime: 2020-06-23 14:39:16 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | export function getList(data) { 10 | return request({ 11 | url: '/adminApi/banner', 12 | method: 'get', 13 | data, 14 | }) 15 | } 16 | export function AddBanner(data) { 17 | return request({ 18 | url: '/adminApi/banner', 19 | method: 'post', 20 | data, 21 | }) 22 | } 23 | export function deleteBanner(data) { 24 | return request({ 25 | url: `/adminApi/banner/${data.id}`, 26 | method: 'delete', 27 | data, 28 | }) 29 | } 30 | // 启用禁用 31 | export function banPick(data) { 32 | return request({ 33 | url: `/adminApi/banner/${data.id}/ban`, 34 | method: 'put', 35 | data, 36 | }) 37 | } 38 | export function edit(data) { 39 | return request({ 40 | url: `/adminApi/banner/${data.id}`, 41 | method: 'put', 42 | data, 43 | }) 44 | } 45 | 46 | export function bannerDetail(data) { 47 | return request({ 48 | url: `/adminApi/banner/${data}`, 49 | method: 'get', 50 | data, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /src/api/refundReasons.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-07-02 11:35:06 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-02 14:51:11 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | 10 | // 分页获取退货原因列表 11 | export function getRefundReasonsList(data) { 12 | return request({ 13 | url: '/adminApi/refundReasons', 14 | method: 'get', 15 | data, 16 | }) 17 | } 18 | // 不分页获取退货原因列表 19 | export function getAllRefundReasonsList(data) { 20 | return request({ 21 | url: '/adminApi/refundReasons/all', 22 | method: 'get', 23 | data, 24 | }) 25 | } 26 | // 添加退货原因 27 | export function addRefundReasons(data) { 28 | return request({ 29 | url: '/adminApi/refundReasons', 30 | method: 'post', 31 | data, 32 | }) 33 | } 34 | // 修改退货原因 35 | export function editRefundReasons(data) { 36 | return request({ 37 | url: `/adminApi/refundReasons/${data.id}`, 38 | method: 'put', 39 | data, 40 | }) 41 | } 42 | // 删除退货原因 43 | export function deleteRefundReasons(data) { 44 | return request({ 45 | url: `/adminApi/refundReasons/${data.id}`, 46 | method: 'delete', 47 | data, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/icons/svg/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/directives/module/clipboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-07-31 15:00:39 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-07-31 15:00:51 6 | * @Descripttion: 复制到粘贴板 7 | */ 8 | 9 | import Clipboard from 'clipboard' 10 | export default { 11 | bind: (el, binding) => { 12 | const clipboard = new Clipboard(el, { 13 | text: () => binding.value.value, 14 | }) 15 | el.__success_callback__ = binding.value.success 16 | el.__error_callback__ = binding.value.error 17 | clipboard.on('success', e => { 18 | const callback = el.__success_callback__ 19 | callback && callback(e) 20 | }) 21 | clipboard.on('error', e => { 22 | const callback = el.__error_callback__ 23 | callback && callback(e) 24 | }) 25 | el.__clipboard__ = clipboard 26 | }, 27 | update: (el, binding) => { 28 | el.__clipboard__.text = () => binding.value.value 29 | el.__success_callback__ = binding.value.success 30 | el.__error_callback__ = binding.value.error 31 | }, 32 | unbind: (el, binding) => { 33 | delete el.__success_callback__ 34 | delete el.__error_callback__ 35 | el.__clipboard__.destroy() 36 | delete el.__clipboard__ 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | 28 | 29 | 46 | 47 | 55 | -------------------------------------------------------------------------------- /src/api/recommends.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-24 15:07:14 4 | * @LastEditTime: 2020-06-24 15:12:53 5 | * @LastEditors: Please set LastEditors 6 | * @Description: 商品推荐 7 | */ 8 | 9 | import request from '@/utils/request' 10 | 11 | // 获取分页查询商品推荐信息 12 | export function getGoodsRecommends(data) { 13 | return request({ 14 | url: '/adminApi/recommends', 15 | method: 'get', 16 | data, 17 | }) 18 | } 19 | 20 | // 保存商品推荐信息 21 | export function createGoodsRecommends(data) { 22 | return request({ 23 | url: '/adminApi/recommends', 24 | method: 'post', 25 | data, 26 | }) 27 | } 28 | 29 | // 删除推荐信息 30 | export function delGoodsRecommends(data) { 31 | return request({ 32 | url: `/adminApi/recommends/${data.id}`, 33 | method: 'delete', 34 | data, 35 | }) 36 | } 37 | 38 | // 启用/禁用热销商品 39 | export function banGoodsRecommends(data) { 40 | return request({ 41 | url: `/adminApi/recommends/${data.id}/banOrEnable`, 42 | method: 'put', 43 | data, 44 | }) 45 | } 46 | 47 | // 修改推荐信息顺序 48 | export function sortGoodsRecommends(data) { 49 | return request({ 50 | url: `/adminApi/recommends/${data.id}/sort`, 51 | method: 'put', 52 | data, 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-11 11:46:00 4 | * @LastEditTime: 2020-06-16 10:27:20 5 | * @LastEditors: Please set LastEditors 6 | * @Description:系统用户管理 7 | */ 8 | 9 | import request from '@/utils/request'; 10 | 11 | // 获取系统用户 12 | export function getAllOperator(data) { 13 | return request({ 14 | url: '/adminApi/admins', 15 | method: 'get', 16 | data, 17 | }); 18 | } 19 | 20 | // 创建系统用户 21 | export function createOperator(data) { 22 | return request({ 23 | url: '/adminApi/admins', 24 | method: 'post', 25 | data, 26 | }); 27 | } 28 | 29 | // 编辑系统用户 30 | export function editOperator(data) { 31 | return request({ 32 | url: `/adminApi/admins/${data.id}`, 33 | method: 'put', 34 | data, 35 | }); 36 | } 37 | 38 | // 删除系统用户 39 | export function delOperator(data) { 40 | return request({ 41 | url: `/adminApi/admins/${data.id}`, 42 | method: 'delete', 43 | data, 44 | }); 45 | } 46 | 47 | // 禁用/启用系统用户 48 | export function banOperator(data) { 49 | return request({ 50 | url: `/adminApi/admins/${data.id}/ban`, 51 | method: 'put', 52 | data, 53 | }); 54 | } 55 | 56 | // 修改密码 57 | export function changePassword(data) { 58 | return request({ 59 | url: `/adminApi/admins/${data.id}/passWord`, 60 | method: 'put', 61 | data, 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/icons/svg/activity.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 14 | 60 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /src/icons/svg/order.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/order.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-06-18 12:38:14 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-04 17:43:53 6 | * @Descripttion: 7 | */ 8 | 9 | import request from '@/utils/request' 10 | 11 | // 获取订单列表 12 | export function getOrderList(data) { 13 | return request({ 14 | url: '/adminApi/order', 15 | method: 'get', 16 | data, 17 | }) 18 | } 19 | // 订单详情 20 | export function orderDetail(data) { 21 | return request({ 22 | url: `/adminApi/order/${data.id}`, 23 | method: 'get', 24 | data, 25 | }) 26 | } 27 | // 关闭订单 28 | export function orderClose(data) { 29 | return request({ 30 | url: `/adminApi/order/${data.id}/cancel`, 31 | method: 'put', 32 | data, 33 | }) 34 | } 35 | // 发货 36 | export function sendExpress(data) { 37 | return request({ 38 | url: `/adminApi/order/${data.id}/sendExpress`, 39 | method: 'put', 40 | data, 41 | }) 42 | } 43 | // 申请退货 44 | export function refund(data) { 45 | return request({ 46 | url: `/adminApi/order/${data.id}/refund`, 47 | method: 'post', 48 | data, 49 | }) 50 | } 51 | // 导出订单单 52 | export function exportUnShippedOrder(data) { 53 | return request({ 54 | url: `/adminApi/order/export/unShipped`, 55 | method: 'get', 56 | data, 57 | }) 58 | } 59 | // 导入发货单 60 | export function importShipped(data) { 61 | return request({ 62 | url: `/adminApi/order/import/shipped`, 63 | method: 'post', 64 | data, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-06-15 12:32:38 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-03 11:05:20 6 | * @Descripttion: 7 | */ 8 | 9 | import request from '@/utils/request' 10 | 11 | // 登录 12 | export function login(data) { 13 | return request({ 14 | url: '/adminApi/login', 15 | method: 'get', 16 | data, 17 | }) 18 | } 19 | // 获取用户列表 20 | export function getUserList(data) { 21 | return request({ 22 | url: '/adminApi/users', 23 | method: 'get', 24 | data, 25 | }) 26 | } 27 | // 获取用户信息 28 | export function getUserInfo(data) { 29 | return request({ 30 | url: `/adminApi/users/${data.id}`, 31 | method: 'get', 32 | data, 33 | }) 34 | } 35 | // 用户状态管理 36 | export function banState(data) { 37 | return request({ 38 | url: `/adminApi/users/${data.userId}/banOrEnable`, 39 | method: 'put', 40 | data, 41 | }) 42 | } 43 | // 获取用户收货地址列表 44 | export function getUserAddress(data) { 45 | return request({ 46 | url: `/adminApi/users/${data.id}/addresses`, 47 | method: 'get', 48 | data, 49 | }) 50 | } 51 | // 获取用户自购记录 52 | export function getUserOrder(data) { 53 | return request({ 54 | url: `/adminApi/users/${data.id}/orders`, 55 | method: 'get', 56 | data, 57 | }) 58 | } 59 | // 获取用户优惠券信息 60 | export function getUserCoupons(data) { 61 | return request({ 62 | url: `/adminApi/users/${data.id}/coupons`, 63 | method: 'get', 64 | data, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/icons/svg/ordersvg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/comment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: tianhao 3 | * @Date: 2020-06-22 16:33:34 4 | * @LastEditors: 刘家辰 5 | * @LastEditTime: 2020-07-04 12:28:06 6 | * @Descripttion: 评论 7 | */ 8 | import request from '@/utils/request' 9 | export function getCommentList(data) { 10 | return request({ 11 | url: '/adminApi/comment', 12 | method: 'get', 13 | data, 14 | }) 15 | } 16 | export function getDetail(data) { 17 | return request({ 18 | url: `/adminApi/comment/${data.id}`, 19 | method: 'get', 20 | data, 21 | }) 22 | } 23 | export function agree(data) { 24 | return request({ 25 | url: `/adminApi/comment/${data.id}/agree`, 26 | method: 'put', 27 | data, 28 | }) 29 | } 30 | export function ban(data) { 31 | return request({ 32 | url: `/adminApi/comment/${data.id}/ban`, 33 | method: 'put', 34 | data, 35 | }) 36 | } 37 | export function noAgree(data) { 38 | return request({ 39 | url: `/adminApi/comment/${data.id}/notAgree`, 40 | method: 'put', 41 | data, 42 | }) 43 | } 44 | export function cancelBan(data) { 45 | return request({ 46 | url: `/adminApi/comment/${data.id}/cancelBan`, 47 | method: 'put', 48 | data, 49 | }) 50 | } 51 | // 添加假评论 52 | export function createComment(data) { 53 | return request({ 54 | url: `/adminApi/comment`, 55 | method: 'post', 56 | data, 57 | }) 58 | } 59 | // 删除加评论 60 | export function delComment(data) { 61 | return request({ 62 | url: `/adminApi/comment/${data.id}`, 63 | method: 'delete', 64 | data, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 20 | 21 | 49 | 50 | 61 | -------------------------------------------------------------------------------- /src/icons/svg/banner.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/brand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/catSpec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-06-16 14:48:16 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-06-17 12:13:20 6 | * @Descripttion: 7 | */ 8 | 9 | import request from '@/utils/request' 10 | 11 | // 获取类目管理下面的规格列表 12 | export function getCatSpec(data) { 13 | return request({ 14 | url: '/adminApi/catSpec', 15 | method: 'get', 16 | data, 17 | }) 18 | } 19 | // 添加类目管理下面的参数 20 | export function createCatSpec(data) { 21 | return request({ 22 | url: '/adminApi/catSpec', 23 | method: 'post', 24 | data, 25 | }) 26 | } 27 | // 删除类目管理下面的参数 28 | export function deleteCatSpec(data) { 29 | return request({ 30 | url: `/adminApi/catSpec/${data.id}`, 31 | method: 'delete', 32 | data, 33 | }) 34 | } 35 | // 编辑类目管理下面的参数 36 | export function editCatSpec(data) { 37 | return request({ 38 | url: `/adminApi/catSpec/${data.id}`, 39 | method: 'put', 40 | data, 41 | }) 42 | } 43 | // 添加类目管理下面的属性 44 | export function createCatSkus(data) { 45 | return request({ 46 | url: '/adminApi/catSkus', 47 | method: 'post', 48 | data, 49 | }) 50 | } 51 | // 编辑类目管理下面的属性 52 | export function editCatSkus(data) { 53 | return request({ 54 | url: `/adminApi/catSkus/${data.id}`, 55 | method: 'put', 56 | data, 57 | }) 58 | } 59 | // 删除类目管理下面的属性 60 | export function deleteCatSkus(data) { 61 | return request({ 62 | url: `/adminApi/catSkus/${data.id}`, 63 | method: 'delete', 64 | data, 65 | }) 66 | } 67 | // 获取类目管理下面的属性 68 | export function getCatSkus(data) { 69 | return request({ 70 | url: `/adminApi/catSkus`, 71 | method: 'get', 72 | data, 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /src/api/coupon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-07-01 11:33:48 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-06 11:11:19 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | 10 | // 新增优惠券 11 | export function addCoupon(data) { 12 | return request({ 13 | url: '/adminApi/coupon', 14 | method: 'post', 15 | data, 16 | }) 17 | } 18 | // 获取优惠券列表 19 | export function getCouponList(data) { 20 | return request({ 21 | url: '/adminApi/coupon', 22 | method: 'get', 23 | data, 24 | }) 25 | } 26 | // 获取优惠券详情 27 | export function getCouponInfo(data) { 28 | return request({ 29 | url: `/adminApi/coupon/${data.id}`, 30 | method: 'get', 31 | data, 32 | }) 33 | } 34 | // 编辑优惠券 35 | export function editCoupon(data) { 36 | return request({ 37 | url: `/adminApi/coupon/${data.id}`, 38 | method: 'put', 39 | data, 40 | }) 41 | } 42 | // 删除优惠券 43 | export function deleteCoupon(data) { 44 | return request({ 45 | url: `/adminApi/coupon/${data.id}`, 46 | method: 'delete', 47 | data, 48 | }) 49 | } 50 | // 获取优惠券统计信息 51 | export function getStatisticsInfo(data) { 52 | return request({ 53 | url: `/adminApi/coupon/${data.id}/statistics`, 54 | method: 'get', 55 | data, 56 | }) 57 | } 58 | // 获取持有详情 59 | export function getHoldDetailInfo(data) { 60 | return request({ 61 | url: `/adminApi/coupon/${data.id}/holdDetail`, 62 | method: 'get', 63 | data, 64 | }) 65 | } 66 | // 优惠券启用/禁用管理 67 | export function banState(data) { 68 | return request({ 69 | url: `/adminApi/coupon/${data.id}/banOrEnable`, 70 | method: 'put', 71 | data, 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /src/icons/svg/operation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/goods.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-17 12:07:33 4 | * @LastEditTime: 2020-06-20 18:27:51 5 | * @LastEditors: Please set LastEditors 6 | * @Description: 商品列表 7 | */ 8 | import request from '@/utils/request' 9 | 10 | // 获取商品 11 | export function getGoods(data) { 12 | return request({ 13 | url: '/adminApi/item', 14 | method: 'get', 15 | data, 16 | }) 17 | } 18 | // 创建商品 19 | export function createGoods(data) { 20 | return request({ 21 | url: '/adminApi/item', 22 | method: 'post', 23 | data, 24 | }) 25 | } 26 | // 编辑商品 27 | export function editGoods(data) { 28 | return request({ 29 | url: `/adminApi/item/${data.id}`, 30 | method: 'put', 31 | data, 32 | }) 33 | } 34 | // 删除商品 35 | export function deleteGoods(data) { 36 | return request({ 37 | url: `/adminApi/item/${data.id}`, 38 | method: 'delete', 39 | data, 40 | }) 41 | } 42 | 43 | // 上新商品 44 | export function putnewGoods(data) { 45 | return request({ 46 | url: `/adminApi/item/${data.id}/newFlagOrNot`, 47 | method: 'put', 48 | data, 49 | }) 50 | } 51 | // 上架商品 52 | export function putawayGoods(data) { 53 | return request({ 54 | url: `/adminApi/item/${data.id}/putOnOrDown`, 55 | method: 'put', 56 | data, 57 | }) 58 | } 59 | // 获取商品详情 60 | export function getGoodsDetail(data) { 61 | return request({ 62 | url: `/adminApi/item/${data.id}`, 63 | method: 'get', 64 | data, 65 | }) 66 | } 67 | // 根据商品ID查询sku列表 68 | export function getGoodsInfo(data) { 69 | return request({ 70 | url: `/adminApi/item/${data.id}/sku`, 71 | method: 'get', 72 | data, 73 | }) 74 | } 75 | // 商品是否可编辑 76 | export function isEdit(data) { 77 | return request({ 78 | url: `/adminApi/item/${data.id}/editFlag`, 79 | method: 'get', 80 | data, 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/activity.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: peng.wan 3 | * @Date: 2020-06-18 20:01:05 4 | * @LastEditors: peng.wan 5 | * @LastEditTime: 2020-07-27 11:56:00 6 | * @Descripttion: 7 | */ 8 | import request from '@/utils/request' 9 | 10 | // 获取活动列表 11 | export function getActivityList(data) { 12 | return request({ 13 | url: '/adminApi/limitPromotions', 14 | method: 'get', 15 | data, 16 | }) 17 | } 18 | // 仅获取活动信息 19 | export function getActivityInfo(data) { 20 | return request({ 21 | url: `/adminApi/limitPromotions/${data.id}`, 22 | method: 'get', 23 | data, 24 | }) 25 | } 26 | // 获取活动全部信息(包含商品及SKU) 27 | export function getAllActivityInfo(data) { 28 | return request({ 29 | url: `/adminApi/limitPromotions/${data.id}/all`, 30 | method: 'get', 31 | data, 32 | }) 33 | } 34 | // 添加活动 35 | export function addActivity(data) { 36 | return request({ 37 | url: `/adminApi/limitPromotions`, 38 | method: 'post', 39 | data, 40 | }) 41 | } 42 | // 修改活动信息 43 | export function editActivityInfo(data) { 44 | return request({ 45 | url: `/adminApi/limitPromotions/${data.id}`, 46 | method: 'put', 47 | data, 48 | }) 49 | } 50 | // 活动启用/禁用管理 51 | export function banState(data) { 52 | return request({ 53 | url: `/adminApi/limitPromotions/${data.id}/banOrEnable`, 54 | method: 'put', 55 | data, 56 | }) 57 | } 58 | // 活动销售数据统计 59 | export function getStatistics(data) { 60 | return request({ 61 | url: `/adminApi/limitPromotions/${data.id}/statistics`, 62 | method: 'get', 63 | data, 64 | }) 65 | } 66 | // 活动关联订单明细 67 | export function getOrders(data) { 68 | return request({ 69 | url: `/adminApi/limitPromotions/${data.id}/orders`, 70 | method: 'get', 71 | data, 72 | }) 73 | } 74 | // 活动商品明细 75 | export function getItems(data) { 76 | return request({ 77 | url: `/adminApi/limitPromotions/${data.id}/items`, 78 | method: 'get', 79 | data, 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /src/icons/svg/afterSales.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 30 | 31 | 67 | -------------------------------------------------------------------------------- /src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | /** 16 | * Because it's so fucking difficult to detect the scrolling element, just move them all 17 | * @param {number} amount 18 | */ 19 | function move(amount) { 20 | document.documentElement.scrollTop = amount 21 | document.body.parentNode.scrollTop = amount 22 | document.body.scrollTop = amount 23 | } 24 | 25 | function position() { 26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 27 | } 28 | 29 | /** 30 | * @param {number} to 31 | * @param {number} duration 32 | * @param {Function} callback 33 | */ 34 | export function scrollTo(to, duration, callback) { 35 | const start = position() 36 | const change = to - start 37 | const increment = 20 38 | let currentTime = 0 39 | duration = (typeof (duration) === 'undefined') ? 500 : duration 40 | var animateScroll = function() { 41 | // increment the time 42 | currentTime += increment 43 | // find the value with the quadratic in-out easing function 44 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 45 | // move the document.body 46 | move(val) 47 | // do the animation unless its over 48 | if (currentTime < duration) { 49 | requestAnimFrame(animateScroll) 50 | } else { 51 | if (callback && typeof (callback) === 'function') { 52 | // the animation is done so lets callback 53 | callback() 54 | } 55 | } 56 | } 57 | animateScroll() 58 | } 59 | -------------------------------------------------------------------------------- /src/icons/svg/cat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "4.3.0", 4 | "description": "A vue admin", 5 | "author": "Pan", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vue-cli-service serve", 9 | "prod": "vue-cli-service build", 10 | "stage": "vue-cli-service build --mode staging", 11 | "preview": "node build/index.js --preview", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test:unit": "jest --clearCache && vue-cli-service test:unit", 14 | "test:ci": "npm run lint && npm run test:unit", 15 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" 16 | }, 17 | "dependencies": { 18 | "axios": "0.18.1", 19 | "element-ui": "2.13.0", 20 | "js-cookie": "2.2.0", 21 | "moment": "^2.26.0", 22 | "normalize.css": "7.0.0", 23 | "nprogress": "0.2.0", 24 | "path-to-regexp": "2.4.0", 25 | "vue": "2.6.10", 26 | "vue-cropper": "^0.5.4", 27 | "vue-router": "3.0.6", 28 | "vuedraggable": "^2.24.1", 29 | "vuex": "3.1.0", 30 | "vuex-persistedstate": "^3.0.1" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "7.0.0", 34 | "@babel/register": "7.0.0", 35 | "@vue/cli-plugin-babel": "3.6.0", 36 | "@vue/cli-plugin-eslint": "^3.9.1", 37 | "@vue/cli-plugin-unit-jest": "3.6.3", 38 | "@vue/cli-service": "3.6.0", 39 | "@vue/test-utils": "1.0.0-beta.29", 40 | "autoprefixer": "^9.5.1", 41 | "babel-core": "7.0.0-bridge.0", 42 | "babel-eslint": "10.0.1", 43 | "babel-jest": "23.6.0", 44 | "chalk": "2.4.2", 45 | "connect": "3.6.6", 46 | "eslint": "5.15.3", 47 | "eslint-plugin-vue": "5.2.2", 48 | "html-webpack-plugin": "3.2.0", 49 | "mockjs": "1.0.1-beta3", 50 | "runjs": "^4.3.2", 51 | "sass": "^1.26.8", 52 | "sass-loader": "^7.1.0", 53 | "script-ext-html-webpack-plugin": "2.1.3", 54 | "serve-static": "^1.13.2", 55 | "svg-sprite-loader": "4.1.3", 56 | "svgo": "1.2.2", 57 | "vue-template-compiler": "2.6.10" 58 | }, 59 | "engines": { 60 | "node": ">=8.9", 61 | "npm": ">= 3.0.0" 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 8 | 22 | 23 | 40 | 41 | 90 | -------------------------------------------------------------------------------- /src/icons/svg/recommends.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, 13 | sans-serif; 14 | } 15 | 16 | label { 17 | // font-weight: 700; 18 | } 19 | 20 | html { 21 | height: 100%; 22 | box-sizing: border-box; 23 | } 24 | 25 | #app { 26 | height: 100%; 27 | } 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | a:focus, 36 | a:active { 37 | outline: none; 38 | } 39 | 40 | a, 41 | a:focus, 42 | a:hover { 43 | cursor: pointer; 44 | color: inherit; 45 | text-decoration: none; 46 | } 47 | 48 | div:focus { 49 | outline: none; 50 | } 51 | 52 | .clearfix { 53 | &:after { 54 | visibility: hidden; 55 | display: block; 56 | font-size: 0; 57 | content: ' '; 58 | clear: both; 59 | height: 0; 60 | } 61 | } 62 | 63 | // main-container global css 64 | .app-container { 65 | padding: 20px; 66 | } 67 | .el-submenu__title i { 68 | font-weight: bold !important; 69 | font-size: 14px !important; 70 | } 71 | .el-form-item { 72 | // width: 400px; 73 | } 74 | .el-input__inner { 75 | // width: auto; 76 | } 77 | .el-textarea__inner { 78 | width: auto; 79 | } 80 | .el-textarea .el-textarea__inner { 81 | width: 100%; 82 | } 83 | .el-button--primary { 84 | background-color: #1890ff; 85 | border-color: #1890ff; 86 | } 87 | .dialogStyle { 88 | min-width: 700px; 89 | } 90 | .activityDetail .el-card__body { 91 | height: 200px; 92 | box-sizing: content-box; 93 | } 94 | .flex-row .el-form-item__label, 95 | .userLimitAmount .el-form-item__label { 96 | text-align: left; 97 | } 98 | .refundSause_dynamic .el-form-item__error { 99 | right: 0; 100 | } 101 | .newUploadForm .el-form-item__content { 102 | display: flex; 103 | align-items: center; 104 | } 105 | .el-checkbox__input.is-checked + .el-checkbox__label { 106 | color: unset; 107 | } 108 | .el-tooltip__popper { 109 | max-width: 500px; 110 | } 111 | -------------------------------------------------------------------------------- /src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/coupon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 20 | 21 | 59 | 60 | 101 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-20 18:55:32 4 | * @LastEditors: xin.chen 5 | * @LastEditTime: 2020-09-21 16:19:10 6 | * @Descripttion: 7 | */ 8 | 9 | import Cookies from 'js-cookie'; 10 | import { login, logout, getInfo } from '@/api/user'; 11 | import { getToken } from '@/api/upload'; 12 | 13 | const state = { 14 | sidebar: { 15 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 16 | withoutAnimation: false, 17 | }, 18 | 19 | device: 'desktop', 20 | upLoadToken: null, 21 | 22 | // 自定义表单类型 23 | field: [ 24 | { key: '', name: '单行文本', type: 'text', placeholder: '', disabled: false }, 25 | { key: '', name: '选择器', type: 'select', placeholder: '' }, 26 | { key: '', name: '级联选择器', type: 'cascader', placeholder: '', disabled: false }, 27 | { 28 | key: '', 29 | name: '日期时间选择器', 30 | type: 'datetimepicker', 31 | placeholder: '', 32 | disabled: false, 33 | }, 34 | { 35 | key: '', 36 | name: '创建条目选择器', 37 | type: 'allowcreate', 38 | placeholder: '', 39 | disabled: false, 40 | }, 41 | ], 42 | }; 43 | 44 | const mutations = { 45 | TOGGLE_SIDEBAR: state => { 46 | state.sidebar.opened = !state.sidebar.opened; 47 | state.sidebar.withoutAnimation = false; 48 | if (state.sidebar.opened) { 49 | Cookies.set('sidebarStatus', 1); 50 | } else { 51 | Cookies.set('sidebarStatus', 0); 52 | } 53 | }, 54 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 55 | Cookies.set('sidebarStatus', 0); 56 | state.sidebar.opened = false; 57 | state.sidebar.withoutAnimation = withoutAnimation; 58 | }, 59 | TOGGLE_DEVICE: (state, device) => { 60 | state.device = device; 61 | }, 62 | SET_UPLOAD_TOKEN: (state, token) => { 63 | console.log('set token', token); 64 | state.upLoadToken = token; 65 | }, 66 | // 编辑自定义表单类型 67 | SET_UPLOAD_FIELD: (state, field) => { 68 | state.field = field; 69 | }, 70 | }; 71 | 72 | const actions = { 73 | toggleSideBar({ commit }) { 74 | commit('TOGGLE_SIDEBAR'); 75 | }, 76 | closeSideBar({ commit }, { withoutAnimation }) { 77 | commit('CLOSE_SIDEBAR', withoutAnimation); 78 | }, 79 | toggleDevice({ commit }, device) { 80 | commit('TOGGLE_DEVICE', device); 81 | }, 82 | setUpLoadToken({ commit }, token) { 83 | commit('SET_UPLOAD_TOKEN', token); 84 | }, 85 | // 编辑自定义表单类型 86 | setuploadfield({ commit }, field) { 87 | commit('SET_UPLOAD_FIELD', field); 88 | }, 89 | }; 90 | 91 | export default { 92 | namespaced: true, 93 | state, 94 | mutations, 95 | actions, 96 | }; 97 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/user' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | import { resetRouter } from '@/router' 4 | 5 | const getDefaultState = () => { 6 | return { 7 | token: '', 8 | name: '', 9 | avatar: '', 10 | } 11 | } 12 | 13 | const state = getDefaultState() 14 | 15 | const mutations = { 16 | RESET_STATE: state => { 17 | Object.assign(state, getDefaultState()) 18 | }, 19 | SET_TOKEN: (state, token) => { 20 | state.token = token 21 | }, 22 | SET_NAME: (state, name) => { 23 | state.name = name 24 | }, 25 | SET_AVATAR: (state, avatar) => { 26 | state.avatar = avatar 27 | }, 28 | } 29 | 30 | const actions = { 31 | // user login 32 | login({ commit }, userInfo) { 33 | const { phone, passWord } = userInfo 34 | return new Promise((resolve, reject) => { 35 | login({ phone, passWord }) 36 | .then(response => { 37 | const { data } = response 38 | commit('SET_TOKEN', data.token) 39 | commit('SET_NAME', data.admin.name) 40 | commit('SET_AVATAR', data.admin.avatar) 41 | setToken(data.token) 42 | sessionStorage.setItem('token', data.token) 43 | resolve() 44 | }) 45 | .catch(error => { 46 | reject(error) 47 | }) 48 | }) 49 | }, 50 | 51 | // get user info 52 | getInfo({ commit, state }) { 53 | return new Promise((resolve, reject) => { 54 | getInfo(state.token) 55 | .then(response => { 56 | const { data } = response 57 | 58 | if (!data) { 59 | reject('Verification failed, please Login again.') 60 | } 61 | 62 | const { name, avatar } = data.admin 63 | 64 | commit('SET_NAME', name) 65 | commit('SET_AVATAR', avatar) 66 | resolve(data) 67 | }) 68 | .catch(error => { 69 | reject(error) 70 | }) 71 | }) 72 | }, 73 | 74 | // user logout 75 | logout({ commit, state }) { 76 | return new Promise((resolve, reject) => { 77 | // logout(state.token) 78 | // .then(() => { 79 | removeToken() // must remove token first 80 | resetRouter() 81 | commit('RESET_STATE') 82 | resolve() 83 | // }) 84 | // .catch(error => { 85 | // reject(error) 86 | // }) 87 | }) 88 | }, 89 | 90 | // remove token 91 | resetToken({ commit }) { 92 | return new Promise(resolve => { 93 | removeToken() // must remove token first 94 | commit('RESET_STATE') 95 | resolve() 96 | }) 97 | }, 98 | } 99 | 100 | export default { 101 | namespaced: true, 102 | state, 103 | mutations, 104 | actions, 105 | } 106 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | 22 | 81 | 82 | 95 | -------------------------------------------------------------------------------- /src/icons/svg/supperlier.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/password/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 39 | 40 | 116 | 117 | 122 | -------------------------------------------------------------------------------- /src/utils/guid.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wei.chen 3 | * @Date: 2020-06-15 15:53:48 4 | * @LastEditors: wei.chen 5 | * @LastEditTime: 2020-06-15 16:08:27 6 | * @Descripttion: 全局生成一个唯一的id 7 | */ 8 | function GUID() { 9 | this.date = new Date(); 10 | 11 | /* 判断是否初始化过,如果初始化过以下代码,则以下代码将不再执行,实际中只执行一次 */ 12 | if (typeof this.newGUID != 'function') { 13 | /* 生成GUID码 */ 14 | GUID.prototype.newGUID = function() { 15 | this.date = new Date(); 16 | var guidStr = ''; 17 | let sexadecimalDate = this.hexadecimal(this.getGUIDDate(), 16); 18 | let sexadecimalTime = this.hexadecimal(this.getGUIDTime(), 16); 19 | for (var i = 0; i < 9; i++) { 20 | guidStr += Math.floor(Math.random() * 16).toString(16); 21 | } 22 | guidStr += sexadecimalDate; 23 | guidStr += sexadecimalTime; 24 | while (guidStr.length < 32) { 25 | guidStr += Math.floor(Math.random() * 16).toString(16); 26 | } 27 | return `qn${guidStr}`; 28 | }; 29 | /* 30 | * 功能:获取当前日期的GUID格式,即8位数的日期:19700101 31 | * 返回值:返回GUID日期格式的字条串 32 | */ 33 | GUID.prototype.getGUIDDate = function() { 34 | return ( 35 | this.date.getFullYear() + 36 | this.addZero(this.date.getMonth() + 1) + 37 | this.addZero(this.date.getDay()) 38 | ); 39 | }; 40 | 41 | /* 42 | * 功能:获取当前时间的GUID格式,即8位数的时间,包括毫秒,毫秒为2位数:12300933 43 | * 返回值:返回GUID日期格式的字条串 44 | */ 45 | GUID.prototype.getGUIDTime = function() { 46 | return ( 47 | this.addZero(this.date.getHours()) + 48 | this.addZero(this.date.getMinutes()) + 49 | this.addZero(this.date.getSeconds()) + 50 | this.addZero(parseInt(this.date.getMilliseconds() / 10)) 51 | ); 52 | }; 53 | 54 | /* 55 | * 功能: 为一位数的正整数前面添加0,如果是可以转成非NaN数字的字符串也可以实现 56 | * 参数: 参数表示准备再前面添加0的数字或可以转换成数字的字符串 57 | * 返回值: 如果符合条件,返回添加0后的字条串类型,否则返回自身的字符串 58 | */ 59 | GUID.prototype.addZero = function(num) { 60 | if (Number(num).toString() != 'NaN' && num >= 0 && num < 10) { 61 | return '0' + Math.floor(num); 62 | } else { 63 | return num.toString(); 64 | } 65 | }; 66 | 67 | /* 68 | * 功能:将y进制的数值,转换为x进制的数值 69 | * 参数:第1个参数表示欲转换的数值;第2个参数表示欲转换的进制;第3个参数可选,表示当前的进制数,如不写则为10 70 | * 返回值:返回转换后的字符串 71 | */ 72 | GUID.prototype.hexadecimal = function(num, x, y) { 73 | if (y != undefined) { 74 | return parseInt(num.toString(), y).toString(x); 75 | } else { 76 | return parseInt(num.toString()).toString(x); 77 | } 78 | }; 79 | /* 80 | * 功能:格式化32位的字符串为GUID模式的字符串 81 | * 参数:第1个参数表示32位的字符串 82 | * 返回值:标准GUID格式的字符串 83 | */ 84 | GUID.prototype.formatGUID = function(guidStr) { 85 | var str1 = guidStr.slice(0, 8) + '-', 86 | str2 = guidStr.slice(8, 12) + '-', 87 | str3 = guidStr.slice(12, 16) + '-', 88 | str4 = guidStr.slice(16, 20) + '-', 89 | str5 = guidStr.slice(20); 90 | return str1 + str2 + str3 + str4 + str5; 91 | }; 92 | } 93 | } 94 | 95 | export { GUID }; 96 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 刘家辰 3 | * @Date: 2020-06-11 11:46:00 4 | * @LastEditTime: 2020-07-31 14:41:06 5 | * @LastEditors: wei.chen 6 | * @Description: 7 | */ 8 | 9 | import axios from 'axios' 10 | import { MessageBox, Message } from 'element-ui' 11 | import store from '@/store' 12 | import { getToken } from '@/utils/auth' 13 | 14 | // create an axios instance 15 | const service = axios.create({ 16 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 17 | // withCredentials: true, // send cookies when cross-domain requests 18 | timeout: 5000, // request timeout 19 | }) 20 | 21 | // request interceptor 22 | service.interceptors.request.use( 23 | config => { 24 | // do something before request is sent 25 | 26 | if (store.getters.token) { 27 | // let each request carry token 28 | // ['X-Token'] is a custom headers key 29 | // please modify it according to the actual situation 30 | config.headers['Authorization'] = getToken() 31 | } 32 | return config 33 | }, 34 | error => { 35 | // do something with request error 36 | console.log(error) // for debug 37 | return Promise.reject(error) 38 | } 39 | ) 40 | 41 | // response interceptor 42 | service.interceptors.response.use( 43 | response => { 44 | const res = response.data 45 | // debugger 46 | // if the custom code is not 20000, it is judged as an error. 47 | if (res.code !== 200) { 48 | switch (res.code) { 49 | case 10005: 50 | res.msg = '值不可以重复!' 51 | break 52 | } 53 | Message({ 54 | message: res.msg || 'Error', 55 | type: 'error', 56 | duration: 5 * 1000, 57 | }) 58 | if (res.code === 402) { 59 | // token 过期了 60 | // 1、重置用户的状态 61 | // 2、用户跳转到 登录界面 62 | } 63 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; 64 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 65 | // to re-login 66 | MessageBox.confirm('登录状态失效,请重新登录', '确认退出', { 67 | confirmButtonText: '重新登录', 68 | cancelButtonText: 'Cancel', 69 | type: 'warning', 70 | }).then(() => { 71 | store.dispatch('user/resetToken').then(() => { 72 | location.reload() 73 | }) 74 | }) 75 | } 76 | return Promise.reject(new Error(res.msg || 'Error')) 77 | } else { 78 | return res 79 | } 80 | }, 81 | error => { 82 | Message({ 83 | message: '服务开了小差,请稍后重试', 84 | type: 'error', 85 | duration: 3 * 1000, 86 | }) 87 | return Promise.reject(error) 88 | } 89 | ) 90 | // get请求封装 91 | const request = val => { 92 | let { data, url, method } = val 93 | if (method == 'get' || method == 'GET') { 94 | return service({ url, method, params: data }) 95 | } else { 96 | return service({ url, method, data }) 97 | } 98 | } 99 | 100 | export default request 101 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const defaultSettings = require('./src/settings.js') 4 | 5 | function resolve(dir) { 6 | return path.join(__dirname, dir) 7 | } 8 | 9 | const name = defaultSettings.title || 'Admin' 10 | 11 | // dev port 12 | const port = process.env.port || process.env.npm_config_port || 9000 13 | 14 | module.exports = { 15 | publicPath: '/', 16 | outputDir: 'dist', 17 | assetsDir: 'static', 18 | lintOnSave: false, 19 | productionSourceMap: false, 20 | devServer: { 21 | port: port, 22 | open: true, 23 | overlay: { 24 | warnings: false, 25 | errors: true, 26 | }, 27 | // before: require('./mock/mock-server.js'), 28 | }, 29 | configureWebpack: { 30 | name: name, 31 | resolve: { 32 | alias: { 33 | '@': resolve('src'), 34 | }, 35 | }, 36 | }, 37 | chainWebpack(config) { 38 | config.plugins.delete('preload') 39 | config.plugins.delete('prefetch') // TODO: need test 40 | 41 | // set svg-sprite-loader 42 | config.module 43 | .rule('svg') 44 | .exclude.add(resolve('src/icons')) 45 | .end() 46 | config.module 47 | .rule('icons') 48 | .test(/\.svg$/) 49 | .include.add(resolve('src/icons')) 50 | .end() 51 | .use('svg-sprite-loader') 52 | .loader('svg-sprite-loader') 53 | .options({ 54 | symbolId: 'icon-[name]', 55 | }) 56 | .end() 57 | 58 | // set preserveWhitespace 59 | config.module 60 | .rule('vue') 61 | .use('vue-loader') 62 | .loader('vue-loader') 63 | .tap(options => { 64 | options.compilerOptions.preserveWhitespace = true 65 | return options 66 | }) 67 | .end() 68 | 69 | config.when(process.env.NODE_ENV !== 'development', config => { 70 | config 71 | .plugin('ScriptExtHtmlWebpackPlugin') 72 | .after('html') 73 | .use('script-ext-html-webpack-plugin', [ 74 | { 75 | // `runtime` must same as runtimeChunk name. default is `runtime` 76 | inline: /runtime\..*\.js$/, 77 | }, 78 | ]) 79 | .end() 80 | config.optimization.splitChunks({ 81 | chunks: 'all', 82 | cacheGroups: { 83 | libs: { 84 | name: 'chunk-libs', 85 | test: /[\\/]node_modules[\\/]/, 86 | priority: 10, 87 | chunks: 'initial', // only package third parties that are initially dependent 88 | }, 89 | elementUI: { 90 | name: 'chunk-elementUI', // split elementUI into a single package 91 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app 92 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm 93 | }, 94 | commons: { 95 | name: 'chunk-commons', 96 | test: resolve('src/components'), // can customize your rules 97 | minChunks: 3, // minimum common number 98 | priority: 5, 99 | reuseExistingChunk: true, 100 | }, 101 | }, 102 | }) 103 | config.optimization.runtimeChunk('single') 104 | }) 105 | }, 106 | } 107 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 96 | -------------------------------------------------------------------------------- /src/views/worksheet/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 45 | 46 | 95 | 96 | 140 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import { getToken } from '@/utils/auth'; 4 | import { getPageTitle } from '@/utils/getPageTitle'; 5 | 6 | Vue.use(Router); 7 | 8 | /* Layout */ 9 | import Layout from '@/layout'; 10 | 11 | /** 12 | * Note: sub-menu only appear when route children.length >= 1 13 | * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 14 | * 15 | * hidden: true 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 16 | * alwaysShow: true 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 17 | * 18 | // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 19 | // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 20 | // 若你想不管路由下面的 children 声明的个数都显示你的根路由 21 | // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 22 | * redirect: noRedirect 23 | * name:'router-name' 设定路由的名字,一定要填写不然使用时会出现各种问题 24 | * meta : { 25 | title: 'title' 设置该路由在侧边栏和面包屑中展示的名字 26 | icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon 27 | noCache 如果设置为true,则不会被 缓存(默认 false) 28 | breadcrumb: false 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) 29 | // 当路由设置了该属性,则会高亮相对应的侧边栏。 30 | // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list 31 | // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置 32 | activeMenu: '/example/list' 33 | } 34 | */ 35 | 36 | /** 37 | * constantRoutes 38 | * a base page that does not have permission requirements 39 | * all roles can be accessed 40 | */ 41 | export const constantRoutes = [ 42 | { 43 | path: '/login', 44 | component: () => import('@/views/login/index'), 45 | hidden: true, 46 | }, 47 | 48 | { 49 | path: '/404', 50 | component: () => import('@/views/404'), 51 | hidden: true, 52 | }, 53 | { 54 | path: '/', 55 | component: Layout, 56 | redirect: '/dashboard', 57 | children: [ 58 | { 59 | path: 'dashboard', 60 | name: '首页', 61 | component: () => import('@/views/dashboard/index'), 62 | meta: { title: '首页', icon: 'dashboard' }, 63 | }, 64 | ], 65 | }, 66 | { 67 | path: '/worksheet', 68 | component: Layout, 69 | redirect: '/worksheet', 70 | children: [ 71 | { 72 | path: 'worksheet', 73 | name: '自定义表单', 74 | component: () => import('@/views/worksheet/index'), 75 | meta: { title: '自定义表单', icon: 'dashboard' }, 76 | }, 77 | ], 78 | }, 79 | 80 | // 404 page must be placed at the end !!! 81 | { path: '*', redirect: '/404', hidden: true }, 82 | ]; 83 | const createRouter = () => 84 | new Router({ 85 | // mode: 'history', // require service support 86 | scrollBehavior: () => ({ y: 0 }), 87 | routes: constantRoutes, 88 | }); 89 | 90 | const router = createRouter(); 91 | 92 | // router.beforeEach(async (to, from, next) => { 93 | // // start progress bar 94 | // // NProgress.start() 95 | 96 | // // set page title 97 | // document.title = getPageTitle(to.meta.title) 98 | 99 | // // determine whether the user has logged in 100 | // const hasToken = getToken() 101 | // // 判断是否有token 102 | // if (hasToken) { 103 | // // 如果有token,直接进入登录界面强制调整到首页 104 | // if (to.path === '/login') { 105 | // // if is logged in, redirect to the home page 106 | // next({ path: '/' }) 107 | // } 108 | // } else { 109 | // /* 没有 token 的值 */ 110 | // next(`/login?redirect=${to.path}`) 111 | // } 112 | // }) 113 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 114 | export function resetRouter() { 115 | const newRouter = createRouter(); 116 | router.matcher = newRouter.matcher; // reset router 117 | } 118 | 119 | export default router; 120 | -------------------------------------------------------------------------------- /src/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 56 | 57 | 145 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | .main-container { 3 | min-height: 100%; 4 | transition: margin-left 0.28s; 5 | margin-left: $sideBarWidth; 6 | position: relative; 7 | background-color: #f0f2f5; 8 | } 9 | 10 | .sidebar-container { 11 | transition: width 0.28s; 12 | width: $sideBarWidth !important; 13 | background-color: $menuHover; 14 | height: 100%; 15 | position: fixed; 16 | font-size: 0px; 17 | top: 0; 18 | bottom: 0; 19 | left: 0; 20 | z-index: 1001; 21 | overflow: hidden; 22 | 23 | // reset element-ui css 24 | .horizontal-collapse-transition { 25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 26 | 0s padding-right ease-in-out; 27 | } 28 | 29 | .scrollbar-wrapper { 30 | overflow-x: hidden !important; 31 | } 32 | 33 | .el-scrollbar__bar.is-vertical { 34 | right: 0px; 35 | } 36 | 37 | .el-scrollbar { 38 | height: 100%; 39 | } 40 | 41 | &.has-logo { 42 | .el-scrollbar { 43 | height: calc(100% - 50px); 44 | } 45 | } 46 | 47 | .is-horizontal { 48 | display: none; 49 | } 50 | 51 | a { 52 | display: inline-block; 53 | width: 100%; 54 | overflow: hidden; 55 | } 56 | 57 | .svg-icon { 58 | margin-right: 16px; 59 | } 60 | 61 | .el-menu { 62 | border: none; 63 | height: 100%; 64 | width: 100% !important; 65 | } 66 | 67 | // menu hover 68 | .submenu-title-noDropdown, 69 | .el-submenu__title { 70 | color: $navTextColor !important; 71 | &:hover { 72 | background-color: $menuHover !important; 73 | } 74 | } 75 | 76 | .is-active > .el-submenu__title { 77 | color: $navTextColor !important; 78 | } 79 | 80 | & .nest-menu .el-submenu > .el-submenu__title, 81 | & .el-submenu .el-menu-item { 82 | min-width: $sideBarWidth !important; 83 | background-color: $subMenuBg !important; 84 | &:hover { 85 | background-color: $subMenuHover !important; 86 | } 87 | } 88 | } 89 | 90 | .hideSidebar { 91 | .sidebar-container { 92 | width: 54px !important; 93 | } 94 | 95 | .main-container { 96 | margin-left: 54px; 97 | } 98 | 99 | .submenu-title-noDropdown { 100 | padding: 0 !important; 101 | position: relative; 102 | 103 | .el-tooltip { 104 | padding: 0 !important; 105 | 106 | .svg-icon { 107 | margin-left: 20px; 108 | } 109 | } 110 | } 111 | 112 | .el-submenu { 113 | overflow: hidden; 114 | 115 | & > .el-submenu__title { 116 | padding: 0 !important; 117 | 118 | .svg-icon { 119 | margin-left: 20px; 120 | } 121 | 122 | .el-submenu__icon-arrow { 123 | display: none; 124 | } 125 | } 126 | } 127 | 128 | .el-menu--collapse { 129 | .el-submenu { 130 | & > .el-submenu__title { 131 | & > span { 132 | height: 0; 133 | width: 0; 134 | overflow: hidden; 135 | visibility: hidden; 136 | display: inline-block; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | .el-menu--collapse .el-menu .el-submenu { 144 | min-width: $sideBarWidth !important; 145 | } 146 | 147 | // mobile responsive 148 | .mobile { 149 | .main-container { 150 | margin-left: 0px; 151 | } 152 | 153 | .sidebar-container { 154 | transition: transform 0.28s; 155 | width: $sideBarWidth !important; 156 | } 157 | 158 | &.hideSidebar { 159 | .sidebar-container { 160 | pointer-events: none; 161 | transition-duration: 0.3s; 162 | transform: translate3d(-$sideBarWidth, 0, 0); 163 | } 164 | } 165 | } 166 | 167 | .withoutAnimation { 168 | .main-container, 169 | .sidebar-container { 170 | transition: none; 171 | } 172 | } 173 | } 174 | 175 | // when menu collapsed 176 | .el-menu--vertical { 177 | & > .el-menu { 178 | .svg-icon { 179 | margin-right: 16px; 180 | } 181 | } 182 | 183 | .nest-menu .el-submenu > .el-submenu__title, 184 | .el-menu-item { 185 | &:hover { 186 | // you can use $subMenuHover 187 | background-color: $menuHover !important; 188 | } 189 | } 190 | 191 | // the scroll bar appears when the subMenu is too long 192 | > .el-menu--popup { 193 | max-height: 100vh; 194 | overflow-y: auto; 195 | 196 | &::-webkit-scrollbar-track-piece { 197 | background: #d3dce6; 198 | } 199 | 200 | &::-webkit-scrollbar { 201 | width: 6px; 202 | } 203 | 204 | &::-webkit-scrollbar-thumb { 205 | background: #99a9bf; 206 | border-radius: 20px; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | import chalk from 'chalk'; 6 | 7 | /** 8 | * Parse the time to string 9 | * @param {(Object|string|number)} time 10 | * @param {string} cFormat 11 | * @returns {string | null} 12 | */ 13 | export function parseTime(time, cFormat) { 14 | if (arguments.length === 0 || !time) { 15 | return null; 16 | } 17 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'; 18 | let date; 19 | if (typeof time === 'object') { 20 | date = time; 21 | } else { 22 | if (typeof time === 'string') { 23 | if (/^[0-9]+$/.test(time)) { 24 | // support "1548221490638" 25 | time = parseInt(time); 26 | } else { 27 | // support safari 28 | // https://stackoverflow.com/questions/4310953/invalid-date-in-safari 29 | time = time.replace(new RegExp(/-/gm), '/'); 30 | } 31 | } 32 | 33 | if (typeof time === 'number' && time.toString().length === 10) { 34 | time = time * 1000; 35 | } 36 | date = new Date(time); 37 | } 38 | const formatObj = { 39 | y: date.getFullYear(), 40 | m: date.getMonth() + 1, 41 | d: date.getDate(), 42 | h: date.getHours(), 43 | i: date.getMinutes(), 44 | s: date.getSeconds(), 45 | a: date.getDay(), 46 | }; 47 | const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { 48 | const value = formatObj[key]; 49 | // Note: getDay() returns 0 on Sunday 50 | if (key === 'a') { 51 | return ['日', '一', '二', '三', '四', '五', '六'][value]; 52 | } 53 | return value.toString().padStart(2, '0'); 54 | }); 55 | return time_str; 56 | } 57 | 58 | /** 59 | * @param {number} time 60 | * @param {string} option 61 | * @returns {string} 62 | */ 63 | export function formatTime(time, option) { 64 | if (('' + time).length === 10) { 65 | time = parseInt(time) * 1000; 66 | } else { 67 | time = +time; 68 | } 69 | const d = new Date(time); 70 | const now = Date.now(); 71 | 72 | const diff = (now - d) / 1000; 73 | 74 | if (diff < 30) { 75 | return '刚刚'; 76 | } else if (diff < 3600) { 77 | // less 1 hour 78 | return Math.ceil(diff / 60) + '分钟前'; 79 | } else if (diff < 3600 * 24) { 80 | return Math.ceil(diff / 3600) + '小时前'; 81 | } else if (diff < 3600 * 24 * 2) { 82 | return '1天前'; 83 | } 84 | if (option) { 85 | return parseTime(time, option); 86 | } else { 87 | return ( 88 | d.getMonth() + 89 | 1 + 90 | '月' + 91 | d.getDate() + 92 | '日' + 93 | d.getHours() + 94 | '时' + 95 | d.getMinutes() + 96 | '分' 97 | ); 98 | } 99 | } 100 | 101 | /** 102 | * @param {string} url 103 | * @returns {Object} 104 | */ 105 | export function param2Obj(url) { 106 | const search = url.split('?')[1]; 107 | if (!search) { 108 | return {}; 109 | } 110 | return JSON.parse( 111 | '{"' + 112 | decodeURIComponent(search) 113 | .replace(/"/g, '\\"') 114 | .replace(/&/g, '","') 115 | .replace(/=/g, '":"') 116 | .replace(/\+/g, ' ') + 117 | '"}' 118 | ); 119 | } 120 | 121 | /** 122 | * @name: 123 | * @description: sku解析 [{properties:"材质:羊毛;尺寸:M;颜色:黑色;"},{properties:"材质:羊毛;尺寸:M;颜色:黑色;"}] 124 | * @param {Array} val 包含properties值数组 125 | * @return: names:属性名 sku 属性值 126 | */ 127 | export function getSku(val) { 128 | if (!val) { 129 | return false; 130 | } 131 | let properties = []; 132 | let names = []; 133 | let sku = []; 134 | let arr2 = []; 135 | let obj = {}; 136 | let handle = []; 137 | for (let i = 0; i < val.length; i++) { 138 | properties.push(val[i].properties); 139 | } 140 | for (let i = 0; i < properties.length; i++) { 141 | let item = properties[i].split(';'); 142 | 143 | let arr1 = []; 144 | for (let j = 0; j < item.length; j++) { 145 | let name = item[j].split(':'); 146 | handle.push(name[0], name[1]); 147 | arr1.push(name[0]); 148 | arr2.push(name[1]); 149 | obj[name[0]] = []; 150 | Object.keys(obj).forEach(item => { 151 | if (item == name[0]) { 152 | // console.log(name[1], obj[item]) 153 | obj[item].push(name[1]); 154 | } 155 | }); 156 | } 157 | // 由于拼接时候最后一个会有空,需要删除 158 | arr1.splice(arr1.length - 1, 1); 159 | arr2.splice(arr2.length - 1, 1); 160 | names = arr1; 161 | sku = Array.from(new Set(arr2)); 162 | } 163 | // 模拟用户操作 164 | handle = handle.filter(item => { 165 | return item; 166 | }); 167 | return { names, sku, handle }; 168 | } 169 | 170 | /** 171 | * @name: 172 | * @description: 复制内容到粘贴板 173 | * @param {str} content : 需要复制的内容 174 | * @param {str} message : 复制完后的提示,不传则默认提示"复制成功" 175 | * @return: 176 | */ 177 | 178 | export function copyToClip(content, message) { 179 | var aux = document.createElement('input'); 180 | aux.setAttribute('value', content); 181 | document.body.appendChild(aux); 182 | aux.select(); 183 | document.execCommand('copy'); 184 | document.body.removeChild(aux); 185 | if (message == null) { 186 | confirm('复制成功'); 187 | } else { 188 | confirm(message); 189 | } 190 | } 191 | 192 | /** 193 | *文件下载 194 | *引入后直接掉用 (此方法可能会引起跨域的问题) 195 | *传入参数 ulr地址 name文件名 196 | */ 197 | export const downloadMp3 = (filePath, fileName = 'yuying.wav') => { 198 | fetch(filePath) 199 | .then(res => res.blob()) 200 | .then(blob => { 201 | const a = document.createElement('a'); 202 | document.body.appendChild(a); 203 | a.style.display = 'none'; 204 | // 使用获取到的blob对象创建的url 205 | const url = window.URL.createObjectURL(blob); 206 | a.href = url; 207 | // 指定下载的文件名 208 | a.download = fileName; 209 | a.click(); 210 | document.body.removeChild(a); 211 | // 移除blob对象的url 212 | window.URL.revokeObjectURL(url); 213 | }); 214 | }; 215 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 229 | -------------------------------------------------------------------------------- /src/icons/svg/service.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 153 | 154 | 200 | 201 | 264 | -------------------------------------------------------------------------------- /src/components/Table/index.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 263 | 264 | 289 | -------------------------------------------------------------------------------- /src/components/upload/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 47 | 270 | 359 | -------------------------------------------------------------------------------- /src/views/worksheet/worksheetcom.vue: -------------------------------------------------------------------------------- 1 | 8 | /** * @name: 自定义表单 * @description: 基于vue2.0 element mobx二次封装自定义表单组件 * 9 | @formData:当前自定义表单所有类型集合 * @formData:当前自定义表单所有类型 */ 10 | 11 | 89 | 90 | 418 | 419 | 420 | 424 | -------------------------------------------------------------------------------- /src/components/cropperImg/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 88 | 377 | 465 | -------------------------------------------------------------------------------- /src/views/Admin/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 222 | 223 | 497 | 498 | 504 | --------------------------------------------------------------------------------