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