commit message
This commit is contained in:
commit
a62eb480b3
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 资源公共路径,需要以 /开头和结尾
|
||||||
|
VITE_PUBLIC_PATH = '/'
|
||||||
|
|
||||||
|
# 是否启用MOCK
|
||||||
|
VITE_APP_USE_MOCK = false
|
||||||
|
|
||||||
|
# proxy
|
||||||
|
VITE_PROXY = [["/api-dev","http://127.0.0.1:8002/api"]]
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VITE_APP_GLOB_BASE_API = '/api-dev'
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# 资源公共路径,需要以 /开头和结尾
|
||||||
|
VITE_PUBLIC_PATH = '/'
|
||||||
|
|
||||||
|
# 是否启用MOCK
|
||||||
|
VITE_APP_USE_MOCK = true
|
||||||
|
|
||||||
|
# proxy
|
||||||
|
VITE_PROXY = [["/api-local","http://127.0.0.1:8002/api"],["/api-mock","http://127.0.0.1:8003"]]
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VITE_APP_GLOB_BASE_API = '/api-local'
|
||||||
|
|
||||||
|
# mock base api
|
||||||
|
VITE_APP_GLOB_BASE_API_MOCK = '/api-mock'
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 资源公共路径,需要以 /开头和结尾
|
||||||
|
VITE_PUBLIC_PATH = '/'
|
||||||
|
|
||||||
|
# 是否启用MOCK
|
||||||
|
VITE_APP_USE_MOCK = false
|
||||||
|
|
||||||
|
# proxy
|
||||||
|
VITE_PROXY = [["/api-prod","http://127.0.0.1:8002/api"]]
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VITE_APP_GLOB_BASE_API = '/api-prod'
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 资源公共路径,需要以 /开头和结尾
|
||||||
|
VITE_PUBLIC_PATH = '/'
|
||||||
|
|
||||||
|
# 是否启用MOCK
|
||||||
|
VITE_APP_USE_MOCK = false
|
||||||
|
|
||||||
|
# proxy
|
||||||
|
VITE_PROXY = [["/api-test","http://127.0.0.1:8002/api/"]]
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VITE_APP_GLOB_BASE_API = '/api-test'
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-recommended',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: 'babel-eslint'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
/* 强制每行的最大属性 */
|
||||||
|
'vue/max-attributes-per-line': [2, {
|
||||||
|
/* 当开始标签位于单行时,每行的最大属性数 */
|
||||||
|
'singleline': 10,
|
||||||
|
/* 当开始标签位于多行时,每行的最大属性数 */
|
||||||
|
'multiline': {
|
||||||
|
'max': 1
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
/* 在单行元素的内容之前和之后需要换行符 */
|
||||||
|
'vue/singleline-html-element-content-newline': 'off',
|
||||||
|
/* 在单行元素的内容之前和之后需要换行符 */
|
||||||
|
'vue/multiline-html-element-content-newline': 'off',
|
||||||
|
/* 组件名称驼峰 */
|
||||||
|
'vue/component-definition-name-casing': ['error', 'PascalCase'],
|
||||||
|
/* 禁止使用 v-html */
|
||||||
|
'vue/no-v-html': 'off',
|
||||||
|
/* 格式化箭头函数的箭头前后空格 */
|
||||||
|
'arrow-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
/* 强制缩进 */
|
||||||
|
'block-spacing': [2, 'always'],
|
||||||
|
/* 强制执行一个真正的大括号风格 */
|
||||||
|
'brace-style': [2, '1tbs', {
|
||||||
|
/* 允许一个块打开和关闭括号在同一行上 */
|
||||||
|
'allowSingleLine': true
|
||||||
|
}],
|
||||||
|
/* 为属性名称强制执行 camelcase 样式 */
|
||||||
|
'camelcase': [0, {
|
||||||
|
'properties': 'always'
|
||||||
|
}],
|
||||||
|
/* 不允许对象和数组尾随逗号 */
|
||||||
|
'comma-dangle': [2, 'never'],
|
||||||
|
/* 对象逗号前后允许空格 */
|
||||||
|
'comma-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
/* 在数组元素、对象属性或变量声明之后和同一行上需要逗号 */
|
||||||
|
'comma-style': [2, 'last'],
|
||||||
|
/* 检查是否存在有效的super()调用 */
|
||||||
|
'constructor-super': 2,
|
||||||
|
/* 以允许支柱少单行if,else if,else,for,while,或do,同时还规定使用其他实例花括号 */
|
||||||
|
'curly': [2, 'multi-line'],
|
||||||
|
/* 成员表达式中的点应与属性部分位于同一行 */
|
||||||
|
'dot-location': [2, 'property'],
|
||||||
|
/* 在非空文件的末尾至少执行一个换行符 */
|
||||||
|
'eol-last': 2,
|
||||||
|
/* 强制使用===和!== */
|
||||||
|
'eqeqeq': ['error', 'always'],
|
||||||
|
/* 强化*发生器功能的间距 */
|
||||||
|
'generator-star-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
/* 使用回调模式时会处理这个错误 */
|
||||||
|
'handle-callback-err': [2, '^(err|error)$'],
|
||||||
|
/* 强制执行一致的缩进样式 */
|
||||||
|
'indent': [2, 2, {
|
||||||
|
/* 强制缩进级别case的条款switch声明 */
|
||||||
|
'SwitchCase': 1
|
||||||
|
}],
|
||||||
|
/* 一致使用单引号 */
|
||||||
|
'jsx-quotes': [2, 'prefer-single'],
|
||||||
|
/* 强制在对象字面量属性中的键和值之间保持一致的间距 */
|
||||||
|
'key-spacing': [2, {
|
||||||
|
/* 不允许在对象文字中的键和冒号之间使用空格 */
|
||||||
|
'beforeColon': false,
|
||||||
|
/* 需要在冒号和对象文字中的值之间至少有一个空格 */
|
||||||
|
'afterColon': true
|
||||||
|
}],
|
||||||
|
/* 强制执行围绕关键字和关键字标记的一致空格 */
|
||||||
|
'keyword-spacing': [2, {
|
||||||
|
'before': true,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
/* 要求构造函数名以大写字母开头 */
|
||||||
|
'new-cap': [2, {
|
||||||
|
/* 要求new使用大写启动函数调用所有操作符 */
|
||||||
|
'newIsCap': true,
|
||||||
|
/* 允许在没有new操作符的情况下调用大写启动的函数 */
|
||||||
|
'capIsNew': false
|
||||||
|
}],
|
||||||
|
/* 在使用new关键字调用不带参数的构造函数时需要括号,以便提高代码清晰度 */
|
||||||
|
'new-parens': 2,
|
||||||
|
/* 不允许使用Array构造函数 */
|
||||||
|
'no-array-constructor': 2,
|
||||||
|
/* 阻止使用已弃用和次优代码 */
|
||||||
|
'no-caller': 2,
|
||||||
|
/* 禁止调用console对象的方法 */
|
||||||
|
'no-console': 'off',
|
||||||
|
/* 标记修改类声明的变量 */
|
||||||
|
'no-class-assign': 2,
|
||||||
|
/* 不允许在试验条件不明确赋值运算符if,for,while,和do...while语句 */
|
||||||
|
'no-cond-assign': 2,
|
||||||
|
/* 标记修改使用const关键字声明的变量 */
|
||||||
|
'no-const-assign': 2,
|
||||||
|
/* 不允许正则表达式中的控制字符 */
|
||||||
|
'no-control-regex': 0,
|
||||||
|
/* 不允许在变量上使用delete操作符 */
|
||||||
|
'no-delete-var': 2,
|
||||||
|
/* 不允许在函数声明或表达式中使用重复的参数名称 */
|
||||||
|
'no-dupe-args': 2,
|
||||||
|
/* 标记在级别成员中使用重复名称 */
|
||||||
|
'no-dupe-class-members': 2,
|
||||||
|
/* 不允许在对象文字中使用重复键 */
|
||||||
|
'no-dupe-keys': 2,
|
||||||
|
/* 不允许在switch语句的case子句中使用重复的测试表达式 */
|
||||||
|
'no-duplicate-case': 2,
|
||||||
|
/* 不允许在正则表达式中使用空字符类 */
|
||||||
|
'no-empty-character-class': 2,
|
||||||
|
/* 不允许空的解构模式 */
|
||||||
|
'no-empty-pattern': 2,
|
||||||
|
/* 禁止使用eval()函数来防止潜在的危险 */
|
||||||
|
'no-eval': 2,
|
||||||
|
/* 禁止对 catch 子句中的异常重新赋值 */
|
||||||
|
'no-ex-assign': 2,
|
||||||
|
/* 不允许直接修改内建对象的原型 */
|
||||||
|
'no-extend-native': 2,
|
||||||
|
/* 避免不必要的使用 */
|
||||||
|
'no-extra-bind': 2,
|
||||||
|
/* 禁止不必要的布尔转换 */
|
||||||
|
'no-extra-boolean-cast': 2,
|
||||||
|
/* 仅在函数表达式附近禁止不必要的括号 */
|
||||||
|
'no-extra-parens': [2, 'functions'],
|
||||||
|
/* 消除一个案件无意中掉到另一个案件 */
|
||||||
|
'no-fallthrough': 2,
|
||||||
|
/* 消除浮点小数点,并在数值有小数点但在其之前或之后缺少数字时发出警告 */
|
||||||
|
'no-floating-decimal': 2,
|
||||||
|
/* 不允许重新分配function声明 */
|
||||||
|
'no-func-assign': 2,
|
||||||
|
/* 消除隐含eval()通过使用setTimeout(),setInterval()或execScript() */
|
||||||
|
'no-implied-eval': 2,
|
||||||
|
/* 不允许function嵌套块中的声明 */
|
||||||
|
'no-inner-declarations': [2, 'functions'],
|
||||||
|
/* 不允许RegExp构造函数中的无效正则表达式字符串 */
|
||||||
|
'no-invalid-regexp': 2,
|
||||||
|
/* 捕获不是正常制表符和空格的无效空格 */
|
||||||
|
'no-irregular-whitespace': 2,
|
||||||
|
/* 防止因使用 __iterator__属性而出现的错误 */
|
||||||
|
'no-iterator': 2,
|
||||||
|
/* 禁用与变量同名的标签 */
|
||||||
|
'no-label-var': 2,
|
||||||
|
/* 禁用标签语句 */
|
||||||
|
'no-labels': [2, {
|
||||||
|
'allowLoop': false,
|
||||||
|
'allowSwitch': false
|
||||||
|
}],
|
||||||
|
/* 禁用不必要的嵌套块 */
|
||||||
|
'no-lone-blocks': 2,
|
||||||
|
/* 禁止混用tab和space */
|
||||||
|
'no-mixed-spaces-and-tabs': 2,
|
||||||
|
/* 禁止出现多个空格 */
|
||||||
|
'no-multi-spaces': 2,
|
||||||
|
/* 禁止多行字符串 */
|
||||||
|
'no-multi-str': 2,
|
||||||
|
/* 强制最大连续空行数1 */
|
||||||
|
'no-multiple-empty-lines': [2, {
|
||||||
|
'max': 1
|
||||||
|
}],
|
||||||
|
/* 不允许修改只读全局变量 */
|
||||||
|
'no-global-assign': 2,
|
||||||
|
/* 不允许使用Object构造函数 */
|
||||||
|
'no-new-object': 2,
|
||||||
|
/* 消除new require表达的使用 */
|
||||||
|
'no-new-require': 2,
|
||||||
|
/* 防止Symbol与new操作员的意外呼叫 */
|
||||||
|
'no-new-symbol': 2,
|
||||||
|
/* 杜绝使用String,Number以及Boolean与new操作 */
|
||||||
|
'no-new-wrappers': 2,
|
||||||
|
/* 不允许调用Math,JSON和Reflect对象作为功能 */
|
||||||
|
'no-obj-calls': 2,
|
||||||
|
/* 不允许使用八进制文字 */
|
||||||
|
'no-octal': 2,
|
||||||
|
/* 不允许字符串文字中的八进制转义序列 */
|
||||||
|
'no-octal-escape': 2,
|
||||||
|
/* 防止 Node.js 中的目录路径字符串连接 */
|
||||||
|
'no-path-concat': 2,
|
||||||
|
/* 当一个对象被__proto__创建时被设置为该对象的构造函数的原始原型属性。getPrototypeOf是获得“原型”的首选方法 */
|
||||||
|
'no-proto': 2,
|
||||||
|
/* 消除在同一范围内具有多个声明的变量 */
|
||||||
|
'no-redeclare': 2,
|
||||||
|
/* 在正则表达式文字中不允许有多个空格 */
|
||||||
|
'no-regex-spaces': 2,
|
||||||
|
/* return陈述中的任务,除非用圆括号括起来,否则不允许赋值 */
|
||||||
|
'no-return-assign': [2, 'except-parens'],
|
||||||
|
/* 消除自我分配 */
|
||||||
|
'no-self-assign': 2,
|
||||||
|
/* 禁止自身比较 */
|
||||||
|
'no-self-compare': 2,
|
||||||
|
/* 不允许使用逗号操作符 */
|
||||||
|
'no-sequences': 2,
|
||||||
|
/* 关键字不能被遮蔽 */
|
||||||
|
'no-shadow-restricted-names': 2,
|
||||||
|
/* 不允许功能标识符与其应用程序之间的间距 */
|
||||||
|
'no-spaced-func': 2,
|
||||||
|
/* 禁用稀疏数组 */
|
||||||
|
'no-sparse-arrays': 2,
|
||||||
|
/* 在构造函数中禁止在调用super()之前使用this或super。 */
|
||||||
|
'no-this-before-super': 2,
|
||||||
|
/* 限制可以被抛出的异常 */
|
||||||
|
'no-throw-literal': 2,
|
||||||
|
/* 禁用行尾空白 */
|
||||||
|
'no-trailing-spaces': 2,
|
||||||
|
/* 禁用未声明的变量 */
|
||||||
|
'no-undef': 2,
|
||||||
|
/* 禁用未声明的变量 */
|
||||||
|
'no-undef-init': 2,
|
||||||
|
/* 禁止使用令人困惑的多行表达式 */
|
||||||
|
'no-unexpected-multiline': 2,
|
||||||
|
/* 禁用一成不变的循环条件 */
|
||||||
|
'no-unmodified-loop-condition': 2,
|
||||||
|
/* 禁止可以表达为更简单结构的三元操作符 */
|
||||||
|
'no-unneeded-ternary': [2, {
|
||||||
|
/* 禁止条件表达式作为默认的赋值模式 */
|
||||||
|
'defaultAssignment': false
|
||||||
|
}],
|
||||||
|
/* 禁止在 return、throw、continue 和 break 语句后出现不可达代码 */
|
||||||
|
'no-unreachable': 2,
|
||||||
|
/* 禁止在 finally 语句块中出现控制流语句 */
|
||||||
|
'no-unsafe-finally': 2,
|
||||||
|
/* 禁止未使用过的变量 */
|
||||||
|
'no-unused-vars': [2, {
|
||||||
|
'vars': 'all',
|
||||||
|
'args': 'none'
|
||||||
|
}],
|
||||||
|
/* 禁用不必要的 .call() 和 .apply() */
|
||||||
|
'no-useless-call': 2,
|
||||||
|
/* 禁止在对象中使用不必要的计算属性 */
|
||||||
|
'no-useless-computed-key': 2,
|
||||||
|
/* 禁用不必要的构造函数 */
|
||||||
|
'no-useless-constructor': 2,
|
||||||
|
/* 在不改变代码行为的情况下可以安全移除的转义 */
|
||||||
|
'no-useless-escape': 0,
|
||||||
|
/* 禁止属性前有空白 */
|
||||||
|
'no-whitespace-before-property': 2,
|
||||||
|
/* 禁用 with 语句 */
|
||||||
|
'no-with': 2,
|
||||||
|
/* 强制函数中的变量在一起声明或分开声明 */
|
||||||
|
'one-var': [2, {
|
||||||
|
/* 要求每个作用域的初始化的变量有多个变量声明 */
|
||||||
|
'initialized': 'never'
|
||||||
|
}],
|
||||||
|
/* 强制操作符使用一致的换行符风格 */
|
||||||
|
'operator-linebreak': [2, 'after', {
|
||||||
|
/* 覆盖对指定的操作的全局设置 */
|
||||||
|
'overrides': {
|
||||||
|
'?': 'before',
|
||||||
|
':': 'before'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
/* 要求或禁止块内填充 */
|
||||||
|
'padded-blocks': [2, 'never'],
|
||||||
|
/* 强制使用一致的反勾号、双引号或单引号 */
|
||||||
|
'quotes': [2, 'single', {
|
||||||
|
/* 允许字符串使用单引号或双引号,只要字符串中包含了一个其它引号,否则需要转义 */
|
||||||
|
'avoidEscape': true,
|
||||||
|
/* 允许字符串使用反勾号 */
|
||||||
|
'allowTemplateLiterals': true
|
||||||
|
}],
|
||||||
|
/* 要求或禁止使用分号代替 ASI */
|
||||||
|
'semi': [2, 'never'],
|
||||||
|
/* 强制分号前后有空格 */
|
||||||
|
'semi-spacing': [2, {
|
||||||
|
'before': false,
|
||||||
|
'after': true
|
||||||
|
}],
|
||||||
|
/* 要求或禁止语句块之前的空格 */
|
||||||
|
'space-before-blocks': [2, 'always'],
|
||||||
|
/* 要求或禁止函数圆括号之前有一个空格 */
|
||||||
|
'space-before-function-paren': [2, 'never'],
|
||||||
|
/* 禁止或强制圆括号内的空格 */
|
||||||
|
'space-in-parens': [2, 'never'],
|
||||||
|
/* 要求中缀操作符周围有空格 */
|
||||||
|
'space-infix-ops': 2,
|
||||||
|
/* 要求或禁止在一元操作符之前或之后存在空格 */
|
||||||
|
'space-unary-ops': [2, {
|
||||||
|
/* 适用于单词类一元操作符,例如:new、delete、typeof、void、yield */
|
||||||
|
'words': true,
|
||||||
|
/* 适用于这些一元操作符: -、+、--、++、!、!! */
|
||||||
|
'nonwords': false
|
||||||
|
}],
|
||||||
|
/* 要求或禁止在注释前有空白 */
|
||||||
|
'spaced-comment': [2, 'always', {
|
||||||
|
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||||
|
}],
|
||||||
|
/* 强制模板字符串中空格的使用 禁止花括号内出现空格 */
|
||||||
|
'template-curly-spacing': [2, 'never'],
|
||||||
|
/* 要求调用 isNaN()检查 NaN */
|
||||||
|
'use-isnan': 2,
|
||||||
|
/* 强制 typeof 表达式与有效的字符串进行比较 */
|
||||||
|
'valid-typeof': 2,
|
||||||
|
/* 需要把立即执行的函数包裹起来 */
|
||||||
|
'wrap-iife': [2, 'any'],
|
||||||
|
/* 强制在 yield* 表达式中 * 周围使用空格 */
|
||||||
|
'yield-star-spacing': [2, 'both'],
|
||||||
|
/* 要求或者禁止Yoda条件 */
|
||||||
|
'yoda': [2, 'never'],
|
||||||
|
/* 建议使用const */
|
||||||
|
'prefer-const': 2,
|
||||||
|
/* 禁用 debugger */
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||||
|
/* 强制在花括号中使用一致的空格 */
|
||||||
|
'object-curly-spacing': [2, 'always', {
|
||||||
|
/* 禁止以对象元素开始或结尾的对象的花括号中有空格 */
|
||||||
|
objectsInObjects: false
|
||||||
|
}],
|
||||||
|
/* 禁止或强制在括号内使用空格 */
|
||||||
|
'array-bracket-spacing': [2, 'never']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["johnsoncodehk.volar"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Vue 3 + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const GLOB_CONFIG_FILE_NAME = 'app.config.js'
|
||||||
|
export const GLOB_CONFIG_NAME = '__APP__GLOB__CONF__'
|
||||||
|
export const OUTPUT_DIR = 'dist'
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { writeFileSync } from 'fs-extra'
|
||||||
|
import { OUTPUT_DIR } from '../constant'
|
||||||
|
import { getEnvConfig, getRootPath } from '../utils'
|
||||||
|
|
||||||
|
export function runBuildCNAME() {
|
||||||
|
const { VITE_APP_GLOB_CNAME } = getEnvConfig()
|
||||||
|
if (!VITE_APP_GLOB_CNAME) return
|
||||||
|
try {
|
||||||
|
writeFileSync(getRootPath(`${OUTPUT_DIR}/CNAME`), VITE_APP_GLOB_CNAME)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('CNAME file failed to package:\n' + error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { GLOB_CONFIG_FILE_NAME, GLOB_CONFIG_NAME, OUTPUT_DIR } from '../constant'
|
||||||
|
import fs, { writeFileSync } from 'fs-extra'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { getEnvConfig, getRootPath } from '../utils'
|
||||||
|
|
||||||
|
function createConfig(option) {
|
||||||
|
const { config, configName, configFileName } = option
|
||||||
|
try {
|
||||||
|
const windowConf = `window.${configName}`
|
||||||
|
const configStr = `${windowConf}=${JSON.stringify(config)};
|
||||||
|
Object.freeze(${windowConf});
|
||||||
|
Object.defineProperty(window, "${configName}", {
|
||||||
|
configurable: false,
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
`.replace(/\s/g, '')
|
||||||
|
fs.mkdirp(getRootPath(OUTPUT_DIR))
|
||||||
|
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('configuration file configuration file failed to package:\n' + error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runBuildConfig() {
|
||||||
|
const config = getEnvConfig()
|
||||||
|
const configName = GLOB_CONFIG_NAME
|
||||||
|
const configFileName = GLOB_CONFIG_FILE_NAME
|
||||||
|
createConfig({ config, configName, configFileName })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import { runBuildConfig } from './build-config'
|
||||||
|
import { runBuildCNAME } from './build-cname'
|
||||||
|
|
||||||
|
export const runBuild = async () => {
|
||||||
|
try {
|
||||||
|
runBuildConfig()
|
||||||
|
runBuildCNAME()
|
||||||
|
console.log(`✨ ${chalk.cyan('build successfully!')}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red('vite build error:\n' + error))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runBuild()
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
|
export function wrapperEnv(envOptions) {
|
||||||
|
if (!envOptions) return {}
|
||||||
|
const ret = {}
|
||||||
|
|
||||||
|
for (const key in envOptions) {
|
||||||
|
let val = envOptions[key]
|
||||||
|
if (['true', 'false'].includes(val)) {
|
||||||
|
val = val === 'true'
|
||||||
|
}
|
||||||
|
if (['VITE_PORT'].includes(key)) {
|
||||||
|
val = +val
|
||||||
|
}
|
||||||
|
if (key === 'VITE_PROXY' && val) {
|
||||||
|
try {
|
||||||
|
val = JSON.parse(val.replace(/'/g, '"'))
|
||||||
|
} catch (error) {
|
||||||
|
val = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret[key] = val
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
process.env[key] = val
|
||||||
|
} else if (typeof key === 'object') {
|
||||||
|
process.env[key] = JSON.stringify(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境下生效的配置文件名
|
||||||
|
*/
|
||||||
|
function getConfFiles() {
|
||||||
|
const script = process.env.npm_lifecycle_script
|
||||||
|
const reg = new RegExp('--mode ([a-z_\\d]+)')
|
||||||
|
const result = reg.exec(script)
|
||||||
|
if (result) {
|
||||||
|
const mode = result[1]
|
||||||
|
return ['.env', '.env.local', `.env.${mode}`]
|
||||||
|
}
|
||||||
|
return ['.env', '.env.local', '.env.production']
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEnvConfig(match = 'VITE_APP_GLOB_', confFiles = getConfFiles()) {
|
||||||
|
let envConfig = {}
|
||||||
|
confFiles.forEach((item) => {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(path.resolve(process.cwd(), item))) {
|
||||||
|
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
|
||||||
|
envConfig = { ...envConfig, ...env }
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error in parsing ${item}`, e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const reg = new RegExp(`^(${match})`)
|
||||||
|
Object.keys(envConfig).forEach((key) => {
|
||||||
|
if (!reg.test(key)) {
|
||||||
|
Reflect.deleteProperty(envConfig, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return envConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRootPath(...dir) {
|
||||||
|
return path.resolve(process.cwd(), ...dir)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import html from 'vite-plugin-html'
|
||||||
|
import { version } from '../../../package.json'
|
||||||
|
import { GLOB_CONFIG_FILE_NAME } from '../../constant'
|
||||||
|
export function configHtmlPlugin(viteEnv, isBuild) {
|
||||||
|
const { VITE_APP_TITLE, VITE_PUBLIC_PATH } = viteEnv
|
||||||
|
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`
|
||||||
|
|
||||||
|
const getAppConfigSrc = () => {
|
||||||
|
return `${path}${GLOB_CONFIG_FILE_NAME}?v=${version}-${new Date().getTime()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlPlugin = html({
|
||||||
|
minify: isBuild,
|
||||||
|
inject: {
|
||||||
|
data: {
|
||||||
|
title: VITE_APP_TITLE
|
||||||
|
},
|
||||||
|
tags: isBuild
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
tag: 'script',
|
||||||
|
attrs: {
|
||||||
|
src: getAppConfigSrc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return htmlPlugin
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
|
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||||
|
|
||||||
|
import { unocss } from './unocss'
|
||||||
|
import { configHtmlPlugin } from './html'
|
||||||
|
import { configMockPlugin } from './mock'
|
||||||
|
|
||||||
|
export function createVitePlugins(viteEnv, isBuild) {
|
||||||
|
const plugins = [
|
||||||
|
vue(),
|
||||||
|
Components({
|
||||||
|
resolvers: [NaiveUiResolver()]
|
||||||
|
}),
|
||||||
|
VueSetupExtend(),
|
||||||
|
unocss(),
|
||||||
|
configHtmlPlugin(viteEnv, isBuild)
|
||||||
|
]
|
||||||
|
|
||||||
|
viteEnv?.VITE_APP_USE_MOCK && plugins.push(configMockPlugin(isBuild))
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { viteMockServe } from 'vite-plugin-mock'
|
||||||
|
|
||||||
|
export function configMockPlugin(isBuild) {
|
||||||
|
return viteMockServe({
|
||||||
|
ignore: /^\_/,
|
||||||
|
mockPath: 'mock',
|
||||||
|
localEnabled: !isBuild,
|
||||||
|
prodEnabled: isBuild,
|
||||||
|
injectCode: `
|
||||||
|
import { setupProdMockServer } from '../mock/_create-prod-server';
|
||||||
|
setupProdMockServer();
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Unocss from 'unocss/vite'
|
||||||
|
import { presetUno, presetAttributify, presetIcons } from 'unocss'
|
||||||
|
|
||||||
|
// https://github.com/antfu/unocss
|
||||||
|
export function unocss() {
|
||||||
|
return Unocss({
|
||||||
|
presets: [presetUno(), presetAttributify(), presetIcons()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
const httpsRE = /^https:\/\//
|
||||||
|
export function createProxy(list = []) {
|
||||||
|
const ret = {}
|
||||||
|
for (const [prefix, target] of list) {
|
||||||
|
const isHttps = httpsRE.test(target)
|
||||||
|
// https://github.com/http-party/node-http-proxy#options
|
||||||
|
ret[prefix] = {
|
||||||
|
target: target,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
|
||||||
|
// https is require secure=false
|
||||||
|
...(isHttps ? { secure: false } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="Expires" content="0" />
|
||||||
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
|
<meta http-equiv="Cache-control" content="no-cache" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<title><%= title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||||
|
|
||||||
|
const modules = import.meta.globEager('./**/*.js')
|
||||||
|
const mockModules = []
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
if (key.includes('/_')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mockModules.push(...modules[key].default)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function setupProdMockServer() {
|
||||||
|
createProdMockServer(mockModules)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import Mock from 'mockjs'
|
||||||
|
|
||||||
|
export function resultSuccess(data, { message = 'ok' } = {}) {
|
||||||
|
return Mock.mock({
|
||||||
|
code: 0,
|
||||||
|
data,
|
||||||
|
message,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Mock from 'mockjs'
|
||||||
|
import { resultSuccess } from '../_util'
|
||||||
|
|
||||||
|
const Random = Mock.Random
|
||||||
|
|
||||||
|
const access_token = Random.string('upper', 32, 32)
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/api-mock/login/login',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'post',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess({ access_token })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import Mock from 'mockjs'
|
||||||
|
import { resultSuccess } from '../_util'
|
||||||
|
import { asyncRoutes } from './router.js'
|
||||||
|
|
||||||
|
const routes = deepClone([...asyncRoutes])
|
||||||
|
|
||||||
|
function deepClone(source) {
|
||||||
|
if (!source && typeof source !== 'object') {
|
||||||
|
throw new Error('error arguments', 'deepClone')
|
||||||
|
}
|
||||||
|
const targetObj = source.constructor === Array ? [] : {}
|
||||||
|
Object.keys(source).forEach(keys => {
|
||||||
|
if (source[keys] && typeof source[keys] === 'object') {
|
||||||
|
targetObj[keys] = deepClone(source[keys])
|
||||||
|
} else {
|
||||||
|
targetObj[keys] = source[keys]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return targetObj
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuList = []
|
||||||
|
const userList = []
|
||||||
|
const count = 100
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
menuList.push(Mock.mock({
|
||||||
|
title: '@name',
|
||||||
|
'type|1': ['0', '1'],
|
||||||
|
'method|1': ['put', 'post'],
|
||||||
|
path: '@name',
|
||||||
|
component: '@name',
|
||||||
|
permission: '@name',
|
||||||
|
'status|1': ['1', '2'],
|
||||||
|
sort: '@natural',
|
||||||
|
'hide|1': ['0', '1'],
|
||||||
|
createTime: '@datetime'
|
||||||
|
}))
|
||||||
|
userList.push(Mock.mock({
|
||||||
|
code: '',
|
||||||
|
avatar: '@image',
|
||||||
|
username: '@name',
|
||||||
|
realname: '@cname',
|
||||||
|
roles: '',
|
||||||
|
type: '',
|
||||||
|
status: '',
|
||||||
|
deptName: '',
|
||||||
|
createTime: '@datetime',
|
||||||
|
updateTime: '@datetime'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: '/api-mock/index/getMenuList',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: () => {
|
||||||
|
return resultSuccess(routes)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api-mock/menu/index',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: config => {
|
||||||
|
const { page = 1, limit = 10 } = config.query
|
||||||
|
const mockList = menuList.filter(item => {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
const List = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
|
||||||
|
return resultSuccess(List)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api-mock/user/apiIndex',
|
||||||
|
timeout: 1000,
|
||||||
|
method: 'get',
|
||||||
|
response: config => {
|
||||||
|
const { page = 1, limit = 10 } = config.query
|
||||||
|
const mockList = userList.filter(item => {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
const List = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
|
||||||
|
return resultSuccess(List)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
const asyncRoutes = [
|
||||||
|
{
|
||||||
|
path: '/system',
|
||||||
|
component: 'Layout',
|
||||||
|
redirect: '/system/menu',
|
||||||
|
name: 'System',
|
||||||
|
meta: {
|
||||||
|
title: '系统管理'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'menu',
|
||||||
|
component: 'views/system/menu/index',
|
||||||
|
name: 'SystemMenu',
|
||||||
|
meta: {
|
||||||
|
title: '菜单管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
component: 'views/system/user/index',
|
||||||
|
name: 'SystemUser',
|
||||||
|
meta: {
|
||||||
|
title: '菜单管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'role',
|
||||||
|
component: 'views/system/role/index',
|
||||||
|
name: 'SystemRole',
|
||||||
|
meta: {
|
||||||
|
title: '角色管理'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
asyncRoutes
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "vite_vue3",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --mode localhost",
|
||||||
|
"build:test": "vite build --mode test && esno ./build/script",
|
||||||
|
"build:dev": "vite build --mode development && esno ./build/script",
|
||||||
|
"build:prod": "vite build --mode production && esno ./build/script",
|
||||||
|
"serve": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vicons/antd": "^0.10.0",
|
||||||
|
"@vicons/ionicons5": "^0.10.0",
|
||||||
|
"axios": "^0.26.1",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"pinia": "^2.0.13",
|
||||||
|
"vue": "^3.2.16",
|
||||||
|
"vue-router": "^4.0.14",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@unocss/preset-attributify": "^0.16.4",
|
||||||
|
"@unocss/preset-icons": "^0.16.4",
|
||||||
|
"@unocss/preset-uno": "^0.16.4",
|
||||||
|
"@vitejs/plugin-vue": "^1.9.3",
|
||||||
|
"@vue/cli-plugin-eslint": "^5.0.4",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"chalk": "^5.0.1",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"eslint": "^7.19.0",
|
||||||
|
"eslint-plugin-html": "^6.2.0",
|
||||||
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
|
"esno": "^0.13.0",
|
||||||
|
"fs-extra": "^10.0.1",
|
||||||
|
"naive-ui": "^2.27.0",
|
||||||
|
"sass": "^1.49.11",
|
||||||
|
"unocss": "^0.16.4",
|
||||||
|
"unplugin-vue-components": "^0.18.5",
|
||||||
|
"vite": "^2.6.4",
|
||||||
|
"vite-plugin-html": "^2.1.2",
|
||||||
|
"vite-plugin-mock": "^2.9.6",
|
||||||
|
"vite-plugin-vue-setup-extend": "^0.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<n-config-provider inline-theme-disabled :theme-overrides="themeOverrides">
|
||||||
|
<n-loading-bar-provider>
|
||||||
|
<loading-bar />
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<component :is="Component" />
|
||||||
|
</router-view>
|
||||||
|
</n-loading-bar-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import themeOverrides from '@/utils/ui/theme.js'
|
||||||
|
import LoadingBar from '@/components/LoadingBar/index.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
.n-config-provider {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export const login = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/auth/login',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const refreshToken = () => {
|
||||||
|
return request({
|
||||||
|
url: '/auth/refreshToken',
|
||||||
|
method: 'post',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { mockAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export function userLogin(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/login/login',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function userCaptcha() {
|
||||||
|
return request({
|
||||||
|
url: '/login/captcha',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export function getPosts(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/posts',
|
||||||
|
method: 'get',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPostById({ id }) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function savePost(id, data = {}) {
|
||||||
|
if (id) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/post',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePost(id) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { mockAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export function getMenu() {
|
||||||
|
return request({
|
||||||
|
url: '/index/getMenuList',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMenuList() {
|
||||||
|
return request({
|
||||||
|
url: '/menu/index',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserList(params) {
|
||||||
|
return request({
|
||||||
|
url: '/user/apiIndex',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export function getUsers(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/users',
|
||||||
|
method: 'get',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser(id) {
|
||||||
|
if (id) {
|
||||||
|
return request({
|
||||||
|
url: `/user/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return request({
|
||||||
|
url: '/user',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveUser(data = {}, id) {
|
||||||
|
if (id) {
|
||||||
|
return request({
|
||||||
|
url: '/user',
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: `/user/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<n-config-provider inline-theme-disabled :theme-overrides="themeOverrides">
|
||||||
|
<n-loading-bar-provider>
|
||||||
|
<!-- <loading-bar /> -->
|
||||||
|
<n-dialog-provider>
|
||||||
|
<dialog-content />
|
||||||
|
<n-message-provider>
|
||||||
|
<message-content />
|
||||||
|
<slot />
|
||||||
|
</n-message-provider>
|
||||||
|
</n-dialog-provider>
|
||||||
|
</n-loading-bar-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import themeOverrides from '@/utils/ui/theme.js'
|
||||||
|
// import MessageContent from './MessageContent.vue'
|
||||||
|
// import DialogContent from './DialogContent.vue'
|
||||||
|
// import LoadingBar from './LoadingBar.vue'
|
||||||
|
|
||||||
|
// import { useAppStore } from '@/store/modules/app'
|
||||||
|
// const appStore = useAppStore()
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<n-breadcrumb>
|
||||||
|
<n-breadcrumb-item href="#">
|
||||||
|
北京总行
|
||||||
|
</n-breadcrumb-item>
|
||||||
|
<n-breadcrumb-item href="#">
|
||||||
|
天津分行
|
||||||
|
</n-breadcrumb-item>
|
||||||
|
<n-breadcrumb-item href="#">
|
||||||
|
平山道支行
|
||||||
|
</n-breadcrumb-item>
|
||||||
|
</n-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<!--顶部左侧区域-->
|
||||||
|
<div class="table-toolbar-left">
|
||||||
|
<slot name="tableTitle" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-toolbar-right">
|
||||||
|
<!--顶部右侧区域-->
|
||||||
|
<slot name="toolbar" />
|
||||||
|
<!--刷新-->
|
||||||
|
<span @click="reload">刷新</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-table">
|
||||||
|
<n-data-table v-bind="getBindProps" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NDataTable } from 'naive-ui'
|
||||||
|
import { unref, computed } from 'vue'
|
||||||
|
export default {
|
||||||
|
name: 'DataTable',
|
||||||
|
props: {
|
||||||
|
...NDataTable.props
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const getBindProps = computed(() => {
|
||||||
|
return {
|
||||||
|
...unref(props)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(getBindProps)
|
||||||
|
return {
|
||||||
|
getBindProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 15px 10px;
|
||||||
|
.table-toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.table-toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 1;
|
||||||
|
.table-toolbar-icon {
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-color);
|
||||||
|
&:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-toolbar-inner-popover-title {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<div class="tableAction" :style="`justify-content: ${getAlign}`">
|
||||||
|
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
|
||||||
|
<n-button v-if="!action.type || action.type === 'button'" class="tableAction__item" v-bind="action.props">{{ action.label }}</n-button>
|
||||||
|
<n-popconfirm
|
||||||
|
v-if="action.type === 'popconfirm'"
|
||||||
|
v-bind="action.props"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<n-button v-bind="action.ButtonProps" class="tableAction__item">{{ action.label }}</n-button>
|
||||||
|
</template>
|
||||||
|
{{ action.tip }}
|
||||||
|
</n-popconfirm>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, computed, toRaw, reactive } from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableAction',
|
||||||
|
props: {
|
||||||
|
actions: {
|
||||||
|
type: Array,
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
align: {
|
||||||
|
type: String,
|
||||||
|
default: 'center',
|
||||||
|
validator: (value) => {
|
||||||
|
return ['left', 'right', 'center'].indexOf(value) !== -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const data = reactive({
|
||||||
|
permissionList: [
|
||||||
|
'basic_list'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const getActions = computed(() => {
|
||||||
|
return (toRaw(props.actions) || [])
|
||||||
|
.filter((action) => {
|
||||||
|
return data.permissionList.includes(action.auth) || action.auth === ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const getAlign = computed(() => {
|
||||||
|
return toRaw(props.align)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
getActions,
|
||||||
|
getAlign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
.tableAction{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.tableAction__item{
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
// justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<n-image v-bind="getProps" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, computed, toRaw } from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableImage',
|
||||||
|
props: {
|
||||||
|
images: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const getProps = computed(() => {
|
||||||
|
return (toRaw(props.images))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
getProps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
<template>
|
||||||
|
<!-- <n-data-table>
|
||||||
|
n-data-table
|
||||||
|
</n-data-table> -->
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<!--顶部左侧区域-->
|
||||||
|
<div class="flex items-center table-toolbar-left">
|
||||||
|
<slot name="tableTitle" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center table-toolbar-right">
|
||||||
|
<!--顶部右侧区域-->
|
||||||
|
<slot name="toolbar" />
|
||||||
|
<!--刷新-->
|
||||||
|
<!-- <n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="table-toolbar-right-icon" @click="reload">
|
||||||
|
<n-icon size="18">
|
||||||
|
<ReloadOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
</template> -->
|
||||||
|
<span @click="reload">刷新</span>
|
||||||
|
<!-- </n-tooltip> -->
|
||||||
|
<!--表格设置单独抽离成组件-->
|
||||||
|
<!-- <ColumnSetting /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="s-table">
|
||||||
|
<n-data-table
|
||||||
|
ref="tableElRef"
|
||||||
|
v-bind="getBindValues"
|
||||||
|
:pagination="pagination"
|
||||||
|
>
|
||||||
|
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
|
||||||
|
<slot :name="item" v-bind="data" />
|
||||||
|
</template>
|
||||||
|
</n-data-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { tableProps } from './tools/props.js'
|
||||||
|
import { useDataSource } from './tools/useDataSource.js'
|
||||||
|
import { usePagination } from './tools/usePagination.js'
|
||||||
|
|
||||||
|
import { unref, ref, computed, toRaw } from 'vue'
|
||||||
|
export default {
|
||||||
|
name: 'DataTable',
|
||||||
|
props: {
|
||||||
|
...tableProps
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'fetch-success',
|
||||||
|
'fetch-error',
|
||||||
|
'update:checked-row-keys',
|
||||||
|
'edit-end',
|
||||||
|
'edit-cancel',
|
||||||
|
'edit-row-end',
|
||||||
|
'edit-change'
|
||||||
|
],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const loadingRef = ref(unref(props).loading)
|
||||||
|
const getLoading = computed(() => unref(loadingRef))
|
||||||
|
function setLoading(loading) {
|
||||||
|
loadingRef.value = loading
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pagination-start */
|
||||||
|
const pagination = computed(() => toRaw(unref(getPaginationInfo)))
|
||||||
|
const { getPaginationInfo, setPagination } = usePagination(props)
|
||||||
|
/* pagination-end */
|
||||||
|
|
||||||
|
/* tableData-start */
|
||||||
|
const tableData = ref([])
|
||||||
|
const { getDataSourceRef, reload } = useDataSource(props, { getPaginationInfo, setPagination, tableData, setLoading }, emit)
|
||||||
|
|
||||||
|
// 组装表格信息
|
||||||
|
const getBindValues = computed(() => {
|
||||||
|
const tableData = unref(getDataSourceRef)
|
||||||
|
return {
|
||||||
|
...unref(props),
|
||||||
|
loading: unref(getLoading),
|
||||||
|
// columns: toRaw(unref(getPageColumns)),
|
||||||
|
// rowKey: unref(getRowKey),
|
||||||
|
data: tableData,
|
||||||
|
// size: unref(getTableSize),
|
||||||
|
remote: true,
|
||||||
|
'max-height': 'auto'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/* tableData-end */
|
||||||
|
|
||||||
|
return {
|
||||||
|
pagination,
|
||||||
|
fetch,
|
||||||
|
reload,
|
||||||
|
getBindValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 0 16px 0;
|
||||||
|
.table-toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.table-toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 1;
|
||||||
|
.table-toolbar-icon {
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-color);
|
||||||
|
&:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-toolbar-inner-popover-title {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { NDataTable } from 'naive-ui'
|
||||||
|
|
||||||
|
export const tableProps = {
|
||||||
|
...NDataTable.props,
|
||||||
|
/* 初始化接口请求 */
|
||||||
|
request: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
/* 分页信息 */
|
||||||
|
pagination: {
|
||||||
|
type: [Object, Boolean],
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
/* 分页设置信息 */
|
||||||
|
paginationSetting: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
// 当前页的字段名
|
||||||
|
pageField: 'page',
|
||||||
|
// 每页数量字段名
|
||||||
|
sizeField: 'pageSize',
|
||||||
|
// 接口返回的数据字段名
|
||||||
|
listField: 'list',
|
||||||
|
// 接口返回总页数字段名
|
||||||
|
totalField: 'pageCount',
|
||||||
|
// 默认分页数量
|
||||||
|
defaultPageSize: 10,
|
||||||
|
// 可切换每页数量集合
|
||||||
|
pageSizes: [10, 20, 30, 40, 50]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { ref, unref, computed, onMounted } from 'vue'
|
||||||
|
import { isBoolean } from '@/utils/is'
|
||||||
|
|
||||||
|
export function useDataSource(propsRef, { getPaginationInfo, setPagination, setLoading, tableData }, emit) {
|
||||||
|
const dataSourceRef = ref([])
|
||||||
|
|
||||||
|
async function fetch(opt) {
|
||||||
|
try {
|
||||||
|
// setLoading(true)
|
||||||
|
const { request, pagination } = unref(propsRef)
|
||||||
|
/* 无接口请求中断 */
|
||||||
|
if (!request) return
|
||||||
|
/* 获取分页信息 */
|
||||||
|
const paginationSetting = propsRef.paginationSetting
|
||||||
|
const pageField = paginationSetting.pageField
|
||||||
|
const sizeField = paginationSetting.sizeField
|
||||||
|
const totalField = paginationSetting.totalField
|
||||||
|
const listField = paginationSetting.listField
|
||||||
|
|
||||||
|
let pageParams = {}
|
||||||
|
const { page = 1, pageSize = 10 } = unref(getPaginationInfo)
|
||||||
|
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
|
||||||
|
pageParams = {}
|
||||||
|
} else {
|
||||||
|
pageParams[pageField] = (opt && opt[pageField]) || page
|
||||||
|
pageParams[sizeField] = pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...pageParams
|
||||||
|
}
|
||||||
|
const res = await request(params)
|
||||||
|
console.log('res', res)
|
||||||
|
const resultTotal = res[totalField] || 0
|
||||||
|
const currentPage = res[pageField]
|
||||||
|
|
||||||
|
// // 如果数据异常,需获取正确的页码再次执行
|
||||||
|
// if (resultTotal) {
|
||||||
|
// if (page > resultTotal) {
|
||||||
|
// setPagination({
|
||||||
|
// [pageField]: resultTotal
|
||||||
|
// })
|
||||||
|
// fetch(opt)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
const resultInfo = res[listField] ? res[listField] : []
|
||||||
|
dataSourceRef.value = resultInfo
|
||||||
|
setPagination({
|
||||||
|
[pageField]: currentPage,
|
||||||
|
[totalField]: resultTotal
|
||||||
|
})
|
||||||
|
// if (opt && opt[pageField]) {
|
||||||
|
// setPagination({
|
||||||
|
// [pageField]: opt[pageField] || 1
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
emit('fetch-success', {
|
||||||
|
items: unref(resultInfo),
|
||||||
|
resultTotal
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
// emit('fetch-error', error)
|
||||||
|
// dataSourceRef.value = []
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDataSourceRef = computed(() => {
|
||||||
|
const dataSource = unref(dataSourceRef)
|
||||||
|
if (!dataSource || dataSource.length === 0) {
|
||||||
|
return unref(dataSourceRef)
|
||||||
|
}
|
||||||
|
return unref(dataSourceRef)
|
||||||
|
})
|
||||||
|
|
||||||
|
function getDataSource() {
|
||||||
|
console.log(getDataSourceRef.value)
|
||||||
|
return getDataSourceRef.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTableData(values) {
|
||||||
|
dataSourceRef.value = values
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
fetch()
|
||||||
|
}, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
getDataSourceRef,
|
||||||
|
getDataSource,
|
||||||
|
setTableData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { computed, unref, ref } from 'vue'
|
||||||
|
import { isBoolean } from '@/utils/is'
|
||||||
|
|
||||||
|
export function usePagination(refProps) {
|
||||||
|
const configRef = ref({})
|
||||||
|
const show = ref(true)
|
||||||
|
console.log('configRef', configRef)
|
||||||
|
console.log('refProps', refProps)
|
||||||
|
const getPaginationInfo = computed(() => {
|
||||||
|
const { pagination, paginationSetting } = unref(refProps)
|
||||||
|
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pageSize: paginationSetting.defaultPageSize,
|
||||||
|
pageSizes: paginationSetting.pageSizes,
|
||||||
|
showSizePicker: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
...(isBoolean(pagination) ? {} : pagination),
|
||||||
|
...unref(configRef),
|
||||||
|
pageCount: unref(configRef)[paginationSetting.totalField]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function setPagination(info) {
|
||||||
|
const paginationInfo = unref(getPaginationInfo)
|
||||||
|
configRef.value = {
|
||||||
|
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
|
||||||
|
...info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { getPaginationInfo, setPagination }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template />
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { isNullOrUndef } from '@/utils/is'
|
||||||
|
import { useDialog } from 'naive-ui'
|
||||||
|
|
||||||
|
const NDialog = useDialog()
|
||||||
|
|
||||||
|
class Dialog {
|
||||||
|
success(title, option) {
|
||||||
|
this.showDialog('success', { title, ...option })
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(title, option) {
|
||||||
|
this.showDialog('warning', { title, ...option })
|
||||||
|
}
|
||||||
|
|
||||||
|
error(title, option) {
|
||||||
|
this.showDialog('error', { title, ...option })
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(type = 'success', option) {
|
||||||
|
if (isNullOrUndef(option.title)) {
|
||||||
|
// ! 没有title的情况
|
||||||
|
option.showIcon = false
|
||||||
|
}
|
||||||
|
NDialog[type]({
|
||||||
|
positiveText: 'OK',
|
||||||
|
closable: false,
|
||||||
|
...option
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm(option = {}) {
|
||||||
|
this.showDialog(option.type || 'error', {
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: option.confirm,
|
||||||
|
onNegativeClick: option.cancel,
|
||||||
|
onMaskClick: option.cancel,
|
||||||
|
...option
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window['$dialog'] = new Dialog()
|
||||||
|
Object.freeze(window.$dialog)
|
||||||
|
Object.defineProperty(window, '$dialog', {
|
||||||
|
configurable: false,
|
||||||
|
writable: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div />
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LoadingBar'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script setup>
|
||||||
|
import { useLoadingBar } from 'naive-ui'
|
||||||
|
window['$loadingBar'] = useLoadingBar()
|
||||||
|
Object.defineProperty(window, '$loadingBar', {
|
||||||
|
configurable: false,
|
||||||
|
writable: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template />
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
|
||||||
|
const NMessage = useMessage()
|
||||||
|
|
||||||
|
let loadingMessage = null
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
/**
|
||||||
|
* 规则:
|
||||||
|
* * loading message只显示一个,新的message会替换正在显示的loading message
|
||||||
|
* * loading message不会自动清除,除非被替换成非loading message,非loading message默认2秒后自动清除
|
||||||
|
*/
|
||||||
|
|
||||||
|
removeMessage(message, duration = 2000) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (message) {
|
||||||
|
message.destroy()
|
||||||
|
message = null
|
||||||
|
}
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessage(type, content, option = {}) {
|
||||||
|
if (loadingMessage && loadingMessage.type === 'loading') {
|
||||||
|
// 如果存在则替换正在显示的loading message
|
||||||
|
loadingMessage.type = type
|
||||||
|
loadingMessage.content = content
|
||||||
|
|
||||||
|
if (type !== 'loading') {
|
||||||
|
// 非loading message需设置自动清除
|
||||||
|
this.removeMessage(loadingMessage, option.duration)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不存在正在显示的loading则新建一个message,如果新建的message是loading message则将message赋值存储下来
|
||||||
|
const message = NMessage[type](content, option)
|
||||||
|
if (type === 'loading') {
|
||||||
|
loadingMessage = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loading(content) {
|
||||||
|
this.showMessage('loading', content, { duration: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
success(content, option = {}) {
|
||||||
|
this.showMessage('success', content, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
error(content, option = {}) {
|
||||||
|
this.showMessage('error', content, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
info(content, option = {}) {
|
||||||
|
this.showMessage('info', content, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(content, option = {}) {
|
||||||
|
this.showMessage('warning', content, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window['$message'] = new Message()
|
||||||
|
|
||||||
|
Object.defineProperty(window, '$message', {
|
||||||
|
configurable: false,
|
||||||
|
writable: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template>
|
||||||
|
<n-layout-header class="layout__header" bordered>
|
||||||
|
Header Header Header
|
||||||
|
<!-- <SideMenu menu-mode="horizontal" /> -->
|
||||||
|
</n-layout-header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SideMenu from '@/layout/components/Menu/index.vue'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'LayoutHeader',
|
||||||
|
components: { SideMenu }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<n-menu
|
||||||
|
:mode="menuMode"
|
||||||
|
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
||||||
|
:options="getMenuOptions"
|
||||||
|
@update:value="handleMenuSelect"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { computed, defineProps, toRaw } from 'vue'
|
||||||
|
import { isExternal } from '@/utils/is.js'
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
menuMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'vertical'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuMode = toRaw(props.menuMode)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { currentRoute } = router
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
const getMenuOptions = computed(() => {
|
||||||
|
return generateOptions(permissionStore.routes, '')
|
||||||
|
})
|
||||||
|
|
||||||
|
function resolvePath(basePath, path) {
|
||||||
|
if (isExternal(path)) return path
|
||||||
|
return (
|
||||||
|
'/' +
|
||||||
|
[basePath, path]
|
||||||
|
.filter((path) => !!path && path !== '/')
|
||||||
|
.map((path) => path.replace(/(^\/)|(\/$)/g, ''))
|
||||||
|
.join('/')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOptions(routes, basePath) {
|
||||||
|
const options = []
|
||||||
|
routes.forEach((route) => {
|
||||||
|
if (route.name && !route.isHidden) {
|
||||||
|
const curOption = {
|
||||||
|
label: (route.meta && route.meta.title) || route.name,
|
||||||
|
key: route.name,
|
||||||
|
path: resolvePath(basePath, route.path)
|
||||||
|
}
|
||||||
|
if (route.children && route.children.length) {
|
||||||
|
curOption.children = generateOptions(route.children, resolvePath(basePath, route.path))
|
||||||
|
}
|
||||||
|
options.push(curOption)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMenuSelect(key, item) {
|
||||||
|
if (isExternal(item.path)) {
|
||||||
|
window.open(item.path)
|
||||||
|
} else {
|
||||||
|
router.push(item.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<n-layout-sider
|
||||||
|
class="layout_sidebar"
|
||||||
|
v-bind="getSideOptions"
|
||||||
|
>
|
||||||
|
<div class="project__logo">
|
||||||
|
11
|
||||||
|
</div>
|
||||||
|
<SideMenu />
|
||||||
|
</n-layout-sider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, toRaw } from 'vue'
|
||||||
|
import SideMenu from '@/layout/components/Menu/index.vue'
|
||||||
|
import { useSettingStore } from '@/store/modules/setting.js'
|
||||||
|
const settingStore = useSettingStore()
|
||||||
|
|
||||||
|
const getSideOptions = computed(() => {
|
||||||
|
return toRaw(settingStore.getSidebarSetting)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
<template>
|
||||||
|
<div class="tabs-view" :class="{'tabs-view-fix': tagsMenuSetting.fixed,}">
|
||||||
|
<div class="tabs-view-main">
|
||||||
|
<div ref="navWrap" class="tabs-card" :class="{ 'tabs-card-scrollable': scrollable }">
|
||||||
|
<span class="tabs-card-prev" :class="{ 'tabs-card-prev-hide': !scrollable }" @click="scrollPrev">
|
||||||
|
<n-icon size="16" color="#515a6e">
|
||||||
|
<LeftOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</span>
|
||||||
|
<span class="tabs-card-next" :class="{ 'tabs-card-next-hide': !scrollable }" @click="scrollNext">
|
||||||
|
<n-icon size="16" color="#515a6e">
|
||||||
|
<RightOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</span>
|
||||||
|
<div ref="navScroll" class="tabs-card-scroll">
|
||||||
|
<Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex">
|
||||||
|
<template #item="{ element }">
|
||||||
|
<div
|
||||||
|
:id="`tag${element.fullPath.split('/').join('\/')}`"
|
||||||
|
class="tabs-card-scroll-item"
|
||||||
|
:class="{ 'active-item': activeKey === element.path }"
|
||||||
|
@click.stop="goPage(element)"
|
||||||
|
@contextmenu="handleContextMenu($event, element)"
|
||||||
|
>
|
||||||
|
<span>{{ element.meta.title }}</span>
|
||||||
|
<n-icon v-if="!element.meta.affix" size="14" @click.stop="closeTabItem(element)">
|
||||||
|
<CloseOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
LeftOutlined,
|
||||||
|
RightOutlined,
|
||||||
|
CloseOutlined
|
||||||
|
} from '@vicons/antd'
|
||||||
|
import Draggable from 'vuedraggable'
|
||||||
|
import { reactive, computed, toRaw } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useTagsMenuStore } from '@/store/modules/tagsMenu.js'
|
||||||
|
import { useSettingStore } from '@/store/modules/setting.js'
|
||||||
|
import { getTags, setTags } from '@/utils/tags.js'
|
||||||
|
|
||||||
|
/* 获取路由器 */
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const tagsMenuStore = useTagsMenuStore()
|
||||||
|
const settingStore = useSettingStore()
|
||||||
|
const tabsList = computed(() => tagsMenuStore.tabsList)
|
||||||
|
const tagsMenuSetting = computed(() => settingStore.getTagsMenuSetting)
|
||||||
|
|
||||||
|
let cacheRoutes = []
|
||||||
|
const simpleRoute = getSimpleRoute(route)
|
||||||
|
try {
|
||||||
|
const routesStr = getTags()
|
||||||
|
cacheRoutes = routesStr ? JSON.parse(routesStr) : [simpleRoute]
|
||||||
|
} catch (e) {
|
||||||
|
cacheRoutes = [simpleRoute]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 同步路由信息 */
|
||||||
|
const routes = router.getRoutes()
|
||||||
|
cacheRoutes.forEach((cacheRoute) => {
|
||||||
|
const findRoute = routes.find((route) => route.path === cacheRoute.path)
|
||||||
|
if (route) {
|
||||||
|
cacheRoute.meta = findRoute.meta || cacheRoute.meta
|
||||||
|
cacheRoute.name = (findRoute.name || cacheRoute.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
tagsMenuStore.initTabs(cacheRoutes)
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
activeKey: route.fullPath,
|
||||||
|
scrollable: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
* @param { Object } route
|
||||||
|
* @return { Object }
|
||||||
|
*/
|
||||||
|
function getSimpleRoute(route) {
|
||||||
|
const { fullPath, hash, meta, name, params, path, query } = route
|
||||||
|
return { fullPath, hash, meta, name, params, path, query }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 左侧滚动
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function scrollPrev() {
|
||||||
|
// const containerWidth = navScroll.value.offsetWidth
|
||||||
|
// const currentScroll = navScroll.value.scrollLeft
|
||||||
|
// if (!currentScroll) return
|
||||||
|
// const scrollLeft = currentScroll > containerWidth ? currentScroll - containerWidth : 0
|
||||||
|
// scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description: 右侧滚动
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function scrollNext() {
|
||||||
|
// const containerWidth = navScroll.value.offsetWidth
|
||||||
|
// const currentScroll = navScroll.value.scrollLeft
|
||||||
|
// if (!currentScroll) return
|
||||||
|
// const scrollLeft = currentScroll > containerWidth ? currentScroll - containerWidth : 0
|
||||||
|
// scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description: 页面跳转
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function goPage(e) {
|
||||||
|
// const { fullPath } = e
|
||||||
|
// if (fullPath === route.fullPath) return
|
||||||
|
// state.activeKey = fullPath
|
||||||
|
// go(e, true)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description: 删除项
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
// 删除tab
|
||||||
|
function closeTabItem(e) {
|
||||||
|
// const { fullPath } = e
|
||||||
|
// const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath)
|
||||||
|
// removeTab(routeInfo)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description: 右键菜单
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function handleContextMenu(e, item) {
|
||||||
|
// e.preventDefault()
|
||||||
|
// isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path
|
||||||
|
// state.showDropdown = false
|
||||||
|
// nextTick().then(() => {
|
||||||
|
// state.showDropdown = true
|
||||||
|
// state.dropdownX = e.clientX
|
||||||
|
// state.dropdownY = e.clientY
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
.tabs-view {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 0;
|
||||||
|
display: flex;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
.tabs-view-main {
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
.tabs-card{
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
&.tabs-card-scrollable {
|
||||||
|
padding: 0 32px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tabs-card-prev,
|
||||||
|
.tabs-card-next {
|
||||||
|
width: 32px;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
line-height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
.n-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tabs-card-scroll {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
&-item {
|
||||||
|
background: v-bind(getCardColor);
|
||||||
|
color: v-bind(getBaseColor);
|
||||||
|
height: 32px;
|
||||||
|
padding: 6px 16px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-right: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
span {
|
||||||
|
float: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #515a6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-icon {
|
||||||
|
height: 22px;
|
||||||
|
width: 21px;
|
||||||
|
margin-right: -6px;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
color: #808695;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #515a6e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 21px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .active-item {
|
||||||
|
// color: v-bind(getAppTheme);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-view-fix {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 5;
|
||||||
|
padding: 6px 19px 6px 10px;
|
||||||
|
left: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-view-default-background {
|
||||||
|
background: #f5f7f9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<!-- <n-space class="layout" :size="0" vertical>
|
||||||
|
<n-layout>
|
||||||
|
<Header />
|
||||||
|
</n-layout>
|
||||||
|
<n-layout has-sider>
|
||||||
|
<SideBar />
|
||||||
|
<n-layout class="layout__content">
|
||||||
|
<Bread />
|
||||||
|
<router-view />
|
||||||
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
|
</n-space> -->
|
||||||
|
<n-space class="layout" :size="0" vertical>
|
||||||
|
<n-layout has-sider>
|
||||||
|
<SideBar v-if="menuMode === 'sidebar'" />
|
||||||
|
<n-layout>
|
||||||
|
<Header />
|
||||||
|
<Tags v-if="tagsMenuSetting.show" />
|
||||||
|
<n-layout class="layout__content">
|
||||||
|
<router-view />
|
||||||
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// import { reactive } from 'vue'
|
||||||
|
import Header from './components/Header/index.vue'
|
||||||
|
import SideBar from './components/Sidebar/index.vue'
|
||||||
|
import Tags from './components/Tags/index.vue'
|
||||||
|
import { useSettingStore } from '@/store/modules/setting.js'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
const settingStore = useSettingStore()
|
||||||
|
const menuMode = computed(() => settingStore.getMenuMode)
|
||||||
|
const tagsMenuSetting = computed(() => settingStore.getTagsMenuSetting)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import '@/styles/index.scss'
|
||||||
|
import 'uno.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { setupRouter } from '@/router'
|
||||||
|
import { setupStore } from '@/store'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
function setupApp() {
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
setupStore(app)
|
||||||
|
setupRouter(app)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
}
|
||||||
|
|
||||||
|
setupApp()
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { createPageLoadingGuard } from './page-loading-guard'
|
||||||
|
import { createPageTitleGuard } from './page-title-guard'
|
||||||
|
import { createPermissionGuard } from './permission-guard'
|
||||||
|
|
||||||
|
export function setupRouterGuard(router) {
|
||||||
|
createPageLoadingGuard(router)
|
||||||
|
createPermissionGuard(router)
|
||||||
|
createPageTitleGuard(router)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
export function createPageLoadingGuard(router) {
|
||||||
|
router.beforeEach(() => {
|
||||||
|
window.$loadingBar?.start()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.$loadingBar?.finish()
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.onError(() => {
|
||||||
|
window.$loadingBar?.error()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
const baseTitle = import.meta.env.VITE_APP_TITLE
|
||||||
|
|
||||||
|
export function createPageTitleGuard(router) {
|
||||||
|
router.afterEach((to) => {
|
||||||
|
const pageTitle = to.meta?.title
|
||||||
|
if (pageTitle) {
|
||||||
|
document.title = `${pageTitle} | ${baseTitle}`
|
||||||
|
} else {
|
||||||
|
document.title = baseTitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import { NOT_FOUND_ROUTE } from '@/router/routes'
|
||||||
|
import { getToken } from '@/utils/token'
|
||||||
|
|
||||||
|
const WHITE_LIST = ['/login', '/redirect']
|
||||||
|
export function createPermissionGuard(router) {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
router.beforeEach(async(to, from, next) => {
|
||||||
|
const token = getToken()
|
||||||
|
if (token) {
|
||||||
|
if (to.path === '/login') {
|
||||||
|
next({ path: '/' })
|
||||||
|
} else {
|
||||||
|
const hasRoutes = !!permissionStore.permissionRoutes.length
|
||||||
|
console.log(permissionStore.permissionRoutes)
|
||||||
|
if (hasRoutes) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// await userStore.getUserInfo()
|
||||||
|
const routes = await permissionStore.generateRoutes()
|
||||||
|
router.addRoute(routes[0])
|
||||||
|
router.addRoute(NOT_FOUND_ROUTE)
|
||||||
|
next({ ...to, replace: true })
|
||||||
|
} catch (error) {
|
||||||
|
// removeToken()
|
||||||
|
// $message.error(error)
|
||||||
|
next({ path: '/login', query: { ...to.query, redirect: to.path }})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (WHITE_LIST.includes(to.path)) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next({ path: '/login', query: { ...to.query, redirect: to.path }})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { setupRouterGuard } from './guard'
|
||||||
|
import { basicRoutes } from './routes'
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHistory('/'),
|
||||||
|
routes: basicRoutes,
|
||||||
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export function resetRouter() {
|
||||||
|
router.getRoutes().forEach((route) => {
|
||||||
|
const { name } = route
|
||||||
|
router.hasRoute(name) && router.removeRoute(name)
|
||||||
|
})
|
||||||
|
basicRoutes.forEach((route) => {
|
||||||
|
!router.hasRoute(route.name) && router.addRoute(route)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupRouter(app) {
|
||||||
|
app.use(router)
|
||||||
|
setupRouterGuard(router)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
import Home from '@/views/dashboard/index.vue'
|
||||||
|
|
||||||
|
export const basicRoutes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: () => import('@/views/login/index.vue'),
|
||||||
|
isHidden: true,
|
||||||
|
meta: {
|
||||||
|
title: '登录页'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Dashboard',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/home',
|
||||||
|
meta: {
|
||||||
|
title: 'Dashboard'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
name: 'Home',
|
||||||
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
title: '首页'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const NOT_FOUND_ROUTE = {
|
||||||
|
name: 'NOT_FOUND',
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
redirect: '/404',
|
||||||
|
isHidden: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const modules = import.meta.globEager('./modules/*.js')
|
||||||
|
const asyncRoutes = []
|
||||||
|
Object.keys(modules).forEach((key) => {
|
||||||
|
asyncRoutes.push(...modules[key].default)
|
||||||
|
})
|
||||||
|
|
||||||
|
export { asyncRoutes }
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/system',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/system/menu',
|
||||||
|
name: 'System',
|
||||||
|
meta: {
|
||||||
|
title: '系统管理'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'menu',
|
||||||
|
component: () => import('@/views/system/menu/index.vue'),
|
||||||
|
name: 'SystemMenu',
|
||||||
|
meta: {
|
||||||
|
title: '菜单管理'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
const setting = {
|
||||||
|
/* 布局模式 vertical / horizontal */
|
||||||
|
/* layoutMode: 'vertical', */
|
||||||
|
/* 导航模式 sidebar / header */
|
||||||
|
menuMode: 'sidebar',
|
||||||
|
headerSetting: {
|
||||||
|
isReload: true
|
||||||
|
},
|
||||||
|
/* 侧边栏属性 */
|
||||||
|
sidebarSetting: {
|
||||||
|
isBorder: true,
|
||||||
|
isTrigger: false,
|
||||||
|
mode: 'width',
|
||||||
|
cWidth: 48,
|
||||||
|
width: 212,
|
||||||
|
isScroll: false
|
||||||
|
},
|
||||||
|
/* tags */
|
||||||
|
tagsMenuSetting: {
|
||||||
|
show: true,
|
||||||
|
fixed: true,
|
||||||
|
background: '#f5f7f9'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default setting
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
export function setupStore(app) {
|
||||||
|
app.use(createPinia())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
themeOverrides: {
|
||||||
|
common: {
|
||||||
|
primaryColor: '#316c72',
|
||||||
|
primaryColorSuppl: '#316c72',
|
||||||
|
primaryColorHover: '#316c72',
|
||||||
|
successColorHover: '#316c72',
|
||||||
|
successColorSuppl: '#316c72'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { asyncRoutes, basicRoutes } from '@/router/routes'
|
||||||
|
import { getMenu } from '@/api/system'
|
||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
import modules from '@/utils/module.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
* @param {*} route
|
||||||
|
* @param {*} role
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function hasPermission(route, role) {
|
||||||
|
// const routeRole = route.meta?.role ? route.meta.role : []
|
||||||
|
// if (!role.length || !routeRole.length) {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// return role.some((item) => routeRole.includes(item))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 过滤权限路由
|
||||||
|
* @param {*} routes
|
||||||
|
* @param {*} role
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function filterAsyncRoutes(routes = [], role) {
|
||||||
|
const ret = []
|
||||||
|
routes.forEach((route) => {
|
||||||
|
if (hasPermission(route, role)) {
|
||||||
|
const curRoute = {
|
||||||
|
...route,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
if (route.children && route.children.length) {
|
||||||
|
curRoute.children = filterAsyncRoutes(route.children, role)
|
||||||
|
} else {
|
||||||
|
Reflect.deleteProperty(curRoute, 'children')
|
||||||
|
}
|
||||||
|
ret.push(curRoute)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description:
|
||||||
|
* @param {*} routes
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function dataArrayToRoutes(routes) {
|
||||||
|
const res = []
|
||||||
|
routes.forEach(item => {
|
||||||
|
const tmp = { ...item }
|
||||||
|
// 如果有component配置
|
||||||
|
if (tmp.component) {
|
||||||
|
// Layout引入
|
||||||
|
if (tmp.component === 'Layout') {
|
||||||
|
tmp.component = Layout
|
||||||
|
} else {
|
||||||
|
const sub_view = tmp.component.replace(/^\/*/g, '')
|
||||||
|
const component = `../${sub_view}.vue`
|
||||||
|
tmp.component = modules[component]
|
||||||
|
}
|
||||||
|
if (tmp.children) {
|
||||||
|
tmp.children = dataArrayToRoutes(tmp.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push(tmp)
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePermissionStore = defineStore('permission', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
accessRoutes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
routes() {
|
||||||
|
return basicRoutes.concat(this.accessRoutes)
|
||||||
|
},
|
||||||
|
permissionRoutes() {
|
||||||
|
return this.accessRoutes
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
generateRoutesMock(role = []) {
|
||||||
|
const accessRoutes = filterAsyncRoutes(asyncRoutes, role)
|
||||||
|
this.accessRoutes = accessRoutes
|
||||||
|
return accessRoutes
|
||||||
|
},
|
||||||
|
async generateRoutes() {
|
||||||
|
try {
|
||||||
|
const res = await getMenu()
|
||||||
|
if (res.code === 0) {
|
||||||
|
const result = dataArrayToRoutes(res.data)
|
||||||
|
this.accessRoutes = result
|
||||||
|
return Promise.resolve(result)
|
||||||
|
} else {
|
||||||
|
return Promise.reject(res.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return Promise.reject(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import config from '@/setting/config.js'
|
||||||
|
|
||||||
|
export const useSettingStore = defineStore({
|
||||||
|
id: 'project-setting',
|
||||||
|
state: () => ({
|
||||||
|
...config
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getMenuMode() {
|
||||||
|
return this.menuMode
|
||||||
|
},
|
||||||
|
getHeaderSetting() {
|
||||||
|
return this.headerSetting
|
||||||
|
},
|
||||||
|
getSidebarSetting() {
|
||||||
|
return this.sidebarSetting
|
||||||
|
},
|
||||||
|
getTagsMenuSetting() {
|
||||||
|
return this.tagsMenuSetting
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
// 不需要出现在标签页中的路由
|
||||||
|
const whiteList = ['Redirect', 'login']
|
||||||
|
|
||||||
|
// 保留固定路由
|
||||||
|
function retainAffixRoute(list) {
|
||||||
|
return list.filter((item) => item?.meta?.affix ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTagsMenuStore = defineStore({
|
||||||
|
id: 'project-tags-menu',
|
||||||
|
state: () => ({
|
||||||
|
tabsList: []
|
||||||
|
}),
|
||||||
|
getters: {},
|
||||||
|
actions: {
|
||||||
|
initTabs(routes) {
|
||||||
|
// 初始化标签页
|
||||||
|
this.tabsList = routes
|
||||||
|
},
|
||||||
|
addTabs(route) {
|
||||||
|
// 添加标签页
|
||||||
|
if (whiteList.includes(route.name)) return false
|
||||||
|
const isExists = this.tabsList.some((item) => item.fullPath === route.fullPath)
|
||||||
|
if (!isExists) {
|
||||||
|
this.tabsList.push(route)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
closeLeftTabs(route) {
|
||||||
|
// 关闭左侧
|
||||||
|
const index = this.tabsList.findIndex((item) => item.fullPath === route.fullPath)
|
||||||
|
this.tabsList.splice(0, index)
|
||||||
|
},
|
||||||
|
closeRightTabs(route) {
|
||||||
|
// 关闭右侧
|
||||||
|
const index = this.tabsList.findIndex((item) => item.fullPath === route.fullPath)
|
||||||
|
this.tabsList.splice(index + 1)
|
||||||
|
},
|
||||||
|
closeOtherTabs(route) {
|
||||||
|
// 关闭其他
|
||||||
|
this.tabsList = this.tabsList.filter((item) => item.fullPath === route.fullPath)
|
||||||
|
},
|
||||||
|
closeCurrentTab(route) {
|
||||||
|
// 关闭当前页
|
||||||
|
const index = this.tabsList.findIndex((item) => item.fullPath === route.fullPath)
|
||||||
|
this.tabsList.splice(index, 1)
|
||||||
|
},
|
||||||
|
closeAllTabs() {
|
||||||
|
// 关闭全部
|
||||||
|
console.log(retainAffixRoute(this.tabsList))
|
||||||
|
this.tabsList = retainAffixRoute(this.tabsList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { getUser } from '@/api/user'
|
||||||
|
import { removeToken } from '@/utils/token'
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
userInfo: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
userId() {
|
||||||
|
return this.userInfo?.id
|
||||||
|
},
|
||||||
|
name() {
|
||||||
|
return this.userInfo?.name
|
||||||
|
},
|
||||||
|
avatar() {
|
||||||
|
return this.userInfo?.avatar
|
||||||
|
},
|
||||||
|
role() {
|
||||||
|
return this.userInfo?.role || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async getUserInfo() {
|
||||||
|
try {
|
||||||
|
const res = await getUser()
|
||||||
|
if (res.code === 0) {
|
||||||
|
const { id, name, avatar, role } = res.data
|
||||||
|
this.userInfo = { id, name, avatar, role }
|
||||||
|
return Promise.resolve(res.data)
|
||||||
|
} else {
|
||||||
|
return Promise.reject(res.message)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return Promise.reject(error.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
removeToken()
|
||||||
|
this.userInfo = {}
|
||||||
|
},
|
||||||
|
setUserInfo(userInfo = {}) {
|
||||||
|
this.userInfo = { ...this.userInfo, ...userInfo }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@import './reset.scss';
|
||||||
|
@import './public.scss';
|
||||||
|
@import './layout.scss';
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
.layout,.n-space{
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout{
|
||||||
|
.layout__header{
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
.layout__content{
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
html {
|
||||||
|
font-size: 4px; // * 1rem = 4px 方便unocss计算:在unocss中 1字体单位 = 0.25rem,相当于 1等份 = 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-family: 'Encode Sans Condensed', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* router view transition fade-slide */
|
||||||
|
.fade-slide-leave-active,
|
||||||
|
.fade-slide-enter-active {
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条凹槽的颜色,还可以设置边框属性 */
|
||||||
|
*::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
-webkit-border-radius: 2em;
|
||||||
|
-moz-border-radius: 2em;
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条的宽度 */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条的设置 */
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #ddd;
|
||||||
|
background-clip: padding-box;
|
||||||
|
-webkit-border-radius: 2em;
|
||||||
|
-moz-border-radius: 2em;
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条鼠标移上去 */
|
||||||
|
*::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #bbb;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a:link,
|
||||||
|
a:visited,
|
||||||
|
a:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
$primaryColor: #316c72;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--vh100: 100vh;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { createWebStorage } from './web-storage'
|
||||||
|
|
||||||
|
export const createLocalStorage = function(option = {}) {
|
||||||
|
return createWebStorage({ prefixKey: option.prefixKey || '', storage: localStorage })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createSessionStorage = function(option = {}) {
|
||||||
|
return createWebStorage({ prefixKey: option.prefixKey || '', storage: sessionStorage })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { isNullOrUndef } from '@/utils/is'
|
||||||
|
|
||||||
|
class WebStorage {
|
||||||
|
constructor(option) {
|
||||||
|
this.storage = option.storage
|
||||||
|
this.prefixKey = option.prefixKey
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey(key) {
|
||||||
|
return `${this.prefixKey}${key}`.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value, expire) {
|
||||||
|
const stringData = JSON.stringify({
|
||||||
|
value,
|
||||||
|
time: Date.now(),
|
||||||
|
expire: !isNullOrUndef(expire) ? new Date().getTime() + expire * 1000 : null
|
||||||
|
})
|
||||||
|
this.storage.setItem(this.getKey(key), stringData)
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
const { value } = this.getItem(key, {})
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
getItem(key, def = null) {
|
||||||
|
const val = this.storage.getItem(this.getKey(key))
|
||||||
|
if (!val) return def
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(val)
|
||||||
|
const { value, time, expire } = data
|
||||||
|
if (isNullOrUndef(expire) || expire > new Date().getTime()) {
|
||||||
|
return { value, time }
|
||||||
|
}
|
||||||
|
this.remove(key)
|
||||||
|
return def
|
||||||
|
} catch (error) {
|
||||||
|
this.remove(key)
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
this.storage.removeItem(this.getKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.storage.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWebStorage({ prefixKey = '', storage = sessionStorage }) {
|
||||||
|
return new WebStorage({ prefixKey, storage })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
|
const WITHOUT_TOKEN_API = [{ url: '/login/login', method: 'POST' }, { url: '/login/captcha', method: 'GET' }]
|
||||||
|
|
||||||
|
export function isWithoutToken({ url, method = '' }) {
|
||||||
|
return WITHOUT_TOKEN_API.some((item) => item.url === url && item.method === method.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBaseParams(params) {
|
||||||
|
if (!params.userId) {
|
||||||
|
params.userId = useUserStore().userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import { setupInterceptor } from './interceptors'
|
||||||
|
|
||||||
|
function createAxios(option = {}) {
|
||||||
|
const defBaseURL = window.__APP__GLOB__CONF__?.VITE_APP_GLOB_BASE_API || import.meta.env.VITE_APP_GLOB_BASE_API
|
||||||
|
const service = axios.create({
|
||||||
|
timeout: option.timeout || 120000,
|
||||||
|
baseURL: option.baseURL || defBaseURL
|
||||||
|
})
|
||||||
|
setupInterceptor(service)
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defAxios = createAxios()
|
||||||
|
|
||||||
|
export const mockAxios = createAxios({
|
||||||
|
baseURL: window.__APP__GLOB__CONF__?.VITE_APP_GLOB_BASE_API_MOCK || import.meta.env.VITE_APP_GLOB_BASE_API_MOCK
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { router } from '@/router'
|
||||||
|
import { getToken, removeToken } from '@/utils/token'
|
||||||
|
import { isWithoutToken } from './help'
|
||||||
|
|
||||||
|
export function setupInterceptor(service) {
|
||||||
|
service.interceptors.request.use(
|
||||||
|
async(config) => {
|
||||||
|
// 防止缓存,给get请求加上时间戳
|
||||||
|
if (config.method === 'get') {
|
||||||
|
config.params = { ...config.params, t: new Date().getTime() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理不需要token的请求
|
||||||
|
if (isWithoutToken(config)) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getToken()
|
||||||
|
if (token) {
|
||||||
|
/**
|
||||||
|
* * jwt token
|
||||||
|
* ! 认证方案: Bearer
|
||||||
|
*/
|
||||||
|
config.headers.Authorization = 'Bearer ' + token
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* * 未登录或者token过期的情况下
|
||||||
|
* * 跳转登录页重新登录,携带当前路由及参数,登录成功会回到原来的页面
|
||||||
|
*/
|
||||||
|
const { currentRoute } = router
|
||||||
|
router.replace({
|
||||||
|
path: '/login',
|
||||||
|
query: { ...currentRoute.query, redirect: currentRoute.path }
|
||||||
|
})
|
||||||
|
return Promise.reject({ code: '-1', message: '未登录' })
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error)
|
||||||
|
)
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response) => response?.data,
|
||||||
|
(error) => {
|
||||||
|
const { code, message } = error.response?.data
|
||||||
|
return Promise.reject({ code, message })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO 此处可以根据后端返回的错误码自定义框架层面的错误处理
|
||||||
|
*/
|
||||||
|
switch (code) {
|
||||||
|
case 401:
|
||||||
|
// 未登录(可能是token过期或者无效了)
|
||||||
|
console.error(message)
|
||||||
|
removeToken()
|
||||||
|
const { currentRoute } = router
|
||||||
|
router.replace({
|
||||||
|
path: '/login',
|
||||||
|
query: { ...currentRoute.query, redirect: currentRoute.path }
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
// 没有权限
|
||||||
|
console.error(message)
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
// 资源不存在
|
||||||
|
console.error(message)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 已知错误resolve,在业务代码中作提醒,未知错误reject,捕获错误统一提示接口异常(9000以上为业务类型错误,需要跟后端确定好)
|
||||||
|
if ([401, 403, 404].includes(code) || code >= 9000) {
|
||||||
|
return Promise.resolve({ code, message })
|
||||||
|
} else {
|
||||||
|
console.error('【err】' + error)
|
||||||
|
return Promise.reject({ message: '接口异常,请稍后重试!' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc 格式化时间
|
||||||
|
* @param {(Object|string|number)} time
|
||||||
|
* @param {string} format
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
export function formatDateTime(time = undefined, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
|
return dayjs(time).format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(date = undefined, format = 'YYYY-MM-DD') {
|
||||||
|
return formatDateTime(date, format)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc 函数节流
|
||||||
|
* @param {Function} fn
|
||||||
|
* @param {Number} wait
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function throttle(fn, wait) {
|
||||||
|
var context, args
|
||||||
|
var previous = 0
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
var now = +new Date()
|
||||||
|
context = this
|
||||||
|
args = arguments
|
||||||
|
if (now - previous > wait) {
|
||||||
|
fn.apply(context, args)
|
||||||
|
previous = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc 函数防抖
|
||||||
|
* @param {Function} func
|
||||||
|
* @param {number} wait
|
||||||
|
* @param {boolean} immediate
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export function debounce(method, wait, immediate) {
|
||||||
|
let timeout
|
||||||
|
return function(...args) {
|
||||||
|
const context = this
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
// 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null
|
||||||
|
if (immediate) {
|
||||||
|
/**
|
||||||
|
* 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null
|
||||||
|
* 这样确保立即执行后wait毫秒内不会被再次触发
|
||||||
|
*/
|
||||||
|
const callNow = !timeout
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
}, wait)
|
||||||
|
if (callNow) {
|
||||||
|
method.apply(context, args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果immediate为false,则函数wait毫秒后执行
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
/**
|
||||||
|
* args是一个类数组对象,所以使用fn.apply
|
||||||
|
* 也可写作method.call(context, ...args)
|
||||||
|
*/
|
||||||
|
method.apply(context, args)
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
const toString = Object.prototype.toString
|
||||||
|
|
||||||
|
export function is(val, type) {
|
||||||
|
return toString.call(val) === `[object ${type}]`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDef(val) {
|
||||||
|
return typeof val !== 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUndef(val) {
|
||||||
|
return typeof val === 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNull(val) {
|
||||||
|
return val === null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObject(val) {
|
||||||
|
return !isNull(val) && is(val, 'Object')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArray(val) {
|
||||||
|
return val && Array.isArray(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isString(val) {
|
||||||
|
return is(val, 'String')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNumber(val) {
|
||||||
|
return is(val, 'Number')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBoolean(val) {
|
||||||
|
return is(val, 'Boolean')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDate(val) {
|
||||||
|
return is(val, 'Date')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRegExp(val) {
|
||||||
|
return is(val, 'RegExp')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunction(val) {
|
||||||
|
return typeof val === 'function'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPromise(val) {
|
||||||
|
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isElement(val) {
|
||||||
|
return isObject(val) && !!val.tagName
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindow(val) {
|
||||||
|
return typeof window !== 'undefined' && isDef(window) && is(val, 'Window')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNullOrUndef(val) {
|
||||||
|
return isNull(val) || isUndef(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmpty(val) {
|
||||||
|
if (isArray(val) || isString(val)) {
|
||||||
|
return val.length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val instanceof Map || val instanceof Set) {
|
||||||
|
return val.size === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(val)) {
|
||||||
|
return Object.keys(val).length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * 类似sql的isnull函数
|
||||||
|
* * 第一个参数为null/undefined/''则返回第二个参数作为默认值,否则返回第一个参数
|
||||||
|
* @param {Number|Boolean|String} val
|
||||||
|
* @param {Number|Boolean|String} replaceVal
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isNullReplace(val, replaceVal = '') {
|
||||||
|
return isNullOrUndef(val) || val === '' ? replaceVal : val
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUrl(path) {
|
||||||
|
const reg =
|
||||||
|
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
|
||||||
|
return reg.test(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isExternal(path) {
|
||||||
|
return /^(https?:|mailto:|tel:)/.test(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isServer = typeof window === 'undefined'
|
||||||
|
|
||||||
|
export const isClient = !isServer
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// const module = import.meta.glob(`@/views/**/*.vue`)
|
||||||
|
// const modules = {}
|
||||||
|
// Object.keys(module).forEach((item) => {
|
||||||
|
// module[item]().then(res => {
|
||||||
|
// const moduleName = item.replace(/^\.\/modules\/(.*)\.\w+$/, '$1')
|
||||||
|
// modules[moduleName] = res?.default
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
const modulesEager = import.meta.globEager(`@/views/**/*.vue`)
|
||||||
|
const modules = Object.keys(modulesEager).reduce((modules, path) => {
|
||||||
|
const moduleName = path.replace(/^\.\/modules\/(.*)\.\w+$/, '$1')
|
||||||
|
modules[moduleName] = modulesEager[path]?.default
|
||||||
|
return modules
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
export default modules
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { createLocalStorage } from './cache'
|
||||||
|
|
||||||
|
const STORAGE_CODE = 'tags_menu'
|
||||||
|
const DURATION = 24 * 60 * 60
|
||||||
|
|
||||||
|
export const lsToken = createLocalStorage()
|
||||||
|
|
||||||
|
export function getTags() {
|
||||||
|
return lsToken.get(STORAGE_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTags(token) {
|
||||||
|
lsToken.set(STORAGE_CODE, token, DURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTags() {
|
||||||
|
lsToken.remove(STORAGE_CODE)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { createLocalStorage } from './cache'
|
||||||
|
// import { refreshToken } from '@/api/auth'
|
||||||
|
|
||||||
|
const TOKEN_CODE = 'access_token'
|
||||||
|
const DURATION = 24 * 60 * 60
|
||||||
|
|
||||||
|
export const lsToken = createLocalStorage()
|
||||||
|
|
||||||
|
/* 获取token */
|
||||||
|
export function getToken() {
|
||||||
|
return lsToken.get(TOKEN_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 设置token */
|
||||||
|
export function setToken(token) {
|
||||||
|
lsToken.set(TOKEN_CODE, token, DURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移出token */
|
||||||
|
export function removeToken() {
|
||||||
|
lsToken.remove(TOKEN_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 刷新token */
|
||||||
|
// export async function refreshAccessToken() {
|
||||||
|
// const tokenItem = lsToken.getItem(TOKEN_CODE)
|
||||||
|
// if (!tokenItem) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// const { time } = tokenItem
|
||||||
|
// if (new Date().getTime() - time > 1000 * 60 * 30) {
|
||||||
|
// try {
|
||||||
|
// const res = await refreshToken()
|
||||||
|
// if (res.code === 0) {
|
||||||
|
// setToken(res.data.token)
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error(error)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
const common = {
|
||||||
|
primaryColor: '#36ad6a'
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeOverrides = {
|
||||||
|
common,
|
||||||
|
Button: {
|
||||||
|
textColor: common.primaryColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default themeOverrides
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
首页
|
||||||
|
<n-button @click="toSystem">跳转</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
export default {
|
||||||
|
name: 'HomePage',
|
||||||
|
setup(props) {
|
||||||
|
const router = useRouter()
|
||||||
|
function toSystem() {
|
||||||
|
router.push({ path: '/login' })
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
toSystem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-button @click="handleLogin">登录</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { userLogin, userCaptcha } from '@/api/login/index.js'
|
||||||
|
import { setToken } from '@/utils/token'
|
||||||
|
export default {
|
||||||
|
name: 'LoginPage',
|
||||||
|
setup() {
|
||||||
|
async function handleLogin() {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
captcha: '1',
|
||||||
|
key: '9132e06a-e2f0-4a9d-88da-b43ced72bb2e',
|
||||||
|
password: '123456',
|
||||||
|
remember: false,
|
||||||
|
username: 'admin'
|
||||||
|
}
|
||||||
|
const res = await userLogin(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
setToken(res.data.access_token)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// userCaptcha()
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleLogin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<data-table :columns="data.columns" :data="data.data" size="large">
|
||||||
|
<template #tableTitle>
|
||||||
|
<n-button type="primary">
|
||||||
|
添加角色
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</data-table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dataTable from '@/components/DataTable/index.vue'
|
||||||
|
import Action from '@/components/DataTable/tools/action.vue'
|
||||||
|
import { getMenuList } from '@/api/system/index.js'
|
||||||
|
import { h, onMounted } from 'vue'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
export default {
|
||||||
|
name: 'MenuPage',
|
||||||
|
components: { dataTable },
|
||||||
|
setup() {
|
||||||
|
const data = reactive({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '菜单标题',
|
||||||
|
key: 'title',
|
||||||
|
align: 'center',
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '菜单类型',
|
||||||
|
key: 'type',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '请求方式',
|
||||||
|
key: 'method',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '路由地址',
|
||||||
|
key: 'path',
|
||||||
|
align: 'center',
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '组件路径',
|
||||||
|
key: 'component',
|
||||||
|
align: 'center',
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '权限标识',
|
||||||
|
key: 'permission',
|
||||||
|
align: 'center',
|
||||||
|
width: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
key: 'sort',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '可见',
|
||||||
|
key: 'hide',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime',
|
||||||
|
align: 'center',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
align: 'center',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h(Action, {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: '添加',
|
||||||
|
type: 'button',
|
||||||
|
props: {
|
||||||
|
type: 'primary',
|
||||||
|
onClick: play.bind(null, row)
|
||||||
|
},
|
||||||
|
auth: 'basic_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '修改',
|
||||||
|
auth: 'basic_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
type: 'popconfirm',
|
||||||
|
auth: 'basic_list'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
align: 'center'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
function play(row) {
|
||||||
|
console.log(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取菜单数据
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
async function fetchList() {
|
||||||
|
const res = await getMenuList()
|
||||||
|
data.data = res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchList()
|
||||||
|
})
|
||||||
|
|
||||||
|
return { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: '',
|
||||||
|
setup() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<data-table :columns="data.columns" :data="data.data" :pagination="data.pagination" size="large" scroll-x="1200">
|
||||||
|
<template #tableTitle>
|
||||||
|
<n-button type="primary">
|
||||||
|
新建
|
||||||
|
</n-button>
|
||||||
|
<n-button type="primary">
|
||||||
|
删除
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</data-table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dataTable from '@/components/DataTable/index.vue'
|
||||||
|
import TableAction from '@/components/DataTable/tools/Action.vue'
|
||||||
|
import TableImage from '@/components/DataTable/tools/Image.vue'
|
||||||
|
import { getUserList } from '@/api/system/index.js'
|
||||||
|
import { h, onMounted } from 'vue'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
export default {
|
||||||
|
name: 'MenuPage',
|
||||||
|
components: { dataTable },
|
||||||
|
setup() {
|
||||||
|
const data = reactive({
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '用户编号',
|
||||||
|
key: 'code',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '头像',
|
||||||
|
key: 'avatar',
|
||||||
|
align: 'center',
|
||||||
|
render(row) {
|
||||||
|
return h(TableImage, {
|
||||||
|
images: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
src: row.avatar
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户账号',
|
||||||
|
key: 'username',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户姓名',
|
||||||
|
key: 'realname',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用户类型',
|
||||||
|
key: 'type',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色',
|
||||||
|
key: 'roles',
|
||||||
|
align: 'center'
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
align: 'center',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '部门',
|
||||||
|
key: 'deptName',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime',
|
||||||
|
align: 'center',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
key: 'updateTime',
|
||||||
|
align: 'center',
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
align: 'center',
|
||||||
|
width: 150,
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h(TableAction, {
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: '添加',
|
||||||
|
type: 'button',
|
||||||
|
props: {
|
||||||
|
type: 'primary',
|
||||||
|
onClick: play.bind(null, row)
|
||||||
|
},
|
||||||
|
auth: 'basic_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '修改',
|
||||||
|
auth: 'basic_list'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
type: 'popconfirm',
|
||||||
|
auth: 'basic_list'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
align: 'center'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: [],
|
||||||
|
pagination: {
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function play(row) {
|
||||||
|
console.log(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 获取用户数据
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
async function fetchList() {
|
||||||
|
const params = {
|
||||||
|
page: 1,
|
||||||
|
limit: 10
|
||||||
|
}
|
||||||
|
const res = await getUserList(params)
|
||||||
|
data.data = res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchList()
|
||||||
|
})
|
||||||
|
|
||||||
|
return { data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang='scss'>
|
||||||
|
.n-button+.n-button{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
import { wrapperEnv } from './build/utils'
|
||||||
|
import { createVitePlugins } from './build/vite/plugin'
|
||||||
|
import { createProxy } from './build/vite/proxy'
|
||||||
|
import { OUTPUT_DIR } from './build/constant'
|
||||||
|
|
||||||
|
function pathResolve(dir) {
|
||||||
|
return resolve(__dirname, '.', dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig(({ command, mode }) => {
|
||||||
|
const root = process.cwd()
|
||||||
|
const isBuild = command === 'build'
|
||||||
|
const env = loadEnv(mode, process.cwd())
|
||||||
|
const viteEnv = wrapperEnv(env)
|
||||||
|
const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY } = viteEnv
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
base: VITE_PUBLIC_PATH || '/',
|
||||||
|
plugins: createVitePlugins(viteEnv, isBuild),
|
||||||
|
lintOnSave: false,
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': pathResolve('src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: `@import '@/styles/variables.scss';`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: VITE_PORT,
|
||||||
|
proxy: createProxy(VITE_PROXY)
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: 'es2015',
|
||||||
|
outDir: OUTPUT_DIR,
|
||||||
|
brotliSize: false,
|
||||||
|
chunkSizeWarningLimit: 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue