├── .browserslistrc
├── babel.config.js
├── public
├── favicon.ico
├── static
│ └── css
│ │ └── loading.css
└── index.html
├── src
├── assets
│ ├── logo.png
│ ├── error_images
│ │ ├── 403.png
│ │ ├── 404.png
│ │ └── cloud.png
│ └── login_images
│ │ ├── login_form.png
│ │ └── login_background.png
├── vab
│ ├── index.js
│ ├── styles
│ │ ├── vab.less
│ │ └── normalize.less
│ └── plugins
│ │ └── permissions.js
├── views
│ ├── index
│ │ └── index.vue
│ ├── test
│ │ └── index.vue
│ ├── vab
│ │ ├── table
│ │ │ └── index.vue
│ │ └── icon
│ │ │ └── index.vue
│ ├── sys
│ │ ├── classify
│ │ │ └── index.vue
│ │ ├── business
│ │ │ └── index.vue
│ │ ├── word
│ │ │ └── index.vue
│ │ ├── ad
│ │ │ └── index.vue
│ │ ├── advice
│ │ │ └── index.vue
│ │ ├── user
│ │ │ └── index.vue
│ │ ├── customer
│ │ │ └── index.vue
│ │ └── report
│ │ │ └── index.vue
│ ├── 404.vue
│ ├── 403.vue
│ └── login
│ │ └── index.vue
├── config
│ ├── config.js
│ ├── default
│ │ ├── index.js
│ │ ├── net.config.js
│ │ ├── theme.config.js
│ │ └── setting.config.js
│ └── index.js
├── api
│ ├── icon.js
│ ├── router.js
│ ├── business.js
│ ├── classify.js
│ ├── word.js
│ ├── ad.js
│ ├── report.js
│ ├── advice.js
│ ├── userlist.js
│ └── user.js
├── App.vue
├── utils
│ ├── pageTitle.js
│ ├── clipboard.js
│ ├── static.js
│ ├── hasRole.js
│ ├── accessToken.js
│ ├── routes.js
│ ├── request.js
│ ├── index.js
│ └── validate.js
├── layout
│ ├── vab-icon
│ │ └── index.vue
│ ├── vab-menu
│ │ ├── components
│ │ │ ├── Submenu.vue
│ │ │ └── MenuItem.vue
│ │ └── index.vue
│ ├── vab-logo
│ │ └── index.vue
│ ├── vab-content
│ │ └── index.vue
│ ├── vab-avatar
│ │ └── index.vue
│ ├── index.vue
│ └── vab-tabs
│ │ └── index.vue
├── store
│ ├── index.js
│ └── modules
│ │ ├── acl.js
│ │ ├── routes.js
│ │ ├── user.js
│ │ ├── tagsBar.js
│ │ └── settings.js
├── main.js
├── components
│ ├── broadcast.vue
│ └── Detail.vue
└── router
│ └── index.js
├── .stylelintrc.js
├── README.md
├── .gitattributes
├── .gitignore
├── mock
├── index.js
├── controller
│ ├── router.js
│ ├── table.js
│ └── user.js
├── utils
│ └── index.js
└── mockServer.js
├── .eslintrc.js
├── prettier.config.js
├── deploy.sh
├── package.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset'],
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/logo.png
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/error_images/403.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/error_images/403.png
--------------------------------------------------------------------------------
/src/assets/error_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/error_images/404.png
--------------------------------------------------------------------------------
/src/assets/error_images/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/error_images/cloud.png
--------------------------------------------------------------------------------
/src/assets/login_images/login_form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/login_images/login_form.png
--------------------------------------------------------------------------------
/src/assets/login_images/login_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shengbid/genuine-vue3/master/src/assets/login_images/login_background.png
--------------------------------------------------------------------------------
/src/vab/index.js:
--------------------------------------------------------------------------------
1 | // 加载插件
2 | const requirePlugin = require.context('./plugins', true, /\.js$/)
3 | requirePlugin.keys().forEach((fileName) => {
4 | requirePlugin(fileName)
5 | })
6 |
--------------------------------------------------------------------------------
/src/views/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 | 欢迎
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/src/config/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 导出自定义配置
3 | **/
4 | const config = {
5 | layout: 'vertical',
6 | donation: false,
7 | templateFolder: 'project',
8 | }
9 | module.exports = config
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 🌱商汇后台管理项目
2 |
3 | ```bash
4 | # 克隆项目
5 | git clone https://gitee.com/shengbide/genuine-admin-vue.git
6 | # 进入项目目录
7 | cd genuine-admin-vue
8 | # 安装依赖
9 | yarn
10 | # 本地开发 启动项目
11 | npm run serve
--------------------------------------------------------------------------------
/src/api/icon.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getIconList(params) {
4 | return request({
5 | url: '/icon/getList',
6 | method: 'get',
7 | params,
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/src/api/router.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getRouterList(params) {
4 | return request({
5 | url: '/menu/navigate',
6 | method: 'get',
7 | params,
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/src/views/test/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.html text eol=lf
2 | *.css text eol=lf
3 | *.js text eol=lf linguist-language=vue
4 | *.scss text eol=lf
5 | *.vue text eol=lf
6 | *.hbs text eol=lf
7 | *.sh text eol=lf
8 | *.md text eol=lf
9 | *.json text eol=lf
10 | *.yml text eol=lf
11 |
--------------------------------------------------------------------------------
/src/config/default/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 导出默认配置(通用配置|主题配置|网络配置)
3 | **/
4 | const setting = require('./setting.config')
5 | const theme = require('./theme.config')
6 | const network = require('./net.config')
7 |
8 | module.exports = { setting, theme, network }
9 |
--------------------------------------------------------------------------------
/src/api/business.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTRequirement',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/classify.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTIndustry',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/word.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTAdvertisementDynamic',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 3个子配置,通用配置|主题配置|网络配置,建议在当前目录下修改config.js修改配置,会覆盖默认配置,也可以直接修改默认配置
3 | */
4 | //默认配置
5 | const { setting, theme, network } = require('./default')
6 | //自定义配置
7 | const config = require('./config')
8 | //导出配置(以自定义配置为主)
9 | module.exports = Object.assign({}, setting, theme, network, config)
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 | package-lock.json
5 | yarn.lock
6 | *.zip
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | pnpm-debug.log*
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/api/ad.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTAdvertisement',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
12 | export async function deleteAd(id) {
13 | return request(`/gsh/setReportStatus/${id}`, {
14 | method: 'put',
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 导入所有 controller 模块,npm run serve时在node环境中自动输出controller文件夹下Mock接口,请勿修改。
4 | */
5 |
6 | const { handleMockArray } = require('./utils')
7 |
8 | const mocks = []
9 | const mockArray = handleMockArray()
10 | mockArray.forEach((item) => {
11 | const obj = require(item)
12 | mocks.push(...obj)
13 | })
14 | module.exports = {
15 | mocks,
16 | }
17 |
--------------------------------------------------------------------------------
/src/api/report.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTReport',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
12 | export async function setSugestionStatus(id) {
13 | return request(`/gsh/setReportStatus/${id}`, {
14 | method: 'put',
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/prettier'],
7 | parserOptions: {
8 | parser: 'babel-eslint',
9 | },
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/src/api/advice.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params) {
5 | return request({
6 | url: '/gsh/listByTSugestion',
7 | method: 'get',
8 | params: handlePage(params),
9 | })
10 | }
11 |
12 | export async function setSugestionStatus(id) {
13 | return request(`/gsh/setSugestionStatus/${id}`, {
14 | method: 'put',
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 80,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: false,
6 | singleQuote: true,
7 | quoteProps: 'as-needed',
8 | jsxSingleQuote: false,
9 | trailingComma: 'es5',
10 | bracketSpacing: true,
11 | jsxBracketSameLine: false,
12 | arrowParens: 'always',
13 | htmlWhitespaceSensitivity: 'ignore',
14 | vueIndentScriptAndStyle: true,
15 | endOfLine: 'lf',
16 | }
17 |
--------------------------------------------------------------------------------
/src/config/default/net.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 导出默认网路配置
3 | **/
4 | const network = {
5 | //配后端数据的接收方式application/json;charset=UTF-8 或 application/x-www-form-urlencoded;charset=UTF-8
6 | contentType: 'application/json;charset=UTF-8',
7 | //消息框消失时间
8 | messageDuration: 3000,
9 | //最长请求时间
10 | requestTimeout: 10000,
11 | //操作正常code,支持String、Array、int多种类型
12 | successCode: [200, 0],
13 | }
14 | module.exports = network
15 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
23 |
--------------------------------------------------------------------------------
/src/utils/pageTitle.js:
--------------------------------------------------------------------------------
1 | import { title, titleReverse, titleSeparator } from '@/config'
2 |
3 | /**
4 | * @author chuzhixin 1204505056@qq.com
5 | * @description 设置标题
6 | * @param pageTitle
7 | * @returns {string}
8 | */
9 | export default function getPageTitle(pageTitle) {
10 | let newTitles = []
11 | if (pageTitle) newTitles.push(pageTitle)
12 | if (title) newTitles.push(title)
13 | if (titleReverse) newTitles = newTitles.reverse()
14 | return newTitles.join(titleSeparator)
15 | }
16 |
--------------------------------------------------------------------------------
/src/layout/vab-icon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
25 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 导入所有 vuex 模块,自动加入namespaced:true,用于解决vuex命名冲突,请勿修改。
4 | */
5 | import { createStore } from 'vuex'
6 |
7 | const files = require.context('./modules', false, /\.js$/)
8 | const modules = {}
9 | files.keys().forEach((key) => {
10 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
11 | })
12 | Object.keys(modules).forEach((key) => {
13 | modules[key]['namespaced'] = true
14 | })
15 | export default createStore({
16 | modules,
17 | })
18 |
--------------------------------------------------------------------------------
/src/vab/styles/vab.less:
--------------------------------------------------------------------------------
1 | @import "./normalize.less";
2 |
3 | html {
4 | body {
5 |
6 | * {
7 | box-sizing: border-box;
8 | }
9 |
10 | /* ant-input-search搜索框 */
11 | .ant-input-search {
12 | max-width: 250px;
13 | }
14 |
15 | /* ant-pagination分页 */
16 | .ant-pagination {
17 | margin-top: @vab-margin;
18 | text-align: center;
19 |
20 | &.ant-table-pagination {
21 | float: none !important;
22 | margin-top: @vab-margin;
23 | }
24 |
25 | }
26 |
27 |
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import Antd from 'ant-design-vue'
3 | import App from './App'
4 | import router from './router'
5 | import store from './store'
6 | import 'ant-design-vue/dist/antd.css'
7 | import '@/vab'
8 | /**
9 | * @author chuzhixin 1204505056@qq.com
10 | * @description 正式环境默认使用mock,正式项目记得注释后再打包
11 | */
12 | // if (process.env.NODE_ENV === 'production') {
13 | // const { mockXHR } = require('@/utils/static')
14 | // mockXHR()
15 | // }
16 |
17 | createApp(App).use(store).use(router).use(Antd).mount('#app')
18 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 | npm run build
4 | cd dist
5 | touch .nojekyll
6 | git init
7 | git add -A
8 | git commit -m 'deploy'
9 | git push -f "https://${access_token}@gitee.com/chu1204505056/vue-admin-beautiful-mini.git" master:gh-pages
10 | git push -f "https://${access_token}@gitee.com/chu1204505056/vue-admin-beautiful-antdv.git" master:gh-pages
11 | start "https://gitee.com/chu1204505056/vue-admin-beautiful-mini/pages"
12 | start "https://gitee.com/chu1204505056/vue-admin-beautiful-antdv/pages"
13 | cd -
14 | exec /bin/bash
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/config/default/theme.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 导出默认主题配置
3 | */
4 | const theme = {
5 | //布局种类 horizontal vertical gallery comprehensive common
6 | layout: 'horizontal',
7 | //主题名称 default ocean green glory white
8 | themeName: 'default',
9 | //是否固定头部
10 | fixedHeader: true,
11 | //是否显示顶部进度条
12 | showProgressBar: true,
13 | //是否显示多标签页
14 | showTabsBar: true,
15 | //是否显示语言选择组件
16 | showLanguage: true,
17 | //是否显示刷新组件
18 | showRefresh: true,
19 | //是否显示搜索组件
20 | showSearch: true,
21 | //是否显示主题组件
22 | showTheme: true,
23 | //是否显示通知组件
24 | showNotice: true,
25 | //是否显示全屏组件
26 | showFullScreen: true,
27 | }
28 | module.exports = theme
29 |
--------------------------------------------------------------------------------
/src/utils/clipboard.js:
--------------------------------------------------------------------------------
1 | import Clipboard from 'clipboard'
2 | import { message } from 'ant-design-vue'
3 |
4 | function clipboardSuccess(text) {
5 | message.success(`复制${text}成功`)
6 | }
7 |
8 | function clipboardError(text) {
9 | message.error(`复制${text}失败`)
10 | }
11 |
12 | /**
13 | * @description 复制数据
14 | * @param text
15 | * @param event
16 | */
17 | export default function handleClipboard(text, event) {
18 | const clipboard = new Clipboard(event.target, {
19 | text: () => text,
20 | })
21 | clipboard.on('success', () => {
22 | clipboardSuccess(text)
23 | clipboard.destroy()
24 | })
25 | clipboard.on('error', () => {
26 | clipboardError(text)
27 | clipboard.destroy()
28 | })
29 | clipboard.onClick(event)
30 | }
31 |
--------------------------------------------------------------------------------
/src/api/userlist.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { handlePage } from '@/utils'
3 |
4 | export function getList(params, type) {
5 | return request({
6 | url: '/gsh/listByTGshUser',
7 | method: 'get',
8 | params: {
9 | ...handlePage(params),
10 | type,
11 | },
12 | })
13 | }
14 |
15 | export function toFreeze(id, type) {
16 | return request({
17 | url: `/gsh/freeze/${id}/${type}`,
18 | method: 'delete',
19 | })
20 | }
21 |
22 | export function toBatchMassage(data) {
23 | return request({
24 | url: '/gsh/batchMassage',
25 | method: 'post',
26 | data,
27 | })
28 | }
29 |
30 | // 获取用户详情
31 | export async function getUserInfo(id) {
32 | return request(`/gsh/gshUserDetail/${id}`)
33 | }
34 |
--------------------------------------------------------------------------------
/mock/controller/router.js:
--------------------------------------------------------------------------------
1 | const data = [
2 | {
3 | path: '/',
4 | component: 'Layout',
5 | redirect: '/index',
6 | meta: {
7 | title: '首页',
8 | icon: 'home-4-line',
9 | affix: true,
10 | },
11 | children: [
12 | {
13 | path: 'index',
14 | name: 'Index',
15 | component: '@/views/index',
16 | meta: {
17 | title: '首页',
18 | icon: 'home-4-line',
19 | affix: true,
20 | },
21 | },
22 | ],
23 | },
24 | ]
25 | module.exports = [
26 | {
27 | url: '/menu/navigate',
28 | type: 'get',
29 | response() {
30 | return {
31 | code: 200,
32 | msg: 'success',
33 | data,
34 | }
35 | },
36 | },
37 | ]
38 |
--------------------------------------------------------------------------------
/src/layout/vab-menu/components/Submenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ item.meta.title }}
8 |
9 |
10 |
11 |
12 |
13 |
34 |
--------------------------------------------------------------------------------
/src/store/modules/acl.js:
--------------------------------------------------------------------------------
1 | const state = () => ({
2 | admin: false,
3 | role: [],
4 | ability: [],
5 | })
6 | const getters = {
7 | admin: (state) => state.admin,
8 | role: (state) => state.role,
9 | ability: (state) => state.ability,
10 | }
11 | const mutations = {
12 | setFull(state, admin) {
13 | state.admin = admin
14 | },
15 | setRole(state, role) {
16 | state.role = role
17 | },
18 | setAbility(state, ability) {
19 | state.ability = ability
20 | },
21 | }
22 | const actions = {
23 | setFull({ commit }, admin) {
24 | commit('setFull', admin)
25 | },
26 | setRole({ commit }, role) {
27 | commit('setRole', role)
28 | },
29 | setAbility({ commit }, ability) {
30 | commit('setAbility', ability)
31 | },
32 | }
33 | export default { state, getters, mutations, actions }
34 |
--------------------------------------------------------------------------------
/src/layout/vab-logo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
26 |
46 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | // import { tokenName } from '@/config'
3 |
4 | // 登陆
5 | export async function login(data) {
6 | return request({
7 | url: '/gsh/login',
8 | method: 'post',
9 | data,
10 | })
11 | }
12 |
13 | // 注册
14 | export async function register(data) {
15 | return request({
16 | url: '/gsh/saveTGshUser',
17 | method: 'post',
18 | data,
19 | })
20 | }
21 | // 获取登陆用户信息
22 | export function getUserInfo(accessToken) {
23 | //此处为了兼容mock.js使用data传递accessToken,如果使用mock可以走headers
24 | // return request({
25 | // url: '/userInfo',
26 | // method: 'post',
27 | // data: {
28 | // [tokenName]: accessToken,
29 | // },
30 | // })
31 | return new Promise((reslove) => {
32 | reslove({
33 | data: {
34 | username: localStorage.getItem('username') || '管理员',
35 | avatar: '',
36 | },
37 | })
38 | })
39 | }
40 |
41 | // 退出登录
42 | export function logout(data) {
43 | return request({
44 | url: '/gsh/logOut',
45 | method: 'post',
46 | data,
47 | })
48 | }
49 | // 获取验证码
50 | export async function getFakeCaptcha(params) {
51 | return request({
52 | url: '/email/sendSimpleEmail',
53 | params,
54 | })
55 | }
56 |
--------------------------------------------------------------------------------
/src/vab/plugins/permissions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 路由守卫,目前两种模式:all模式与intelligence模式
4 | */
5 | import router from '@/router'
6 | import store from '@/store'
7 | import getPageTitle from '@/utils/pageTitle'
8 | import { loginInterception, recordRoute, routesWhiteList } from '@/config'
9 |
10 | router.beforeEach(async (to, from, next) => {
11 | let hasToken = store.getters['user/accessToken']
12 |
13 | if (!loginInterception) hasToken = true
14 |
15 | if (hasToken) {
16 | if (to.path === '/login') {
17 | next({ path: '/' })
18 | } else {
19 | const hasPermission = store.getters['user/username']
20 | if (hasPermission) {
21 | next()
22 | } else {
23 | try {
24 | await store.dispatch('user/getUserInfo')
25 | next()
26 | } catch (error) {
27 | await store.dispatch('user/resetAll')
28 | }
29 | }
30 | }
31 | } else {
32 | if (routesWhiteList.indexOf(to.path) !== -1) {
33 | next()
34 | } else {
35 | if (recordRoute)
36 | next({ path: '/login', query: { redirect: to.path }, replace: true })
37 | else next({ path: '/login', replace: true })
38 | }
39 | }
40 | })
41 | router.afterEach((to) => {
42 | document.title = getPageTitle(to.meta.title)
43 | })
44 |
--------------------------------------------------------------------------------
/mock/utils/index.js:
--------------------------------------------------------------------------------
1 | const { Random } = require('mockjs')
2 | const { join } = require('path')
3 | const fs = require('fs')
4 |
5 | /**
6 | * @author chuzhixin 1204505056@qq.com
7 | * @description 随机生成图片url。
8 | * @param width
9 | * @param height
10 | * @returns {string}
11 | */
12 | function handleRandomImage(width = 50, height = 50) {
13 | return `https://picsum.photos/${width}/${height}?random=${Random.guid()}`
14 | }
15 |
16 | /**
17 | * @author chuzhixin 1204505056@qq.com
18 | * @description 处理所有 controller 模块,npm run serve时在node环境中自动输出controller文件夹下Mock接口,请勿修改。
19 | * @returns {[]}
20 | */
21 | function handleMockArray() {
22 | const mockArray = []
23 | const getFiles = (jsonPath) => {
24 | const jsonFiles = []
25 | const findJsonFile = (path) => {
26 | const files = fs.readdirSync(path)
27 | files.forEach((item) => {
28 | const fPath = join(path, item)
29 | const stat = fs.statSync(fPath)
30 | if (stat.isDirectory() === true) findJsonFile(item)
31 | if (stat.isFile() === true) jsonFiles.push(item)
32 | })
33 | }
34 | findJsonFile(jsonPath)
35 | jsonFiles.forEach((item) => mockArray.push(`./controller/${item}`))
36 | }
37 | getFiles('mock/controller')
38 | return mockArray
39 | }
40 | module.exports = {
41 | handleRandomImage,
42 | handleMockArray,
43 | }
44 |
--------------------------------------------------------------------------------
/src/layout/vab-menu/components/MenuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ routeChildren.meta.title }}
7 |
8 |
9 |
10 |
43 |
--------------------------------------------------------------------------------
/src/utils/static.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 导入所有 controller 模块,浏览器环境中自动输出controller文件夹下Mock接口,请勿修改。
4 | */
5 | import Mock from 'mockjs'
6 | import { paramObj } from '@/utils/index'
7 |
8 | const mocks = []
9 | const files = require.context('../../mock/controller', false, /\.js$/)
10 |
11 | files.keys().forEach((key) => {
12 | mocks.push(...files(key))
13 | })
14 |
15 | export function mockXHR() {
16 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
17 | Mock.XHR.prototype.send = function () {
18 | if (this.custom.xhr) {
19 | this.custom.xhr.withCredentials = this.withCredentials || false
20 |
21 | if (this.responseType) {
22 | this.custom.xhr.responseType = this.responseType
23 | }
24 | }
25 | this.proxy_send(...arguments)
26 | }
27 |
28 | function XHRHttpRequst(respond) {
29 | return function (options) {
30 | let result
31 | if (respond instanceof Function) {
32 | const { body, type, url } = options
33 | result = respond({
34 | method: type,
35 | body: JSON.parse(body),
36 | query: paramObj(url),
37 | })
38 | } else {
39 | result = respond
40 | }
41 | return Mock.mock(result)
42 | }
43 | }
44 |
45 | mocks.forEach((item) => {
46 | Mock.mock(
47 | new RegExp(item.url),
48 | item.type || 'get',
49 | XHRHttpRequst(item.response)
50 | )
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/src/layout/vab-content/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
26 |
27 |
59 |
--------------------------------------------------------------------------------
/src/utils/hasRole.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | export function hasRole(value) {
4 | if (store.getters['acl/admin']) return true
5 | if (value instanceof Array && value.length > 0)
6 | return can(store.getters['acl/role'], {
7 | role: value,
8 | mode: 'oneOf',
9 | })
10 | let mode = 'oneOf'
11 | if (Object.prototype.hasOwnProperty.call(value, 'mode')) mode = value['mode']
12 | let result = true
13 | if (Object.prototype.hasOwnProperty.call(value, 'role'))
14 | result =
15 | result && can(store.getters['acl/role'], { role: value['role'], mode })
16 | if (result && Object.prototype.hasOwnProperty.call(value, 'ability'))
17 | result =
18 | result &&
19 | can(store.getters['acl/ability'], {
20 | role: value['ability'],
21 | mode,
22 | })
23 | return result
24 | }
25 |
26 | export function can(roleOrAbility, value) {
27 | let hasRole = false
28 | if (
29 | value instanceof Object &&
30 | Object.prototype.hasOwnProperty.call(value, 'role') &&
31 | Object.prototype.hasOwnProperty.call(value, 'mode')
32 | ) {
33 | const { role, mode } = value
34 | if (mode === 'allOf') {
35 | hasRole = role.every((item) => {
36 | return roleOrAbility.includes(item)
37 | })
38 | }
39 | if (mode === 'oneOf') {
40 | hasRole = role.some((item) => {
41 | return roleOrAbility.includes(item)
42 | })
43 | }
44 | if (mode === 'except') {
45 | hasRole = !role.some((item) => {
46 | return roleOrAbility.includes(item)
47 | })
48 | }
49 | }
50 | return hasRole
51 | }
52 |
--------------------------------------------------------------------------------
/src/layout/vab-menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
56 |
61 |
--------------------------------------------------------------------------------
/mock/controller/table.js:
--------------------------------------------------------------------------------
1 | const { mock } = require('mockjs')
2 | const { handleRandomImage } = require('../utils')
3 | const List = []
4 | const count = 50
5 | for (let i = 0; i < count; i++) {
6 | List.push(
7 | mock({
8 | uuid: '@uuid',
9 | id: '@id',
10 | title: '@title(1, 2)',
11 | description: '@csentence',
12 | 'status|1': ['published', 'draft', 'deleted'],
13 | author: '@cname',
14 | datetime: '@datetime',
15 | pageViews: '@integer(300, 5000)',
16 | img: handleRandomImage(228, 228),
17 | switch: '@boolean',
18 | percent: '@integer(80,99)',
19 | 'rate|1': [1, 2, 3, 4, 5],
20 | })
21 | )
22 | }
23 |
24 | module.exports = [
25 | {
26 | url: '/table/getList',
27 | type: 'get',
28 | response(config) {
29 | const { title, current = 1, pageSize = 10 } = config.query
30 | let mockList = List.filter((item) => {
31 | return !(title && item.title.indexOf(title) < 0)
32 | })
33 | const pageList = mockList.filter(
34 | (item, index) =>
35 | index < pageSize * current && index >= pageSize * (current - 1)
36 | )
37 | return {
38 | code: 200,
39 | msg: 'success',
40 | total: mockList.length,
41 | data: pageList,
42 | }
43 | },
44 | },
45 | {
46 | url: '/table/doEdit',
47 | type: 'post',
48 | response() {
49 | return {
50 | code: 200,
51 | msg: '模拟保存成功',
52 | }
53 | },
54 | },
55 | {
56 | url: '/table/doDelete',
57 | type: 'post',
58 | response() {
59 | return {
60 | code: 200,
61 | msg: '模拟删除成功',
62 | }
63 | },
64 | },
65 | ]
66 |
--------------------------------------------------------------------------------
/src/layout/vab-avatar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ username }}
11 |
12 |
13 |
14 |
15 | 退出登录
16 |
17 |
18 |
19 |
20 |
21 |
22 |
55 |
64 |
--------------------------------------------------------------------------------
/src/components/broadcast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
66 |
--------------------------------------------------------------------------------
/src/views/vab/table/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "genuine-admin-antdv",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "clear": "rimraf node_modules&&npm install --registry=https://registry.npm.taobao.org",
10 | "use:npm": "nrm use npm",
11 | "use:taobao": "nrm use taobao",
12 | "update": "ncu -u --target greatest&&npm install --registry=https://registry.npm.taobao.org",
13 | "deploy": "start ./deploy.sh"
14 | },
15 | "dependencies": {
16 | "ant-design-vue": "3.2.10",
17 | "axios": "^0.21.1",
18 | "clipboard": "^2.0.8",
19 | "core-js": "^3.15.2",
20 | "dayjs": "^1.10.6",
21 | "js-cookie": "^3.0.0-rc.3",
22 | "mockjs": "^1.1.0",
23 | "remixicon": "^2.5.0",
24 | "vue": "^3.1.4",
25 | "vue-router": "^4.0.10",
26 | "vuex": "^4.0.2"
27 | },
28 | "devDependencies": {
29 | "@vue/cli-plugin-babel": "^4.5.9",
30 | "@vue/cli-plugin-eslint": "^4.5.9",
31 | "@vue/cli-service": "^4.5.9",
32 | "@vue/compiler-sfc": "^3.1.4",
33 | "@vue/eslint-config-prettier": "^6.0.0",
34 | "babel-eslint": "^11.0.0-beta.2",
35 | "body-parser": "^1.19.0",
36 | "chalk": "^4.1.1",
37 | "chokidar": "^3.5.2",
38 | "eslint": "^7.30.0",
39 | "eslint-plugin-prettier": "^3.4.0",
40 | "eslint-plugin-vue": "^7.13.0",
41 | "filemanager-webpack-plugin": "^6.1.4",
42 | "image-webpack-loader": "^7.0.1",
43 | "less": "^4.1.1",
44 | "less-loader": "^7.3.0",
45 | "prettier": "^2.3.2",
46 | "stylelint": "^13.13.1",
47 | "stylelint-config-prettier": "^8.0.2",
48 | "stylelint-config-recess-order": "^2.4.0",
49 | "svg-sprite-loader": "^6.0.9",
50 | "vab-config": "0.0.8",
51 | "webpackbar": "^5.0.0-3"
52 | },
53 | "lint-staged": {
54 | "*.{js,jsx,vue}": [
55 | "vue-cli-service lint",
56 | "git add"
57 | ]
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/static/css/loading.css:
--------------------------------------------------------------------------------
1 | .first-loading-wrp {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | height: 90vh;
7 | min-height: 90vh;
8 | }
9 |
10 | .first-loading-wrp > h1 {
11 | font-size: 24px;
12 | font-weight: bolder;
13 | }
14 |
15 | .first-loading-wrp .loading-wrp {
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | padding: 98px;
20 | }
21 |
22 | .dot {
23 | position: relative;
24 | box-sizing: border-box;
25 | display: inline-block;
26 | width: 64px;
27 | height: 64px;
28 | font-size: 64px;
29 | transform: rotate(45deg);
30 | animation: antRotate 1.2s infinite linear;
31 | }
32 |
33 | .dot i {
34 | position: absolute;
35 | display: block;
36 | width: 28px;
37 | height: 28px;
38 | background-color: #1890ff;
39 | border-radius: 100%;
40 | opacity: 0.3;
41 | transform: scale(0.75);
42 | transform-origin: 50% 50%;
43 | animation: antSpinMove 1s infinite linear alternate;
44 | }
45 |
46 | .dot i:nth-child(1) {
47 | top: 0;
48 | left: 0;
49 | }
50 |
51 | .dot i:nth-child(2) {
52 | top: 0;
53 | right: 0;
54 | -webkit-animation-delay: 0.4s;
55 | animation-delay: 0.4s;
56 | }
57 |
58 | .dot i:nth-child(3) {
59 | right: 0;
60 | bottom: 0;
61 | -webkit-animation-delay: 0.8s;
62 | animation-delay: 0.8s;
63 | }
64 |
65 | .dot i:nth-child(4) {
66 | bottom: 0;
67 | left: 0;
68 | -webkit-animation-delay: 1.2s;
69 | animation-delay: 1.2s;
70 | }
71 |
72 | @keyframes antRotate {
73 | to {
74 | -webkit-transform: rotate(405deg);
75 | transform: rotate(405deg);
76 | }
77 | }
78 |
79 | @-webkit-keyframes antRotate {
80 | to {
81 | -webkit-transform: rotate(405deg);
82 | transform: rotate(405deg);
83 | }
84 | }
85 |
86 | @keyframes antSpinMove {
87 | to {
88 | opacity: 1;
89 | }
90 | }
91 |
92 | @-webkit-keyframes antSpinMove {
93 | to {
94 | opacity: 1;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/views/sys/classify/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 | 编辑
15 |
16 |
17 |
18 |
19 |
20 |
21 |
81 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
13 |
17 |
18 |
19 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
<%= VUE_APP_TITLE %>
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/store/modules/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 路由拦截状态管理,目前两种模式:all模式与intelligence模式,其中partialRoutes是菜单暂未使用
4 | */
5 | import { asyncRoutes, constantRoutes } from '@/router'
6 | import { getRouterList } from '@/api/router'
7 | import { convertRouter, filterRoutes } from '@/utils/routes'
8 |
9 | const state = () => ({
10 | routes: filterRoutes([...constantRoutes, ...asyncRoutes]),
11 | partialRoutes: [],
12 | })
13 | const getters = {
14 | routes: (state) => state.routes,
15 | partialRoutes: (state) => state.partialRoutes,
16 | }
17 | const mutations = {
18 | setRoutes(state, routes) {
19 | state.routes = routes
20 | },
21 | setPartialRoutes(state, routes) {
22 | state.partialRoutes = routes
23 | },
24 | }
25 | const actions = {
26 | /**
27 | * @author chuzhixin 1204505056@qq.com
28 | * @description intelligence模式设置路由
29 | * @param {*} { commit }
30 | * @returns
31 | */
32 | async setRoutes({ commit }) {
33 | const finallyRoutes = filterRoutes([...constantRoutes, ...asyncRoutes])
34 | commit('setRoutes', finallyRoutes)
35 | return [...asyncRoutes]
36 | },
37 | /**
38 | * @author chuzhixin 1204505056@qq.com
39 | * @description all模式设置路由
40 | * @param {*} { commit }
41 | * @returns
42 | */
43 | async setAllRoutes({ commit }) {
44 | let { data } = await getRouterList()
45 | if (data[data.length - 1].path !== '*')
46 | data.push({ path: '*', redirect: '/404', hidden: true })
47 | const asyncRoutes = convertRouter(data)
48 | const finallyRoutes = filterRoutes([...constantRoutes, ...asyncRoutes])
49 | commit('setRoutes', finallyRoutes)
50 | return [...asyncRoutes]
51 | },
52 | /**
53 | * @author chuzhixin 1204505056@qq.com
54 | * @description 画廊布局、综合布局设置路由
55 | * @param {*} { commit }
56 | * @param accessedRoutes 画廊布局、综合布局设置路由
57 | */
58 | setPartialRoutes({ commit }, accessedRoutes) {
59 | commit('setPartialRoutes', accessedRoutes)
60 | },
61 | }
62 | export default { state, getters, mutations, actions }
63 |
--------------------------------------------------------------------------------
/src/views/sys/business/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 | 编辑
15 |
16 |
17 |
18 |
19 |
20 |
21 |
89 |
--------------------------------------------------------------------------------
/src/views/sys/word/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | {{ record.beginTime }} - {{ record.overTime }}
14 |
15 |
16 |
17 | 编辑
18 |
19 |
20 |
21 |
22 |
23 |
24 |
86 |
--------------------------------------------------------------------------------
/src/utils/accessToken.js:
--------------------------------------------------------------------------------
1 | import { storage, tokenTableName } from '@/config'
2 | import cookie from 'js-cookie'
3 |
4 | /**
5 | * @author chuzhixin 1204505056@qq.com
6 | * @description 获取accessToken
7 | * @returns {string|ActiveX.IXMLDOMNode|Promise|any|IDBRequest|MediaKeyStatus|FormDataEntryValue|Function|Promise}
8 | */
9 | export function getAccessToken() {
10 | if (storage) {
11 | if ('localStorage' === storage) {
12 | return localStorage.getItem(tokenTableName)
13 | } else if ('sessionStorage' === storage) {
14 | return sessionStorage.getItem(tokenTableName)
15 | } else if ('cookie' === storage) {
16 | return cookie.get(tokenTableName)
17 | } else {
18 | return localStorage.getItem(tokenTableName)
19 | }
20 | } else {
21 | return localStorage.getItem(tokenTableName)
22 | }
23 | }
24 |
25 | /**
26 | * @author chuzhixin 1204505056@qq.com
27 | * @description 存储accessToken
28 | * @param accessToken
29 | * @returns {void|*}
30 | */
31 | export function setAccessToken(accessToken) {
32 | if (storage) {
33 | if ('localStorage' === storage) {
34 | return localStorage.setItem(tokenTableName, accessToken)
35 | } else if ('sessionStorage' === storage) {
36 | return sessionStorage.setItem(tokenTableName, accessToken)
37 | } else if ('cookie' === storage) {
38 | return cookie.set(tokenTableName, accessToken)
39 | } else {
40 | return localStorage.setItem(tokenTableName, accessToken)
41 | }
42 | } else {
43 | return localStorage.setItem(tokenTableName, accessToken)
44 | }
45 | }
46 |
47 | /**
48 | * @author chuzhixin 1204505056@qq.com
49 | * @description 移除accessToken
50 | * @returns {void|Promise}
51 | */
52 | export function removeAccessToken() {
53 | if (storage) {
54 | if ('localStorage' === storage) {
55 | return localStorage.removeItem(tokenTableName)
56 | } else if ('sessionStorage' === storage) {
57 | return sessionStorage.clear()
58 | } else if ('cookie' === storage) {
59 | return cookie.remove(tokenTableName)
60 | } else {
61 | return localStorage.removeItem(tokenTableName)
62 | }
63 | } else {
64 | return localStorage.removeItem(tokenTableName)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/config/default/setting.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 导出默认通用配置
3 | */
4 | const setting = {
5 | //开发以及部署时的URL,hash模式时在不确定二级目录名称的情况下建议使用""代表相对路径或者"/二级目录/",history模式默认使用"/"或者"/二级目录/"
6 | publicPath: '',
7 | //生产环境构建文件的目录名
8 | outputDir: 'dist',
9 | //放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
10 | assetsDir: 'static',
11 | //开发环境每次保存时是否输出为eslint编译警告
12 | lintOnSave: true,
13 | //进行编译的依赖
14 | transpileDependencies: ['vue-echarts', 'resize-detector'],
15 | //默认的接口地址 如果是开发环境和生产环境走vab-mock-server,当然你也可以选择自己配置成需要的接口地址
16 | baseURL:
17 | '/api',
18 | //标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
19 | title: 'geninue-admin-vue',
20 | //标题分隔符
21 | titleSeparator: ' - ',
22 | //标题是否反转 如果为false:"page - title",如果为ture:"title - page"
23 | titleReverse: false,
24 | //简写
25 | abbreviation: 'vab-pro',
26 | //开发环境端口号
27 | devPort: '9999',
28 | //版本号
29 | version: process.env.VUE_APP_VERSION,
30 | //pro版本copyright可随意修改
31 | copyright: 'chuzhixin 1204505056@qq.com',
32 | //缓存路由的最大数量
33 | keepAliveMaxNum: 99,
34 | //路由模式,可选值为 history 或 hash
35 | routerMode: 'hash',
36 | //不经过token校验的路由
37 | routesWhiteList: ['/login', '/register', '/callback', '/404', '/403'],
38 | //加载时显示文字
39 | loadingText: '正在加载中...',
40 | //token名称
41 | tokenName: 'token',
42 | //token在localStorage、sessionStorage、cookie存储的key的名称
43 | tokenTableName: 'token',
44 | //token存储位置localStorage sessionStorage cookie
45 | storage: 'localStorage',
46 | //token失效回退到登录页时是否记录本次的路由
47 | recordRoute: true,
48 | //是否显示logo,不显示时设置false,显示时请填写remixIcon图标名称,暂时只支持设置remixIcon
49 | logo: 'vuejs-fill',
50 | //语言类型zh、en
51 | i18n: 'zh',
52 | //在哪些环境下显示高亮错误
53 | errorLog: ['development', 'production'],
54 | //是否开启登录拦截
55 | loginInterception: true,
56 | //是否开启登录RSA加密
57 | loginRSA: false,
58 | //intelligence(前端导出路由)和all(后端导出路由)两种方式
59 | authentication: 'intelligence',
60 | //是否开启roles字段进行角色权限控制(如果是all模式后端完全处理角色并进行json组装,可设置false不处理路由中的roles字段)
61 | rolesControl: true,
62 | //vertical gallery comprehensive common布局时是否只保持一个子菜单的展开
63 | uniqueOpened: false,
64 | //vertical布局时默认展开的菜单path,使用逗号隔开建议只展开一个
65 | defaultOpeneds: ['/vab'],
66 | //需要加loading层的请求,防止重复提交
67 | debounce: ['doEdit'],
68 | //需要自动注入并加载的模块
69 | providePlugin: {},
70 | //npm run build时是否自动生成7z压缩包
71 | build7z: false,
72 | //代码生成机生成在view下的文件夹名称
73 | templateFolder: 'project',
74 | //是否显示终端donation打印
75 | donation: false,
76 | //画廊布局和综合布局时,是否点击一级菜单默认开启第一个二级菜单
77 | openFirstMenu: true,
78 | }
79 | module.exports = setting
80 |
--------------------------------------------------------------------------------
/src/utils/routes.js:
--------------------------------------------------------------------------------
1 | import router from '@/router'
2 | import path from 'path'
3 | import { rolesControl } from '@/config'
4 | import { isExternal } from '@/utils/validate'
5 | import { hasRole } from '@/utils/hasRole'
6 |
7 | /**
8 | * @author chuzhixin 1204505056@qq.com
9 | * @description all模式渲染后端返回路由
10 | * @param constantRoutes
11 | * @returns {*}
12 | */
13 | export function convertRouter(constantRoutes) {
14 | return constantRoutes.map((route) => {
15 | if (route.component) {
16 | if (route.component === 'Layout') {
17 | const path = 'layouts'
18 | route.component = (resolve) => require([`@/${path}`], resolve)
19 | } else {
20 | let path = 'views/' + route.component
21 | if (
22 | new RegExp('^/views/.*$').test(route.component) ||
23 | new RegExp('^views/.*$').test(route.component)
24 | ) {
25 | path = route.component
26 | } else if (new RegExp('^/.*$').test(route.component)) {
27 | path = 'views' + route.component
28 | } else if (new RegExp('^@views/.*$').test(route.component)) {
29 | path = route.component.slice(1)
30 | } else {
31 | path = 'views/' + route.component
32 | }
33 | route.component = (resolve) => require([`@/${path}`], resolve)
34 | }
35 | }
36 | if (route.children && route.children.length)
37 | route.children = convertRouter(route.children)
38 |
39 | if (route.children && route.children.length === 0) delete route.children
40 |
41 | return route
42 | })
43 | }
44 |
45 | /**
46 | * @author chuzhixin 1204505056@qq.com
47 | * @description 根据roles数组拦截路由
48 | * @param routes
49 | * @param baseUrl
50 | * @returns {[]}
51 | */
52 | export function filterRoutes(routes, baseUrl = '/') {
53 | return routes
54 | .filter((route) => {
55 | if (route.meta && route.meta.roles)
56 | return !rolesControl || hasRole(route.meta.roles)
57 | else return true
58 | })
59 | .map((route) => {
60 | if (route.path !== '*' && !isExternal(route.path))
61 | route.path = path.resolve(baseUrl, route.path)
62 | route.fullPath = route.path
63 | if (route.children)
64 | route.children = filterRoutes(route.children, route.fullPath)
65 | return route
66 | })
67 | }
68 |
69 | /**
70 | * 根据当前页面firstMenu
71 | * @returns {string}
72 | */
73 | export function handleFirstMenu() {
74 | const firstMenu = router.currentRoute.matched[0].path
75 | if (firstMenu === '') return '/'
76 | return firstMenu
77 | }
78 |
--------------------------------------------------------------------------------
/mock/controller/user.js:
--------------------------------------------------------------------------------
1 | const accessTokens = {
2 | admin: 'admin-accessToken',
3 | editor: 'editor-accessToken',
4 | test: 'test-accessToken',
5 | }
6 |
7 | module.exports = [
8 | {
9 | url: '/login',
10 | type: 'post',
11 | response(config) {
12 | const { username } = config.body
13 | const accessToken = accessTokens[username]
14 | if (!accessToken) {
15 | return {
16 | code: 500,
17 | msg: '帐户或密码不正确。',
18 | }
19 | }
20 | return {
21 | code: 200,
22 | msg: 'success',
23 | data: { accessToken },
24 | }
25 | },
26 | },
27 | {
28 | url: '/socialLogin',
29 | type: 'post',
30 | response(config) {
31 | const { code } = config.body
32 | if (!code) {
33 | return {
34 | code: 500,
35 | msg: '未成功获取Token。',
36 | }
37 | }
38 | return {
39 | code: 200,
40 | msg: 'success',
41 | data: { accessToken: accessTokens['admin'] },
42 | }
43 | },
44 | },
45 | {
46 | url: '/register',
47 | type: 'post',
48 | response() {
49 | return {
50 | code: 200,
51 | msg: '模拟注册成功',
52 | }
53 | },
54 | },
55 | {
56 | url: '/userInfo',
57 | type: 'post',
58 | response(config) {
59 | const { accessToken } = config.body
60 | let roles = ['admin']
61 | let ability = ['READ']
62 | let username = 'admin'
63 | if ('admin-accessToken' === accessToken) {
64 | roles = ['admin']
65 | ability = ['READ', 'WRITE', 'DELETE']
66 | username = 'admin'
67 | }
68 | if ('editor-accessToken' === accessToken) {
69 | roles = ['editor']
70 | ability = ['READ', 'WRITE']
71 | username = 'editor'
72 | }
73 | if ('test-accessToken' === accessToken) {
74 | roles = ['admin', 'editor']
75 | ability = ['READ']
76 | username = 'test'
77 | }
78 | return {
79 | code: 200,
80 | msg: 'success',
81 | data: {
82 | roles,
83 | ability,
84 | username,
85 | 'avatar|1': [
86 | 'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',
87 | 'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',
88 | ],
89 | },
90 | }
91 | },
92 | },
93 | {
94 | url: '/logout',
95 | type: 'post',
96 | response() {
97 | return {
98 | code: 200,
99 | msg: 'success',
100 | }
101 | },
102 | },
103 | ]
104 |
--------------------------------------------------------------------------------
/mock/mockServer.js:
--------------------------------------------------------------------------------
1 | const chokidar = require('chokidar')
2 | const bodyParser = require('body-parser')
3 | const chalk = require('chalk')
4 | const path = require('path')
5 | const Mock = require('mockjs')
6 | const { baseURL } = require('../src/config')
7 | const mockDir = path.join(process.cwd(), 'mock')
8 |
9 | /**
10 | *
11 | * @param app
12 | * @returns {{mockStartIndex: number, mockRoutesLength: number}}
13 | */
14 | const registerRoutes = (app) => {
15 | let mockLastIndex
16 | const { mocks } = require('./index.js')
17 | const mocksForServer = mocks.map((route) => {
18 | return responseFake(route.url, route.type, route.response)
19 | })
20 | for (const mock of mocksForServer) {
21 | app[mock.type](mock.url, mock.response)
22 | mockLastIndex = app._router.stack.length
23 | }
24 | const mockRoutesLength = Object.keys(mocksForServer).length
25 | return {
26 | mockRoutesLength: mockRoutesLength,
27 | mockStartIndex: mockLastIndex - mockRoutesLength,
28 | }
29 | }
30 |
31 | /**
32 | *
33 | * @param url
34 | * @param type
35 | * @param respond
36 | * @returns {{response(*=, *=): void, type: (*|string), url: RegExp}}
37 | */
38 | const responseFake = (url, type, respond) => {
39 | return {
40 | url: new RegExp(`${baseURL}${url}`),
41 | type: type || 'get',
42 | response(req, res) {
43 | res.status(200)
44 | if (JSON.stringify(req.body) !== '{}') {
45 | console.log(chalk.green(`> 请求地址:${req.path}`))
46 | console.log(chalk.green(`> 请求参数:${JSON.stringify(req.body)}\n`))
47 | } else {
48 | console.log(chalk.green(`> 请求地址:${req.path}\n`))
49 | }
50 | res.json(
51 | Mock.mock(respond instanceof Function ? respond(req, res) : respond)
52 | )
53 | },
54 | }
55 | }
56 | /**
57 | *
58 | * @param app
59 | */
60 | module.exports = (app) => {
61 | app.use(bodyParser.json())
62 | app.use(
63 | bodyParser.urlencoded({
64 | extended: true,
65 | })
66 | )
67 |
68 | const mockRoutes = registerRoutes(app)
69 | let mockRoutesLength = mockRoutes.mockRoutesLength
70 | let mockStartIndex = mockRoutes.mockStartIndex
71 | chokidar
72 | .watch(mockDir, {
73 | ignored: /mock-server/,
74 | ignoreInitial: true,
75 | })
76 | .on('all', (event) => {
77 | if (event === 'change' || event === 'add') {
78 | try {
79 | app._router.stack.splice(mockStartIndex, mockRoutesLength)
80 |
81 | Object.keys(require.cache).forEach((item) => {
82 | if (item.includes(mockDir)) {
83 | delete require.cache[require.resolve(item)]
84 | }
85 | })
86 | const mockRoutes = registerRoutes(app)
87 | mockRoutesLength = mockRoutes.mockRoutesLength
88 | mockStartIndex = mockRoutes.mockStartIndex
89 | } catch (error) {
90 | console.log(chalk.red(error))
91 | }
92 | }
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/src/views/sys/ad/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | {{ record.beginTime }} - {{ record.overTime }}
14 |
15 |
16 |
17 | 编辑
18 |
24 | 删除
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
109 |
--------------------------------------------------------------------------------
/src/components/Detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ userInfo.realName }}
18 |
19 |
20 | {{ userInfo.idNumber }}
21 |
22 |
23 | {{ userInfo.age }}
24 |
25 |
26 | {{ userInfo.sex === '0' ? '男' : '女' }}
27 |
28 |
29 | {{ userInfo.major }}
30 |
31 |
32 | {{ userInfo.experience }}
33 |
34 |
35 | {{ userInfo.detail }}
36 |
37 |
38 | {{ userInfo.cellPhone }}
39 |
40 |
41 | {{ userInfo.phone }}
42 |
43 |
44 | {{ userInfo.wechart }}
45 |
46 | {{ userInfo.qq }}
47 |
48 | {{ userInfo.email }}
49 |
50 |
51 | {{ userInfo.other }}
52 |
53 |
54 | {{ userInfo.website }}
55 |
56 |
57 |
58 |
59 |
60 |
110 |
--------------------------------------------------------------------------------
/src/views/vab/icon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
23 |
24 |
25 |
26 |
27 | {{ item }}
28 |
29 |
30 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
93 |
94 |
128 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {
3 | baseURL,
4 | contentType,
5 | debounce,
6 | requestTimeout,
7 | successCode,
8 | tokenName,
9 | } from '@/config'
10 | import store from '@/store'
11 | import qs from 'qs'
12 | import router from '@/router'
13 | import { isArray } from '@/utils/validate'
14 | import { message } from 'ant-design-vue'
15 |
16 | let loadingInstance
17 |
18 | /**
19 | * @author chuzhixin 1204505056@qq.com
20 | * @description 处理code异常
21 | * @param {*} code
22 | * @param {*} msg
23 | */
24 | const handleCode = (code, msg) => {
25 | switch (code) {
26 | case 401:
27 | message.error(msg || '登录失效')
28 | store.dispatch('user/resetAll').catch(() => {})
29 | break
30 | case 403:
31 | router.push({ path: '/401' }).catch(() => {})
32 | break
33 | default:
34 | message.error(msg || `后端接口${code}异常`)
35 | break
36 | }
37 | }
38 |
39 | /**
40 | * @author chuzhixin 1204505056@qq.com
41 | * @description axios初始化
42 | */
43 | const instance = axios.create({
44 | baseURL,
45 | timeout: requestTimeout,
46 | headers: {
47 | 'Content-Type': contentType,
48 | },
49 | })
50 |
51 | /**
52 | * @author chuzhixin 1204505056@qq.com
53 | * @description axios请求拦截器
54 | */
55 | instance.interceptors.request.use(
56 | (config) => {
57 | if (store.getters['user/accessToken'])
58 | config.headers[tokenName] = store.getters['user/accessToken']
59 | if (
60 | config.data &&
61 | config.headers['Content-Type'] ===
62 | 'application/x-www-form-urlencoded;charset=UTF-8'
63 | )
64 | config.data = qs.stringify(config.data)
65 | if (debounce.some((item) => config.url.includes(item))) {
66 | //这里写加载动画
67 | }
68 | return config
69 | },
70 | (error) => {
71 | return Promise.reject(error)
72 | }
73 | )
74 |
75 | /**
76 | * @author chuzhixin 1204505056@qq.com
77 | * @description axios响应拦截器
78 | */
79 | instance.interceptors.response.use(
80 | (response) => {
81 | if (loadingInstance) loadingInstance.close()
82 |
83 | const { data, config } = response
84 | const { msg } = data
85 | const code = Number(data.code)
86 | // 操作正常Code数组
87 | const codeVerificationArray = isArray(successCode)
88 | ? [...successCode]
89 | : [...[successCode]]
90 | // 是否操作正常
91 | if (codeVerificationArray.includes(code)) {
92 | return data
93 | } else {
94 | handleCode(code, msg)
95 | return Promise.reject(
96 | '请求异常拦截:' +
97 | JSON.stringify({ url: config.url, code, msg }) || 'Error'
98 | )
99 | }
100 | },
101 | (error) => {
102 | if (loadingInstance) loadingInstance.close()
103 | const { response, message } = error
104 | if (error.response && error.response.data) {
105 | const { status, data } = response
106 | handleCode(status, data.msg || message)
107 | return Promise.reject(error)
108 | } else {
109 | let { message } = error
110 | if (message === 'Network Error') {
111 | message = '后端接口连接异常'
112 | }
113 | if (message.includes('timeout')) {
114 | message = '后端接口请求超时'
115 | }
116 | if (message.includes('Request failed with status code')) {
117 | const code = message.substr(message.length - 3)
118 | message = '后端接口' + code + '异常'
119 | }
120 | message.error(message || `后端接口未知异常`)
121 | return Promise.reject(error)
122 | }
123 | }
124 | )
125 |
126 | export default instance
127 |
--------------------------------------------------------------------------------
/src/views/sys/advice/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | 牛人
14 | 商户
15 |
16 |
17 | 未处理
18 | 已处理
19 |
20 |
21 |
22 | 详情
23 |
29 | 标记处理
30 |
31 | 广播
32 |
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
141 |
--------------------------------------------------------------------------------
/src/views/sys/user/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
44 |
45 |
154 |
--------------------------------------------------------------------------------
/src/views/sys/customer/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
44 |
45 |
154 |
--------------------------------------------------------------------------------
/src/views/sys/report/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | {{ handleStatus(record.status) }}
14 |
15 |
16 |
17 | 详情
18 |
24 | 标记处理
25 |
26 | 广播
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
157 |
--------------------------------------------------------------------------------
/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 登录、获取用户信息、退出登录、清除accessToken逻辑,不建议修改
4 | */
5 | import { getUserInfo, login, logout } from '@/api/user'
6 | import {
7 | getAccessToken,
8 | removeAccessToken,
9 | setAccessToken,
10 | } from '@/utils/accessToken'
11 | import { title, tokenName } from '@/config'
12 | import { message, notification } from 'ant-design-vue'
13 |
14 | const state = () => ({
15 | accessToken: getAccessToken(),
16 | username: '',
17 | avatar: '',
18 | })
19 | const getters = {
20 | accessToken: (state) => state.accessToken,
21 | username: (state) => state.username,
22 | avatar: (state) => state.avatar,
23 | }
24 | const mutations = {
25 | /**
26 | * @author chuzhixin 1204505056@qq.com
27 | * @description 设置accessToken
28 | * @param {*} state
29 | * @param {*} accessToken
30 | */
31 | setAccessToken(state, accessToken) {
32 | state.accessToken = accessToken
33 | setAccessToken(accessToken)
34 | },
35 | /**
36 | * @author chuzhixin 1204505056@qq.com
37 | * @description 设置用户名
38 | * @param {*} state
39 | * @param {*} username
40 | */
41 | setUsername(state, username) {
42 | state.username = username
43 | },
44 | /**
45 | * @author chuzhixin 1204505056@qq.com
46 | * @description 设置头像
47 | * @param {*} state
48 | * @param {*} avatar
49 | */
50 | setAvatar(state, avatar) {
51 | state.avatar = avatar
52 | },
53 | }
54 | const actions = {
55 | /**
56 | * @author chuzhixin 1204505056@qq.com
57 | * @description 登录拦截放行时,设置虚拟角色
58 | * @param {*} { commit, dispatch }
59 | */
60 | setVirtualRoles({ commit, dispatch }) {
61 | dispatch('acl/setFull', true, { root: true })
62 | commit('setAvatar', 'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif')
63 | commit('setUsername', 'admin(未开启登录拦截)')
64 | },
65 | /**
66 | * @author chuzhixin 1204505056@qq.com
67 | * @description 登录
68 | * @param {*} { commit }
69 | * @param {*} userInfo
70 | */
71 | async login({ commit }, userInfo) {
72 | const { data } = await login(userInfo)
73 | const accessToken = data[tokenName]
74 | localStorage.setItem('username', data.name)
75 | if (accessToken) {
76 | commit('setAccessToken', accessToken)
77 | const hour = new Date().getHours()
78 | const thisTime =
79 | hour < 8
80 | ? '早上好'
81 | : hour <= 11
82 | ? '上午好'
83 | : hour <= 13
84 | ? '中午好'
85 | : hour < 18
86 | ? '下午好'
87 | : '晚上好'
88 | notification.open({
89 | message: `欢迎登录${title}`,
90 | description: `${thisTime}!`,
91 | })
92 | } else {
93 | message.error(`登录接口异常,未正确返回${tokenName}...`)
94 | }
95 | },
96 | /**
97 | * @author chuzhixin 1204505056@qq.com
98 | * @description 获取用户信息接口 这个接口非常非常重要,如果没有明确底层前逻辑禁止修改此方法,错误的修改可能造成整个框架无法正常使用
99 | * @param {*} { commit, dispatch, state }
100 | * @returns
101 | */
102 | async getUserInfo({ commit, state }) {
103 | const { data } = await getUserInfo(state.accessToken)
104 | if (!data) {
105 | message.error(`验证失败,请重新登录...`)
106 | return false
107 | }
108 | let { username, avatar } = data
109 | if (username) {
110 | commit('setUsername', username)
111 | commit('setAvatar', avatar)
112 | } else {
113 | message.error('用户信息接口异常')
114 | }
115 | },
116 |
117 | /**
118 | * @author chuzhixin 1204505056@qq.com
119 | * @description 退出登录
120 | * @param {*} { dispatch }
121 | */
122 | async logout({ dispatch }) {
123 | await logout(state.accessToken)
124 | await dispatch('resetAll')
125 | },
126 | /**
127 | * @author chuzhixin 1204505056@qq.com
128 | * @description 重置accessToken、roles、ability、router等
129 | * @param {*} { commit, dispatch }
130 | */
131 | async resetAll({ dispatch }) {
132 | await dispatch('setAccessToken', '')
133 | // await dispatch('acl/setFull', false, { root: true })
134 | // await dispatch('acl/setRole', [], { root: true })
135 | // await dispatch('acl/setAbility', [], { root: true })
136 | removeAccessToken()
137 | },
138 | /**
139 | * @author chuzhixin 1204505056@qq.com
140 | * @description 设置token
141 | */
142 | setAccessToken({ commit }, accessToken) {
143 | commit('setAccessToken', accessToken)
144 | },
145 | }
146 | export default { state, getters, mutations, actions }
147 |
--------------------------------------------------------------------------------
/src/store/modules/tagsBar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description tagsBar多标签页逻辑,前期借鉴了很多开源项目发现都有个共同的特点很繁琐并不符合框架设计的初衷,后来在github用户cyea的启发下完成了重构,请勿修改
4 | */
5 |
6 | const state = () => ({
7 | visitedRoutes: [],
8 | })
9 | const getters = {
10 | visitedRoutes: (state) => state.visitedRoutes,
11 | }
12 | const mutations = {
13 | /**
14 | * @author chuzhixin 1204505056@qq.com
15 | * @description 添加标签页
16 | * @param {*} state
17 | * @param {*} route
18 | * @returns
19 | */
20 | addVisitedRoute(state, route) {
21 | let target = state.visitedRoutes.find((item) => item.path === route.path)
22 | if (target) {
23 | if (route.fullPath !== target.fullPath) Object.assign(target, route)
24 | return
25 | }
26 | state.visitedRoutes.push(Object.assign({}, route))
27 | },
28 | /**
29 | * @author chuzhixin 1204505056@qq.com
30 | * @description 删除当前标签页
31 | * @param {*} state
32 | * @param {*} route
33 | * @returns
34 | */
35 | delVisitedRoute(state, route) {
36 | state.visitedRoutes.forEach((item, index) => {
37 | if (item.path === route.path) state.visitedRoutes.splice(index, 1)
38 | })
39 | },
40 | /**
41 | * @author chuzhixin 1204505056@qq.com
42 | * @description 删除当前标签页以外其它全部多标签页
43 | * @param {*} state
44 | * @param {*} route
45 | * @returns
46 | */
47 | delOthersVisitedRoutes(state, route) {
48 | state.visitedRoutes = state.visitedRoutes.filter(
49 | (item) => item.meta.affix || item.path === route.path
50 | )
51 | },
52 | /**
53 | * @author chuzhixin 1204505056@qq.com
54 | * @description 删除当前标签页左边全部多标签页
55 | * @param {*} state
56 | * @param {*} route
57 | * @returns
58 | */
59 | delLeftVisitedRoutes(state, route) {
60 | let index = state.visitedRoutes.length
61 | state.visitedRoutes = state.visitedRoutes.filter((item) => {
62 | if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
63 | return item.meta.affix || index <= state.visitedRoutes.indexOf(item)
64 | })
65 | },
66 | /**
67 | * @author chuzhixin 1204505056@qq.com
68 | * @description 删除当前标签页右边全部多标签页
69 | * @param {*} state
70 | * @param {*} route
71 | * @returns
72 | */
73 | delRightVisitedRoutes(state, route) {
74 | let index = state.visitedRoutes.length
75 | state.visitedRoutes = state.visitedRoutes.filter((item) => {
76 | if (item.name === route.name) index = state.visitedRoutes.indexOf(item)
77 | return item.meta.affix || index >= state.visitedRoutes.indexOf(item)
78 | })
79 | },
80 | /**
81 | * @author chuzhixin 1204505056@qq.com
82 | * @description 删除全部多标签页
83 | * @param {*} state
84 | * @param {*} route
85 | * @returns
86 | */
87 | delAllVisitedRoutes(state) {
88 | state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix)
89 | },
90 | }
91 | const actions = {
92 | /**
93 | * @author chuzhixin 1204505056@qq.com
94 | * @description 添加标签页
95 | * @param {*} { commit }
96 | * @param {*} route
97 | */
98 | addVisitedRoute({ commit }, route) {
99 | commit('addVisitedRoute', route)
100 | },
101 | /**
102 | * @author chuzhixin 1204505056@qq.com
103 | * @description 删除当前标签页
104 | * @param {*} { commit }
105 | * @param {*} route
106 | */
107 | delVisitedRoute({ commit }, route) {
108 | commit('delVisitedRoute', route)
109 | },
110 | /**
111 | * @author chuzhixin 1204505056@qq.com
112 | * @description 删除当前标签页以外其它全部多标签页
113 | * @param {*} { commit }
114 | * @param {*} route
115 | */
116 | delOthersVisitedRoutes({ commit }, route) {
117 | commit('delOthersVisitedRoutes', route)
118 | },
119 | /**
120 | * @author chuzhixin 1204505056@qq.com
121 | * @description 删除当前标签页左边全部多标签页
122 | * @param {*} { commit }
123 | * @param {*} route
124 | */
125 | delLeftVisitedRoutes({ commit }, route) {
126 | commit('delLeftVisitedRoutes', route)
127 | },
128 | /**
129 | * @author chuzhixin 1204505056@qq.com
130 | * @description 删除当前标签页右边全部多标签页
131 | * @param {*} { commit }
132 | * @param {*} route
133 | */
134 | delRightVisitedRoutes({ commit }, route) {
135 | commit('delRightVisitedRoutes', route)
136 | },
137 | /**
138 | * @author chuzhixin 1204505056@qq.com
139 | * @description 删除全部多标签页
140 | * @param {*} { commit }
141 | */
142 | delAllVisitedRoutes({ commit }) {
143 | commit('delAllVisitedRoutes')
144 | },
145 | }
146 | export default { state, getters, mutations, actions }
147 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description vue.config.js全局配置
4 | */
5 | const path = require('path')
6 | const {
7 | /* baseURL, */
8 | publicPath,
9 | assetsDir,
10 | outputDir,
11 | lintOnSave,
12 | transpileDependencies,
13 | title,
14 | abbreviation,
15 | devPort,
16 | providePlugin,
17 | build7z,
18 | donation,
19 | } = require('./src/config')
20 | const { webpackBarName, webpackBanner, donationConsole } = require('vab-config')
21 |
22 | if (donation) donationConsole()
23 | const { version, author } = require('./package.json')
24 | const Webpack = require('webpack')
25 | const WebpackBar = require('webpackbar')
26 | const FileManagerPlugin = require('filemanager-webpack-plugin')
27 | const dayjs = require('dayjs')
28 | const date = dayjs().format('YYYY_M_D')
29 | const time = dayjs().format('YYYY-M-D HH:mm:ss')
30 | process.env.VUE_APP_TITLE = title || 'genine-admin'
31 | process.env.VUE_APP_AUTHOR = author || 'chuzhixin'
32 | process.env.VUE_APP_UPDATE_TIME = time
33 | process.env.VUE_APP_VERSION = version
34 |
35 | const resolve = (dir) => {
36 | return path.join(__dirname, dir)
37 | }
38 |
39 | // const mockServer = () => {
40 | // if (process.env.NODE_ENV === 'development') {
41 | // return require('./mock/mockServer.js')
42 | // } else {
43 | // return ''
44 | // }
45 | // }
46 |
47 | module.exports = {
48 | publicPath,
49 | assetsDir,
50 | outputDir,
51 | lintOnSave,
52 | transpileDependencies,
53 | devServer: {
54 | hot: true,
55 | port: devPort,
56 | // open: true,
57 | noInfo: false,
58 | overlay: {
59 | warnings: false,
60 | errors: true,
61 | },
62 | // 注释掉的地方是前端配置代理访问后端的示例
63 | proxy: {
64 | '/api': {
65 | target: `http://39.108.168.143:6179/`,
66 | ws: true,
67 | changeOrigin: true,
68 | pathRewrite: {
69 | ["^/" + "api"]: "",
70 | },
71 | },
72 | },
73 | // after: mockServer(),
74 | },
75 | configureWebpack() {
76 | return {
77 | resolve: {
78 | alias: {
79 | '@': resolve('src'),
80 | '*': resolve(''),
81 | },
82 | },
83 | plugins: [
84 | new Webpack.ProvidePlugin(providePlugin),
85 | new WebpackBar({
86 | name: webpackBarName,
87 | }),
88 | ],
89 | }
90 | },
91 | chainWebpack(config) {
92 | config.resolve.symlinks(true)
93 | config.module.rule('svg').exclude.add(resolve('src/icon/remixIcon')).end()
94 |
95 | config.module
96 | .rule('remixIcon')
97 | .test(/\.svg$/)
98 | .include.add(resolve('src/icon/remixIcon'))
99 | .end()
100 | .use('svg-sprite-loader')
101 | .loader('svg-sprite-loader')
102 | .options({ symbolId: 'remix-icon-[name]' })
103 | .end()
104 |
105 | config.when(process.env.NODE_ENV === 'development', (config) => {
106 | config.devtool('source-map')
107 | })
108 |
109 | config.when(process.env.NODE_ENV !== 'development', (config) => {
110 | config.performance.set('hints', false)
111 | config.devtool('none')
112 | config.optimization.splitChunks({
113 | chunks: 'all',
114 | cacheGroups: {
115 | libs: {
116 | name: 'vue-admin',
117 | test: /[\\/]node_modules[\\/]/,
118 | priority: 10,
119 | chunks: 'initial',
120 | },
121 | },
122 | })
123 | config
124 | .plugin('banner')
125 | .use(Webpack.BannerPlugin, [`${webpackBanner}${time}`])
126 | .end()
127 | config.module
128 | .rule('images')
129 | .use('image-webpack-loader')
130 | .loader('image-webpack-loader')
131 | .options({
132 | bypassOnDebug: true,
133 | })
134 | .end()
135 | })
136 |
137 | if (build7z) {
138 | config.when(process.env.NODE_ENV === 'production', (config) => {
139 | config
140 | .plugin('fileManager')
141 | .use(FileManagerPlugin, [
142 | {
143 | onEnd: {
144 | delete: [`./${outputDir}/video`, `./${outputDir}/data`],
145 | archive: [
146 | {
147 | source: `./${outputDir}`,
148 | destination: `./${outputDir}/${abbreviation}_${outputDir}_${date}.7z`,
149 | },
150 | ],
151 | },
152 | },
153 | ])
154 | .end()
155 | })
156 | }
157 | },
158 | runtimeCompiler: true,
159 | productionSourceMap: false,
160 | css: {
161 | requireModuleExtension: true,
162 | sourceMap: true,
163 | loaderOptions: {
164 | less: {
165 | lessOptions: {
166 | javascriptEnabled: true,
167 | modifyVars: {
168 | 'vab-color-blue': '#1890ff',
169 | 'vab-margin': '20px',
170 | 'vab-padding': '20px',
171 | 'vab-header-height': '65px',
172 | },
173 | },
174 | },
175 | },
176 | },
177 | }
178 |
--------------------------------------------------------------------------------
/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 所有全局配置的状态管理,如无必要请勿修改
4 | */
5 | import defaultSettings from '@/config'
6 | import { isJson } from '@/utils/validate'
7 |
8 | const {
9 | logo,
10 | title,
11 | layout,
12 | header,
13 | themeName,
14 | i18n,
15 | showLanguage,
16 | showProgressBar,
17 | showRefresh,
18 | showSearch,
19 | showTheme,
20 | showTagsBar,
21 | showNotice,
22 | showFullScreen,
23 | } = defaultSettings
24 |
25 | const getLocalStorage = (key) => {
26 | const value = localStorage.getItem(key)
27 | if (isJson(value)) {
28 | return JSON.parse(value)
29 | } else {
30 | return false
31 | }
32 | }
33 |
34 | const theme = getLocalStorage('vue-admin-beautiful-pro-theme')
35 | const { collapse } = getLocalStorage('vue-admin-beautiful-pro-collapse')
36 | const { language } = getLocalStorage('vue-admin-beautiful-pro-language')
37 | const toggleBoolean = (key) => {
38 | return typeof theme[key] !== 'undefined' ? theme[key] : key
39 | }
40 |
41 | const state = () => ({
42 | logo,
43 | title,
44 | collapse,
45 | themeName: theme.themeName || themeName,
46 | layout: theme.layout || layout,
47 | header: theme.header || header,
48 | device: 'desktop',
49 | language: language || i18n,
50 | showLanguage: toggleBoolean(showLanguage),
51 | showProgressBar: toggleBoolean(showProgressBar),
52 | showRefresh: toggleBoolean(showRefresh),
53 | showSearch: toggleBoolean(showSearch),
54 | showTheme: toggleBoolean(showTheme),
55 | showTagsBar: toggleBoolean(showTagsBar),
56 | showNotice: toggleBoolean(showNotice),
57 | showFullScreen: toggleBoolean(showFullScreen),
58 | })
59 | const getters = {
60 | collapse: (state) => state.collapse,
61 | device: (state) => state.device,
62 | header: (state) => state.header,
63 | language: (state) => state.language,
64 | layout: (state) => state.layout,
65 | logo: (state) => state.logo,
66 | title: (state) => state.title,
67 | showLanguage: (state) => state.showLanguage,
68 | showProgressBar: (state) => state.showProgressBar,
69 | showRefresh: (state) => state.showRefresh,
70 | showSearch: (state) => state.showSearch,
71 | showTheme: (state) => state.showTheme,
72 | showTagsBar: (state) => state.showTagsBar,
73 | showNotice: (state) => state.showNotice,
74 | showFullScreen: (state) => state.showFullScreen,
75 | themeName: (state) => state.themeName,
76 | }
77 | const mutations = {
78 | toggleCollapse(state) {
79 | state.collapse = !state.collapse
80 | localStorage.setItem(
81 | 'vue-admin-beautiful-pro-collapse',
82 | `{"collapse":${state.collapse}}`
83 | )
84 | },
85 | toggleDevice(state, device) {
86 | state.device = device
87 | },
88 | changeHeader(state, header) {
89 | state.header = header
90 | },
91 | changeLayout(state, layout) {
92 | state.layout = layout
93 | },
94 | handleShowLanguage(state, showLanguage) {
95 | state.showLanguage = showLanguage
96 | },
97 | handleShowProgressBar(state, showProgressBar) {
98 | state.showProgressBar = showProgressBar
99 | },
100 | handleShowRefresh(state, showRefresh) {
101 | state.showRefresh = showRefresh
102 | },
103 | handleShowSearch(state, showSearch) {
104 | state.showSearch = showSearch
105 | },
106 | handleShowTheme(state, showTheme) {
107 | state.showTheme = showTheme
108 | },
109 | handleShowTagsBar(state, showTagsBar) {
110 | state.showTagsBar = showTagsBar
111 | },
112 | handleShowNotice(state, showNotice) {
113 | state.showNotice = showNotice
114 | },
115 | handleShowFullScreen(state, showFullScreen) {
116 | state.showFullScreen = showFullScreen
117 | },
118 | openSideBar(state) {
119 | state.collapse = false
120 | },
121 | foldSideBar(state) {
122 | state.collapse = true
123 | },
124 | changeLanguage(state, language) {
125 | localStorage.setItem(
126 | 'vue-admin-beautiful-pro-language',
127 | `{"language":"${language}"}`
128 | )
129 | state.language = language
130 | },
131 | }
132 | const actions = {
133 | toggleCollapse({ commit }) {
134 | commit('toggleCollapse')
135 | },
136 | toggleDevice({ commit }, device) {
137 | commit('toggleDevice', device)
138 | },
139 | changeHeader({ commit }, header) {
140 | commit('changeHeader', header)
141 | },
142 | changeLayout({ commit }, layout) {
143 | commit('changeLayout', layout)
144 | },
145 | handleShowLanguage: ({ commit }, showLanguage) => {
146 | commit('handleShowLanguage', showLanguage)
147 | },
148 | handleShowProgressBar: ({ commit }, showProgressBar) => {
149 | commit('handleShowProgressBar', showProgressBar)
150 | },
151 | handleShowRefresh: ({ commit }, showRefresh) => {
152 | commit('handleShowRefresh', showRefresh)
153 | },
154 | handleShowSearch: ({ commit }, showSearch) => {
155 | commit('handleShowSearch', showSearch)
156 | },
157 | handleShowTheme: ({ commit }, showTheme) => {
158 | commit('handleShowTheme', showTheme)
159 | },
160 | handleShowTagsBar({ commit }, showTagsBar) {
161 | commit('handleShowTagsBar', showTagsBar)
162 | },
163 | handleShowNotice: ({ commit }, showNotice) => {
164 | commit('handleShowNotice', showNotice)
165 | },
166 | handleShowFullScreen: ({ commit }, showFullScreen) => {
167 | commit('handleShowFullScreen', showFullScreen)
168 | },
169 | openSideBar({ commit }) {
170 | commit('openSideBar')
171 | },
172 | foldSideBar({ commit }) {
173 | commit('foldSideBar')
174 | },
175 | changeLanguage: ({ commit }, language) => {
176 | commit('changeLanguage', language)
177 | },
178 | }
179 | export default { state, getters, mutations, actions }
180 |
--------------------------------------------------------------------------------
/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |

12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
69 |
70 |
212 |
--------------------------------------------------------------------------------
/src/views/403.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |

8 |

12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
70 |
71 |
213 |
--------------------------------------------------------------------------------
/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
16 |
17 |
27 |
28 |
32 |
51 |
52 |
53 |
54 |
55 |
56 |
136 |
228 |
--------------------------------------------------------------------------------
/src/layout/vab-tabs/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 |
24 | 关闭其他
25 |
26 |
27 | 关闭左侧
28 |
29 |
30 | 关闭右侧
31 |
32 |
33 | 关闭全部
34 |
35 |
36 |
37 |
38 | 更多
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
178 |
220 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from 'vue-router'
2 | import Layout from '@/layout'
3 |
4 | export const constantRoutes = [
5 | {
6 | path: '/login',
7 | component: () => import('@/views/login'),
8 | hidden: true,
9 | },
10 | {
11 | path: '/403',
12 | name: '403',
13 | component: () => import('@/views/403'),
14 | hidden: true,
15 | },
16 | {
17 | path: '/404',
18 | name: '404',
19 | component: () => import('@/views/404'),
20 | hidden: true,
21 | },
22 | ]
23 | export const asyncRoutes = [
24 | {
25 | path: '/',
26 | component: Layout,
27 | redirect: '/index',
28 | meta: {
29 | title: '首页',
30 | icon: 'home-4-line',
31 | affix: true,
32 | },
33 | children: [
34 | {
35 | path: 'index',
36 | name: 'Index',
37 | component: () => import('@/views/index'),
38 | meta: {
39 | title: '首页',
40 | icon: 'home-4-line',
41 | affix: true,
42 | },
43 | },
44 | ],
45 | },
46 | {
47 | path: '/',
48 | component: Layout,
49 | redirect: '/user',
50 | meta: {
51 | title: '用户管理',
52 | icon: 'user-line',
53 | affix: true,
54 | },
55 | children: [
56 | {
57 | path: 'user',
58 | name: 'User',
59 | component: () => import('@/views/sys/user'),
60 | meta: {
61 | title: '用户管理',
62 | icon: 'user-line',
63 | affix: true,
64 | },
65 | },
66 | ],
67 | },
68 | {
69 | path: '/',
70 | component: Layout,
71 | redirect: '/customer',
72 | meta: {
73 | title: '商户管理',
74 | icon: 'group-line',
75 | affix: true,
76 | },
77 | children: [
78 | {
79 | path: 'customer',
80 | name: 'Customer',
81 | component: () => import('@/views/sys/customer'),
82 | meta: {
83 | title: '商户管理',
84 | icon: 'group-line',
85 | affix: true,
86 | },
87 | },
88 | ],
89 | },
90 | {
91 | path: '/',
92 | component: Layout,
93 | redirect: '/advice',
94 | meta: {
95 | title: '建议管理',
96 | icon: 'file-text-line',
97 | affix: true,
98 | },
99 | children: [
100 | {
101 | path: 'advice',
102 | name: 'Advice',
103 | component: () => import('@/views/sys/advice'),
104 | meta: {
105 | title: '建议管理',
106 | icon: 'file-text-line',
107 | affix: true,
108 | },
109 | },
110 | ],
111 | },
112 | {
113 | path: '/',
114 | component: Layout,
115 | redirect: '/report',
116 | meta: {
117 | title: '举报管理',
118 | icon: 'phone-line',
119 | affix: true,
120 | },
121 | children: [
122 | {
123 | path: 'report',
124 | name: 'Report',
125 | component: () => import('@/views/sys/report'),
126 | meta: {
127 | title: '举报管理',
128 | icon: 'phone-line',
129 | affix: true,
130 | },
131 | },
132 | ],
133 | },
134 | {
135 | path: '/',
136 | component: Layout,
137 | redirect: '/advertisement',
138 | meta: {
139 | title: '广告管理',
140 | icon: 'advertisement-line',
141 | affix: true,
142 | },
143 | children: [
144 | {
145 | path: 'advertisement',
146 | name: 'Advertisement',
147 | component: () => import('@/views/sys/ad'),
148 | meta: {
149 | title: '广告管理',
150 | icon: 'advertisement-line',
151 | affix: true,
152 | },
153 | },
154 | ],
155 | },
156 | {
157 | path: '/',
158 | component: Layout,
159 | redirect: '/word',
160 | meta: {
161 | title: '文字管理',
162 | icon: 'text',
163 | affix: true,
164 | },
165 | children: [
166 | {
167 | path: 'word',
168 | name: 'Word',
169 | component: () => import('@/views/sys/word'),
170 | meta: {
171 | title: '文字管理',
172 | icon: 'text',
173 | affix: true,
174 | },
175 | },
176 | ],
177 | },
178 | {
179 | path: '/',
180 | component: Layout,
181 | redirect: '/business',
182 | meta: {
183 | title: '业务需求管理',
184 | icon: 'attachment-line',
185 | affix: true,
186 | },
187 | children: [
188 | {
189 | path: 'business',
190 | name: 'Business',
191 | component: () => import('@/views/sys/business'),
192 | meta: {
193 | title: '业务需求管理',
194 | icon: 'attachment-line',
195 | affix: true,
196 | },
197 | },
198 | ],
199 | },
200 | {
201 | path: '/',
202 | component: Layout,
203 | redirect: '/classify',
204 | meta: {
205 | title: '需求类别管理',
206 | icon: 'stack-line',
207 | affix: true,
208 | },
209 | children: [
210 | {
211 | path: 'classify',
212 | name: 'Classify',
213 | component: () => import('@/views/sys/classify'),
214 | meta: {
215 | title: '需求类别管理',
216 | icon: 'stack-line',
217 | affix: true,
218 | },
219 | },
220 | ],
221 | },
222 | // {
223 | // path: '/vab',
224 | // component: Layout,
225 | // redirect: '/vab/table',
226 | // alwaysShow: true,
227 | // meta: {
228 | // title: '组件',
229 | // icon: 'apps-line',
230 | // },
231 | // children: [
232 | // {
233 | // path: 'table',
234 | // name: 'Table',
235 | // component: () => import('@/views/vab/table'),
236 | // meta: {
237 | // title: '表格',
238 | // icon: 'table-2',
239 | // },
240 | // },
241 | // {
242 | // path: 'icon',
243 | // name: 'Icon',
244 | // component: () => import('@/views/vab/icon'),
245 | // meta: {
246 | // title: '图标',
247 | // icon: 'remixicon-line',
248 | // },
249 | // },
250 | // ],
251 | // },
252 | // {
253 | // path: '/test',
254 | // component: Layout,
255 | // redirect: '/test/test',
256 | // meta: {
257 | // title: '动态路由测试',
258 | // icon: 'test-tube-line',
259 | // },
260 | // children: [
261 | // {
262 | // path: 'test',
263 | // name: 'Test',
264 | // component: () => import('@/views/test'),
265 | // meta: {
266 | // title: '动态路由测试',
267 | // icon: 'test-tube-line',
268 | // },
269 | // },
270 | // ],
271 | // },
272 | // {
273 | // path: '/error',
274 | // name: 'Error',
275 | // component: Layout,
276 | // redirect: '/error/403',
277 | // meta: {
278 | // title: '错误页',
279 | // icon: 'error-warning-line',
280 | // },
281 | // children: [
282 | // {
283 | // path: '403',
284 | // name: 'Error403',
285 | // component: () => import('@/views/403'),
286 | // meta: {
287 | // title: '403',
288 | // icon: 'error-warning-line',
289 | // },
290 | // },
291 | // {
292 | // path: '404',
293 | // name: 'Error404',
294 | // component: () => import('@/views/404'),
295 | // meta: {
296 | // title: '404',
297 | // icon: 'error-warning-line',
298 | // },
299 | // },
300 | // ],
301 | // },
302 | {
303 | path: '/*',
304 | redirect: '/404',
305 | hidden: true,
306 | },
307 | ]
308 | const router = createRouter({
309 | history: createWebHashHistory(),
310 | routes: [...constantRoutes, ...asyncRoutes],
311 | })
312 |
313 | export default router
314 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import { devDependencies } from '../../package.json'
2 | if (!devDependencies['vab-config']) document.body.innerHTML = ''
3 | /**
4 | * @author chuzhixin 1204505056@qq.com
5 | * @description 格式化时间
6 | * @param time
7 | * @param cFormat
8 | * @returns {string|null}
9 | */
10 | export function parseTime(time, cFormat) {
11 | if (arguments.length === 0) {
12 | return null
13 | }
14 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
15 | let date
16 | if (typeof time === 'object') {
17 | date = time
18 | } else {
19 | if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
20 | time = parseInt(time)
21 | }
22 | if (typeof time === 'number' && time.toString().length === 10) {
23 | time = time * 1000
24 | }
25 | date = new Date(time)
26 | }
27 | const formatObj = {
28 | y: date.getFullYear(),
29 | m: date.getMonth() + 1,
30 | d: date.getDate(),
31 | h: date.getHours(),
32 | i: date.getMinutes(),
33 | s: date.getSeconds(),
34 | a: date.getDay(),
35 | }
36 | return format.replace(/{([ymdhisa])+}/g, (result, key) => {
37 | let value = formatObj[key]
38 | if (key === 'a') {
39 | return ['日', '一', '二', '三', '四', '五', '六'][value]
40 | }
41 | if (result.length > 0 && value < 10) {
42 | value = '0' + value
43 | }
44 | return value || 0
45 | })
46 | }
47 |
48 | /**
49 | * @author chuzhixin 1204505056@qq.com
50 | * @description 格式化时间
51 | * @param time
52 | * @param option
53 | * @returns {string}
54 | */
55 | export function formatTime(time, option) {
56 | if (('' + time).length === 10) {
57 | time = parseInt(time) * 1000
58 | } else {
59 | time = +time
60 | }
61 | const d = new Date(time)
62 | const now = Date.now()
63 |
64 | const diff = (now - d) / 1000
65 |
66 | if (diff < 30) {
67 | return '刚刚'
68 | } else if (diff < 3600) {
69 | // less 1 hour
70 | return Math.ceil(diff / 60) + '分钟前'
71 | } else if (diff < 3600 * 24) {
72 | return Math.ceil(diff / 3600) + '小时前'
73 | } else if (diff < 3600 * 24 * 2) {
74 | return '1天前'
75 | }
76 | if (option) {
77 | return parseTime(time, option)
78 | } else {
79 | return (
80 | d.getMonth() +
81 | 1 +
82 | '月' +
83 | d.getDate() +
84 | '日' +
85 | d.getHours() +
86 | '时' +
87 | d.getMinutes() +
88 | '分'
89 | )
90 | }
91 | }
92 |
93 | /**
94 | * @author chuzhixin 1204505056@qq.com
95 | * @description 将url请求参数转为json格式
96 | * @param url
97 | * @returns {{}|any}
98 | */
99 | export function paramObj(url) {
100 | const search = url.split('?')[1]
101 | if (!search) {
102 | return {}
103 | }
104 | return JSON.parse(
105 | '{"' +
106 | decodeURIComponent(search)
107 | .replace(/"/g, '\\"')
108 | .replace(/&/g, '","')
109 | .replace(/=/g, '":"')
110 | .replace(/\+/g, ' ') +
111 | '"}'
112 | )
113 | }
114 |
115 | /**
116 | * @author chuzhixin 1204505056@qq.com
117 | * @description 父子关系的数组转换成树形结构数据
118 | * @param data
119 | * @returns {*}
120 | */
121 | export function translateDataToTree(data) {
122 | const parent = data.filter(
123 | (value) => value.parentId === 'undefined' || value.parentId == null
124 | )
125 | const children = data.filter(
126 | (value) => value.parentId !== 'undefined' && value.parentId != null
127 | )
128 | const translator = (parent, children) => {
129 | parent.forEach((parent) => {
130 | children.forEach((current, index) => {
131 | if (current.parentId === parent.id) {
132 | const temp = JSON.parse(JSON.stringify(children))
133 | temp.splice(index, 1)
134 | translator([current], temp)
135 | typeof parent.children !== 'undefined'
136 | ? parent.children.push(current)
137 | : (parent.children = [current])
138 | }
139 | })
140 | })
141 | }
142 | translator(parent, children)
143 | return parent
144 | }
145 |
146 | /**
147 | * @author chuzhixin 1204505056@qq.com
148 | * @description 树形结构数据转换成父子关系的数组
149 | * @param data
150 | * @returns {[]}
151 | */
152 | export function translateTreeToData(data) {
153 | const result = []
154 | data.forEach((item) => {
155 | const loop = (data) => {
156 | result.push({
157 | id: data.id,
158 | name: data.name,
159 | parentId: data.parentId,
160 | })
161 | const child = data.children
162 | if (child) {
163 | for (let i = 0; i < child.length; i++) {
164 | loop(child[i])
165 | }
166 | }
167 | }
168 | loop(item)
169 | })
170 | return result
171 | }
172 |
173 | /**
174 | * @author chuzhixin 1204505056@qq.com
175 | * @description 10位时间戳转换
176 | * @param time
177 | * @returns {string}
178 | */
179 | export function tenBitTimestamp(time) {
180 | const date = new Date(time * 1000)
181 | const y = date.getFullYear()
182 | let m = date.getMonth() + 1
183 | m = m < 10 ? '' + m : m
184 | let d = date.getDate()
185 | d = d < 10 ? '' + d : d
186 | let h = date.getHours()
187 | h = h < 10 ? '0' + h : h
188 | let minute = date.getMinutes()
189 | let second = date.getSeconds()
190 | minute = minute < 10 ? '0' + minute : minute
191 | second = second < 10 ? '0' + second : second
192 | return y + '年' + m + '月' + d + '日 ' + h + ':' + minute + ':' + second //组合
193 | }
194 |
195 | /**
196 | * @author chuzhixin 1204505056@qq.com
197 | * @description 13位时间戳转换
198 | * @param time
199 | * @returns {string}
200 | */
201 | export function thirteenBitTimestamp(time) {
202 | const date = new Date(time / 1)
203 | const y = date.getFullYear()
204 | let m = date.getMonth() + 1
205 | m = m < 10 ? '' + m : m
206 | let d = date.getDate()
207 | d = d < 10 ? '' + d : d
208 | let h = date.getHours()
209 | h = h < 10 ? '0' + h : h
210 | let minute = date.getMinutes()
211 | let second = date.getSeconds()
212 | minute = minute < 10 ? '0' + minute : minute
213 | second = second < 10 ? '0' + second : second
214 | return y + '年' + m + '月' + d + '日 ' + h + ':' + minute + ':' + second //组合
215 | }
216 |
217 | /**
218 | * @author chuzhixin 1204505056@qq.com
219 | * @description 获取随机id
220 | * @param length
221 | * @returns {string}
222 | */
223 | export function uuid(length = 32) {
224 | const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
225 | let str = ''
226 | for (let i = 0; i < length; i++) {
227 | str += num.charAt(Math.floor(Math.random() * num.length))
228 | }
229 | return str
230 | }
231 |
232 | /**
233 | * @author chuzhixin 1204505056@qq.com
234 | * @description m到n的随机数
235 | * @param m
236 | * @param n
237 | * @returns {number}
238 | */
239 | export function random(m, n) {
240 | return Math.floor(Math.random() * (m - n) + n)
241 | }
242 |
243 | /**
244 | * @author chuzhixin 1204505056@qq.com
245 | * @description addEventListener
246 | * @type {function(...[*]=)}
247 | */
248 | export const on = (function () {
249 | return function (element, event, handler, useCapture = false) {
250 | if (element && event && handler) {
251 | element.addEventListener(event, handler, useCapture)
252 | }
253 | }
254 | })()
255 |
256 | /**
257 | * @author chuzhixin 1204505056@qq.com
258 | * @description removeEventListener
259 | * @type {function(...[*]=)}
260 | */
261 | export const off = (function () {
262 | return function (element, event, handler, useCapture = false) {
263 | if (element && event) {
264 | element.removeEventListener(event, handler, useCapture)
265 | }
266 | }
267 | })()
268 |
269 |
270 | // 处理page参数
271 | export const handlePage = (data) => {
272 | return {
273 | ...data,
274 | pageNo: data.current,
275 | pageSize: data.pageSize,
276 | }
277 | }
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author chuzhixin 1204505056@qq.com
3 | * @description 判读是否为外链
4 | * @param path
5 | * @returns {boolean}
6 | */
7 | export function isExternal(path) {
8 | return /^(https?:|mailto:|tel:)/.test(path)
9 | }
10 |
11 | /**
12 | * @author chuzhixin 1204505056@qq.com
13 | * @description 校验密码是否小于6位
14 | * @param value
15 | * @returns {boolean}
16 | */
17 | export function isPassword(value) {
18 | return value.length >= 6
19 | }
20 |
21 | /**
22 | * @author chuzhixin 1204505056@qq.com
23 | * @description 判断是否为数字
24 | * @param value
25 | * @returns {boolean}
26 | */
27 | export function isNumber(value) {
28 | const reg = /^[0-9]*$/
29 | return reg.test(value)
30 | }
31 |
32 | /**
33 | * @author chuzhixin 1204505056@qq.com
34 | * @description 判断是否是名称
35 | * @param value
36 | * @returns {boolean}
37 | */
38 | export function isName(value) {
39 | const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
40 | return reg.test(value)
41 | }
42 |
43 | /**
44 | * @author chuzhixin 1204505056@qq.com
45 | * @description 判断是否为IP
46 | * @param ip
47 | * @returns {boolean}
48 | */
49 | export function isIP(ip) {
50 | const reg =
51 | /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
52 | return reg.test(ip)
53 | }
54 |
55 | /**
56 | * @author chuzhixin 1204505056@qq.com
57 | * @description 判断是否是传统网站
58 | * @param url
59 | * @returns {boolean}
60 | */
61 | export function isUrl(url) {
62 | const reg =
63 | /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
64 | return reg.test(url)
65 | }
66 |
67 | /**
68 | * @author chuzhixin 1204505056@qq.com
69 | * @description 判断是否是小写字母
70 | * @param value
71 | * @returns {boolean}
72 | */
73 | export function isLowerCase(value) {
74 | const reg = /^[a-z]+$/
75 | return reg.test(value)
76 | }
77 |
78 | /**
79 | * @author chuzhixin 1204505056@qq.com
80 | * @description 判断是否是大写字母
81 | * @param value
82 | * @returns {boolean}
83 | */
84 | export function isUpperCase(value) {
85 | const reg = /^[A-Z]+$/
86 | return reg.test(value)
87 | }
88 |
89 | /**
90 | * @author chuzhixin 1204505056@qq.com
91 | * @description 判断是否是大写字母开头
92 | * @param value
93 | * @returns {boolean}
94 | */
95 | export function isAlphabets(value) {
96 | const reg = /^[A-Za-z]+$/
97 | return reg.test(value)
98 | }
99 |
100 | /**
101 | * @author chuzhixin 1204505056@qq.com
102 | * @description 判断是否是字符串
103 | * @param value
104 | * @returns {boolean}
105 | */
106 | export function isString(value) {
107 | return typeof value === 'string' || value instanceof String
108 | }
109 |
110 | /**
111 | * @author chuzhixin 1204505056@qq.com
112 | * @description 判断是否是数组
113 | * @param arg
114 | * @returns {arg is any[]|boolean}
115 | */
116 | export function isArray(arg) {
117 | if (typeof Array.isArray === 'undefined') {
118 | return Object.prototype.toString.call(arg) === '[object Array]'
119 | }
120 | return Array.isArray(arg)
121 | }
122 |
123 | /**
124 | * @author chuzhixin 1204505056@qq.com
125 | * @description 判断是否是端口号
126 | * @param value
127 | * @returns {boolean}
128 | */
129 | export function isPort(value) {
130 | const reg =
131 | /^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
132 | return reg.test(value)
133 | }
134 |
135 | /**
136 | * @author chuzhixin 1204505056@qq.com
137 | * @description 判断是否是手机号
138 | * @param value
139 | * @returns {boolean}
140 | */
141 | export function isPhone(value) {
142 | const reg = /^1\d{10}$/
143 | return reg.test(value)
144 | }
145 |
146 | /**
147 | * @author chuzhixin 1204505056@qq.com
148 | * @description 判断是否是身份证号(第二代)
149 | * @param value
150 | * @returns {boolean}
151 | */
152 | export function isIdCard(value) {
153 | const reg =
154 | /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
155 | return reg.test(value)
156 | }
157 |
158 | /**
159 | * @author chuzhixin 1204505056@qq.com
160 | * @description 判断是否是邮箱
161 | * @param value
162 | * @returns {boolean}
163 | */
164 | export function isEmail(value) {
165 | const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
166 | return reg.test(value)
167 | }
168 |
169 | /**
170 | * @author chuzhixin 1204505056@qq.com
171 | * @description 判断是否中文
172 | * @param value
173 | * @returns {boolean}
174 | */
175 | export function isChina(value) {
176 | const reg = /^[\u4E00-\u9FA5]{2,4}$/
177 | return reg.test(value)
178 | }
179 |
180 | /**
181 | * @author chuzhixin 1204505056@qq.com
182 | * @description 判断是否为空
183 | * @param value
184 | * @returns {boolean}
185 | */
186 | export function isBlank(value) {
187 | return (
188 | value == null ||
189 | false ||
190 | value === '' ||
191 | value.trim() === '' ||
192 | value.toLocaleLowerCase().trim() === 'null'
193 | )
194 | }
195 |
196 | /**
197 | * @author chuzhixin 1204505056@qq.com
198 | * @description 判断是否为固话
199 | * @param value
200 | * @returns {boolean}
201 | */
202 | export function isTel(value) {
203 | const reg =
204 | /^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})([- ])?)?([0-9]{7,8})(([- 转])*([0-9]{1,4}))?$/
205 | return reg.test(value)
206 | }
207 |
208 | /**
209 | * @author chuzhixin 1204505056@qq.com
210 | * @description 判断是否为数字且最多两位小数
211 | * @param value
212 | * @returns {boolean}
213 | */
214 | export function isNum(value) {
215 | const reg = /^\d+(\.\d{1,2})?$/
216 | return reg.test(value)
217 | }
218 |
219 | /**
220 | * @author chuzhixin 1204505056@qq.com
221 | * @description 判断经度 -180.0~+180.0(整数部分为0~180,必须输入1到5位小数)
222 | * @param value
223 | * @returns {boolean}
224 | */
225 | export function isLongitude(value) {
226 | const reg = /^[-|+]?(0?\d{1,2}\.\d{1,5}|1[0-7]?\d{1}\.\d{1,5}|180\.0{1,5})$/
227 | return reg.test(value)
228 | }
229 |
230 | /**
231 | * @author chuzhixin 1204505056@qq.com
232 | * @description 判断纬度 -90.0~+90.0(整数部分为0~90,必须输入1到5位小数)
233 | * @param value
234 | * @returns {boolean}
235 | */
236 | export function isLatitude(value) {
237 | const reg = /^[-|+]?([0-8]?\d{1}\.\d{1,5}|90\.0{1,5})$/
238 | return reg.test(value)
239 | }
240 |
241 | /**
242 | * @author chuzhixin 1204505056@qq.com
243 | * @description rtsp校验,只要有rtsp://
244 | * @param value
245 | * @returns {boolean}
246 | */
247 | export function isRTSP(value) {
248 | const reg =
249 | /^rtsp:\/\/([a-z]{0,10}:.{0,10}@)?(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
250 | const reg1 =
251 | /^rtsp:\/\/([a-z]{0,10}:.{0,10}@)?(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]):[0-9]{1,5}/
252 | const reg2 =
253 | /^rtsp:\/\/([a-z]{0,10}:.{0,10}@)?(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\//
254 | return reg.test(value) || reg1.test(value) || reg2.test(value)
255 | }
256 |
257 | /**
258 | * @author chuzhixin 1204505056@qq.com
259 | * @description 判断是否为json
260 | * @param value
261 | * @returns {boolean}
262 | */
263 | export function isJson(value) {
264 | if (typeof value == 'string') {
265 | try {
266 | var obj = JSON.parse(value)
267 | if (typeof obj == 'object' && obj) {
268 | return true
269 | } else {
270 | return false
271 | }
272 | } catch (e) {
273 | return false
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
90 |
91 |
92 |
93 | 基于vue{{ dependencies['vue'] }}
94 | + ant-design-vue
95 | {{ dependencies['ant-design-vue'] }}开发
96 |
97 |
98 |
99 |
187 |
243 |
--------------------------------------------------------------------------------
/src/vab/styles/normalize.less:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15;
13 | /* 1 */
14 | -webkit-text-size-adjust: 100%;
15 | /* 2 */
16 | }
17 |
18 | /* Sections
19 | ========================================================================== */
20 |
21 | /**
22 | * Remove the margin in all browsers.
23 | */
24 |
25 | body {
26 | margin: 0;
27 | }
28 |
29 | /**
30 | * Render the `main` element consistently in IE.
31 | */
32 |
33 | main {
34 | display: block;
35 | }
36 |
37 | /**
38 | * Correct the font size and margin on `h1` elements within `section` and
39 | * `article` contexts in Chrome, Firefox, and Safari.
40 | */
41 |
42 | h1 {
43 | margin: 0.67em 0;
44 | font-size: 2em;
45 | }
46 |
47 | /* Grouping content
48 | ========================================================================== */
49 |
50 | /**
51 | * 1. Add the correct box sizing in Firefox.
52 | * 2. Show the overflow in Edge and IE.
53 | */
54 |
55 | hr {
56 | box-sizing: content-box;
57 | /* 1 */
58 | height: 0;
59 | /* 1 */
60 | overflow: visible;
61 | /* 2 */
62 | }
63 |
64 | /**
65 | * 1. Correct the inheritance and scaling of font size in all browsers.
66 | * 2. Correct the odd `em` font sizing in all browsers.
67 | */
68 |
69 | pre {
70 | font-family: monospace;
71 | /* 1 */
72 | font-size: 1em;
73 | /* 2 */
74 | }
75 |
76 | /* Text-level semantics
77 | ========================================================================== */
78 |
79 | /**
80 | * Remove the gray background on active links in IE 10.
81 | */
82 |
83 | a {
84 | background-color: transparent;
85 | }
86 |
87 | /**
88 | * 1. Remove the bottom border in Chrome 57-
89 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
90 | */
91 |
92 | abbr[title] {
93 | text-decoration: underline;
94 | /* 2 */
95 | text-decoration: underline dotted;
96 | /* 2 */
97 | border-bottom: none;
98 | /* 1 */
99 | }
100 |
101 | /**
102 | * Add the correct font weight in Chrome, Edge, and Safari.
103 | */
104 |
105 | b,
106 | strong {
107 | font-weight: bolder;
108 | }
109 |
110 | /**
111 | * 1. Correct the inheritance and scaling of font size in all browsers.
112 | * 2. Correct the odd `em` font sizing in all browsers.
113 | */
114 |
115 | code,
116 | kbd,
117 | samp {
118 | font-family: monospace;
119 | /* 1 */
120 | font-size: 1em;
121 | /* 2 */
122 | }
123 |
124 | /**
125 | * Add the correct font size in all browsers.
126 | */
127 |
128 | small {
129 | font-size: 80%;
130 | }
131 |
132 | /**
133 | * Prevent `sub` and `sup` elements from affecting the line height in
134 | * all browsers.
135 | */
136 |
137 | sub,
138 | sup {
139 | position: relative;
140 | font-size: 75%;
141 | line-height: 0;
142 | vertical-align: baseline;
143 | }
144 |
145 | sub {
146 | bottom: -0.25em;
147 | }
148 |
149 | sup {
150 | top: -0.5em;
151 | }
152 |
153 | /* Embedded content
154 | ========================================================================== */
155 |
156 | /**
157 | * Remove the border on images inside links in IE 10.
158 | */
159 |
160 | img {
161 | border-style: none;
162 | }
163 |
164 | /* Forms
165 | ========================================================================== */
166 |
167 | /**
168 | * 1. Change the font styles in all browsers.
169 | * 2. Remove the margin in Firefox and Safari.
170 | */
171 |
172 | button,
173 | input,
174 | optgroup,
175 | select,
176 | textarea {
177 | margin: 0;
178 | /* 2 */
179 | font-family: inherit;
180 | /* 1 */
181 | font-size: 100%;
182 | /* 1 */
183 | line-height: 1.15;
184 | /* 1 */
185 | }
186 |
187 | /**
188 | * Show the overflow in IE.
189 | * 1. Show the overflow in Edge.
190 | */
191 |
192 | button,
193 | input {
194 | /* 1 */
195 | overflow: visible;
196 | }
197 |
198 | /**
199 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
200 | * 1. Remove the inheritance of text transform in Firefox.
201 | */
202 |
203 | button,
204 | select {
205 | /* 1 */
206 | text-transform: none;
207 | }
208 |
209 | /**
210 | * Correct the inability to style clickable types in iOS and Safari.
211 | */
212 |
213 | button,
214 | [type="button"],
215 | [type="reset"],
216 | [type="submit"] {
217 | -webkit-appearance: button;
218 | }
219 |
220 | /**
221 | * Remove the inner border and padding in Firefox.
222 | */
223 |
224 | button::-moz-focus-inner,
225 | [type="button"]::-moz-focus-inner,
226 | [type="reset"]::-moz-focus-inner,
227 | [type="submit"]::-moz-focus-inner {
228 | padding: 0;
229 | border-style: none;
230 | }
231 |
232 | /**
233 | * Restore the focus styles unset by the previous rule.
234 | */
235 |
236 | button:-moz-focusring,
237 | [type="button"]:-moz-focusring,
238 | [type="reset"]:-moz-focusring,
239 | [type="submit"]:-moz-focusring {
240 | outline: 1px dotted ButtonText;
241 | }
242 |
243 | /**
244 | * Correct the padding in Firefox.
245 | */
246 |
247 | fieldset {
248 | padding: 0.35em 0.75em 0.625em;
249 | }
250 |
251 | /**
252 | * 1. Correct the text wrapping in Edge and IE.
253 | * 2. Correct the color inheritance from `fieldset` elements in IE.
254 | * 3. Remove the padding so developers are not caught out when they zero out
255 | * `fieldset` elements in all browsers.
256 | */
257 |
258 | legend {
259 | box-sizing: border-box;
260 | /* 1 */
261 | display: table;
262 | /* 1 */
263 | max-width: 100%;
264 | /* 1 */
265 | padding: 0;
266 | /* 3 */
267 | color: inherit;
268 | /* 2 */
269 | white-space: normal;
270 | /* 1 */
271 | }
272 |
273 | /**
274 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
275 | */
276 |
277 | progress {
278 | vertical-align: baseline;
279 | }
280 |
281 | /**
282 | * Remove the default vertical scrollbar in IE 10+.
283 | */
284 |
285 | textarea {
286 | overflow: auto;
287 | }
288 |
289 | /**
290 | * 1. Add the correct box sizing in IE 10.
291 | * 2. Remove the padding in IE 10.
292 | */
293 |
294 | [type="checkbox"],
295 | [type="radio"] {
296 | box-sizing: border-box;
297 | /* 1 */
298 | padding: 0;
299 | /* 2 */
300 | }
301 |
302 | /**
303 | * Correct the cursor style of increment and decrement buttons in Chrome.
304 | */
305 |
306 | [type="number"]::-webkit-inner-spin-button,
307 | [type="number"]::-webkit-outer-spin-button {
308 | height: auto;
309 | }
310 |
311 | /**
312 | * 1. Correct the odd appearance in Chrome and Safari.
313 | * 2. Correct the outline style in Safari.
314 | */
315 |
316 | [type="search"] {
317 | -webkit-appearance: textfield;
318 | /* 1 */
319 | outline-offset: -2px;
320 | /* 2 */
321 | }
322 |
323 | /**
324 | * Remove the inner padding in Chrome and Safari on macOS.
325 | */
326 |
327 | [type="search"]::-webkit-search-decoration {
328 | -webkit-appearance: none;
329 | }
330 |
331 | /**
332 | * 1. Correct the inability to style clickable types in iOS and Safari.
333 | * 2. Change font properties to `inherit` in Safari.
334 | */
335 |
336 | ::-webkit-file-upload-button {
337 | -webkit-appearance: button;
338 | /* 1 */
339 | font: inherit;
340 | /* 2 */
341 | }
342 |
343 | /* Interactive
344 | ========================================================================== */
345 |
346 | /*
347 | * Add the correct display in Edge, IE 10+, and Firefox.
348 | */
349 |
350 | details {
351 | display: block;
352 | }
353 |
354 | /*
355 | * Add the correct display in all browsers.
356 | */
357 |
358 | summary {
359 | display: list-item;
360 | }
361 |
362 | /* Misc
363 | ========================================================================== */
364 |
365 | /**
366 | * Add the correct display in IE 10+.
367 | */
368 |
369 | template {
370 | display: none;
371 | }
372 |
373 | /**
374 | * Add the correct display in IE 10.
375 | */
376 |
377 | [hidden] {
378 | display: none;
379 | }
380 |
--------------------------------------------------------------------------------