Commit 2f6253cfb601c0a429ade1a272f5812a55d363af
0 parents
initial commit
Showing
100 changed files
with
2930 additions
and
0 deletions
Too many changes to show.
To preserve performance only 100 of 436 files are displayed.
.editorconfig
0 → 100644
.env
0 → 100644
1 | +++ a/.env | ... | ... |
.env.development
0 → 100644
.env.production
0 → 100644
.eslintignore
0 → 100644
1 | +++ a/.eslintignore | |
1 | + | |
2 | +*.sh | |
3 | +node_modules | |
4 | +lib | |
5 | +*.md | |
6 | +*.scss | |
7 | +*.woff | |
8 | +*.ttf | |
9 | +.vscode | |
10 | +.idea | |
11 | +/dist/ | |
12 | +/mock/ | |
13 | +/public | |
14 | +/docs | |
15 | +.vscode | |
16 | +.local | |
17 | +/bin | |
18 | +/build | |
19 | +/config | |
20 | +Dockerfile | |
21 | +vue.config.js | |
22 | +commit-lint.js | |
23 | +/src/assets/iconfont/ | |
24 | +/types/shims | |
25 | +/src/types/shims | |
26 | +postcss.config.js | |
27 | +stylelint.config.js | |
28 | +commitlint.config.js | ... | ... |
.eslintrc.js
0 → 100644
1 | +++ a/.eslintrc.js | |
1 | +module.exports = { | |
2 | + parser: 'vue-eslint-parser', | |
3 | + parserOptions: { | |
4 | + parser: '@typescript-eslint/parser', | |
5 | + ecmaVersion: 2020, | |
6 | + sourceType: 'module', | |
7 | + ecmaFeatures: { | |
8 | + jsx: true, | |
9 | + jsx: true, | |
10 | + }, | |
11 | + }, | |
12 | + | |
13 | + extends: [ | |
14 | + 'plugin:vue/vue3-recommended', | |
15 | + 'plugin:@typescript-eslint/recommended', | |
16 | + 'prettier/@typescript-eslint', | |
17 | + 'plugin:prettier/recommended', | |
18 | + ], | |
19 | + rules: { | |
20 | + '@typescript-eslint/ban-ts-ignore': 'off', | |
21 | + '@typescript-eslint/explicit-function-return-type': 'off', | |
22 | + '@typescript-eslint/no-explicit-any': 'off', | |
23 | + '@typescript-eslint/no-var-requires': 'off', | |
24 | + '@typescript-eslint/no-empty-function': 'off', | |
25 | + 'vue/custom-event-name-casing': 'off', | |
26 | + 'no-use-before-define': 'off', | |
27 | + // 'no-use-before-define': [ | |
28 | + // 'error', | |
29 | + // { | |
30 | + // functions: false, | |
31 | + // classes: true, | |
32 | + // }, | |
33 | + // ], | |
34 | + '@typescript-eslint/no-use-before-define': 'off', | |
35 | + // '@typescript-eslint/no-use-before-define': [ | |
36 | + // 'error', | |
37 | + // { | |
38 | + // functions: false, | |
39 | + // classes: true, | |
40 | + // }, | |
41 | + // ], | |
42 | + '@typescript-eslint/ban-ts-comment': 'off', | |
43 | + '@typescript-eslint/ban-types': 'off', | |
44 | + '@typescript-eslint/no-non-null-assertion': 'off', | |
45 | + '@typescript-eslint/explicit-module-boundary-types': 'off', | |
46 | + '@typescript-eslint/no-unused-vars': [ | |
47 | + 'error', | |
48 | + { | |
49 | + argsIgnorePattern: '^h$', | |
50 | + varsIgnorePattern: '^h$', | |
51 | + }, | |
52 | + ], | |
53 | + 'no-unused-vars': [ | |
54 | + 'error', | |
55 | + { | |
56 | + argsIgnorePattern: '^h$', | |
57 | + varsIgnorePattern: '^h$', | |
58 | + }, | |
59 | + ], | |
60 | + 'space-before-function-paren': 'off', | |
61 | + }, | |
62 | +}; | ... | ... |
.gitignore
0 → 100644
.ls-lint.yml
0 → 100644
1 | +++ a/.ls-lint.yml | |
1 | +ls: | |
2 | + src/*: | |
3 | + .js: kebab-case | PascalCase | |
4 | + .vue: PascalCase | regex:^index | |
5 | + .ts: camelCase | PascalCase | |
6 | + .d.ts: kebab-case | |
7 | + .mock.ts: kebab-case | |
8 | + .data.ts: camelCase | kebab-case | |
9 | + .test-d.ts: kebab-case | |
10 | + .less: kebab-case | PascalCase | |
11 | + .spec.ts: camelCase | PascalCase | |
12 | + | |
13 | +ignore: | |
14 | + - node_modules | |
15 | + - .git | |
16 | + - .circleci | |
17 | + - .github | |
18 | + - .vscode | |
19 | + - dist | |
20 | + - .local | ... | ... |
.prettierignore
0 → 100644
CHANGELOG.md
0 → 100644
1 | +++ a/CHANGELOG.md | |
1 | +# 2.0.0 (2020-09-28) | |
2 | + | |
3 | +### Features | |
4 | + | |
5 | +- add 37afeff | |
6 | +- add menu ab58829 | |
7 | +- add menu aeb75e7 | |
8 | +- add split menu 6b2b7bd | |
9 | +- add split menu 2e7cb0b | |
10 | +- add split menu 58fa70e | |
11 | +- add split menu aa87a2c | |
12 | +- auth d36878e | |
13 | +- header be36cc2 | |
14 | +- prettier 3f1db50 | |
15 | + | |
16 | +### Performance Improvements | |
17 | + | |
18 | +- form 2f94a5d | |
19 | +- loading 788fd64 | |
20 | +- lockpage 92d6b7e | |
21 | +- menu ae6ace8 | ... | ... |
LICENSE
0 → 100644
1 | +++ a/LICENSE | |
1 | +MIT License | |
2 | + | |
3 | +Copyright (c) 2020-present, Vben | |
4 | + | |
5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | +of this software and associated documentation files (the "Software"), to deal | |
7 | +in the Software without restriction, including without limitation the rights | |
8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | +copies of the Software, and to permit persons to whom the Software is | |
10 | +furnished to do so, subject to the following conditions: | |
11 | + | |
12 | +The above copyright notice and this permission notice shall be included in all | |
13 | +copies or substantial portions of the Software. | |
14 | + | |
15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
21 | +SOFTWARE. | ... | ... |
build/config/glob/lessModifyVars.ts
0 → 100644
1 | +++ a/build/config/glob/lessModifyVars.ts | |
1 | +/** | |
2 | + * less global variable | |
3 | + */ | |
4 | +const primaryColor = '#018ffb'; | |
5 | +//{ | |
6 | +const modifyVars = { | |
7 | + 'primary-color': primaryColor, // Global dominant color | |
8 | + 'info-color': primaryColor, // Default color | |
9 | + 'success-color': '#55D187', // Success color | |
10 | + 'error-color': '#ED6F6F', // False color | |
11 | + 'warning-color': '#EFBD47', // Warning color | |
12 | + 'link-color': primaryColor, // Link color | |
13 | + 'disabled-color': '#C2C2CC', // Failure color | |
14 | + 'heading-color': '#2C3A61', // Title color | |
15 | + 'text-color': '#2C3A61', // Main text color | |
16 | + 'text-color-secondary ': '#606266', // Subtext color | |
17 | + 'background-color-base': '#F0F2F5', // background color | |
18 | + 'font-size-base': '14px', // Main font size | |
19 | + 'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', // Floating shadow | |
20 | + 'border-color-base': '#cececd', // Border color, | |
21 | + 'border-color-split': '#cececd', // Border color, | |
22 | + 'border-radius-base': '2px', // Component/float fillet | |
23 | +}; | |
24 | +//} | |
25 | + | |
26 | +export { modifyVars, primaryColor }; | ... | ... |
build/config/vite/env.ts
0 → 100644
1 | +++ a/build/config/vite/env.ts | |
1 | +import moment from 'moment'; | |
2 | +// @ts-ignore | |
3 | +import pkg from '../../../package.json'; | |
4 | +export function setupBasicEnv() { | |
5 | + // version | |
6 | + process.env.VITE_VERSION = (pkg as any).version; | |
7 | + // build time | |
8 | + process.env.VITE_APP_BUILD_TIME = moment().format('YYYY-MM-DD HH:mm:ss'); | |
9 | + process.env.VITE_BUILD_SHORT_TIME = moment().format('MMDDHHmmss'); | |
10 | +} | ... | ... |
build/config/vite/proxy.ts
0 → 100644
1 | +++ a/build/config/vite/proxy.ts | |
1 | +type ProxyItem = [string, string]; | |
2 | + | |
3 | +type ProxyList = ProxyItem[]; | |
4 | + | |
5 | +export function createProxy(list: ProxyList) { | |
6 | + const ret: any = {}; | |
7 | + for (const [prefix, target] of list) { | |
8 | + ret[prefix] = { | |
9 | + target: target, | |
10 | + changeOrigin: true, | |
11 | + rewrite: (path: string) => path.replace(new RegExp(`^${prefix}`), ''), | |
12 | + }; | |
13 | + } | |
14 | + return ret; | |
15 | +} | ... | ... |
build/gzip/index.ts
0 → 100644
1 | +++ a/build/gzip/index.ts | |
1 | +// Build gzip after packaging | |
2 | +// import { readFile, writeFile } from 'fs'; | |
3 | +import viteConfig from '../../vite.config'; | |
4 | +import { | |
5 | + // basename, | |
6 | + join, | |
7 | +} from 'path'; | |
8 | +// import { promisify } from 'util'; | |
9 | +// import { gzip, ZlibOptions } from 'zlib'; | |
10 | +import { readAllFile } from '../utils'; | |
11 | + | |
12 | +// const readFilePromise = promisify(readFile); | |
13 | +// const writeFilePromise = promisify(writeFile); | |
14 | + | |
15 | +// function createGzip() {} | |
16 | + | |
17 | +const FILE_REG = /\.(js|mjs|json|css|html)$/; | |
18 | + | |
19 | +const OUT_DIR = viteConfig.outDir || 'dist'; | |
20 | + | |
21 | +// TODO 待开发 | |
22 | +const files = readAllFile(join(process.cwd(), OUT_DIR), FILE_REG); | ... | ... |
build/gzip/types.ts
0 → 100644
1 | +++ a/build/gzip/types.ts | |
1 | +import type { ZlibOptions } from 'zlib'; | |
2 | + | |
3 | +export type StringMappingOption = (originalString: string) => string; | |
4 | +export type CustomCompressionOption = ( | |
5 | + content: string | Buffer | |
6 | +) => string | Buffer | Promise<string | Buffer>; | |
7 | + | |
8 | +export interface GzipPluginOptions { | |
9 | + /** | |
10 | + * Control which of the output files to compress | |
11 | + * | |
12 | + * Defaults to `/\.(js|mjs|json|css|html)$/` | |
13 | + */ | |
14 | + filter?: RegExp | ((fileName: string) => boolean); | |
15 | + | |
16 | + /** | |
17 | + * GZIP compression options, see https://nodejs.org/api/zlib.html#zlib_class_options | |
18 | + */ | |
19 | + gzipOptions?: ZlibOptions; | |
20 | + | |
21 | + /** | |
22 | + * Specified the minimum size in Bytes for a file to get compressed. | |
23 | + * Files that are smaller than this threshold will not be compressed. | |
24 | + * This does not apply to the files specified through `additionalFiles`! | |
25 | + */ | |
26 | + minSize?: number; | |
27 | + | |
28 | + /** | |
29 | + * This option allows you to compress additional files outside of the main rollup bundling process. | |
30 | + * The processing is delayed to make sure the files are written on disk; the delay is controlled | |
31 | + * through `additionalFilesDelay`. | |
32 | + */ | |
33 | + additionalFiles?: string[]; | |
34 | + | |
35 | + /** | |
36 | + * This options sets a delay (ms) before the plugin compresses the files specified through `additionalFiles`. | |
37 | + * Increase this value if your artifacts take a long time to generate. | |
38 | + * | |
39 | + * Defaults to `2000` | |
40 | + */ | |
41 | + additionalFilesDelay?: number; | |
42 | + | |
43 | + /** | |
44 | + * Set a custom compression algorithm. The function can either return the compressed contents synchronously, | |
45 | + * or otherwise return a promise for asynchronous processing. | |
46 | + */ | |
47 | + customCompression?: CustomCompressionOption; | |
48 | + | |
49 | + /** | |
50 | + * Set a custom file name convention for the compressed files. Can be a suffix string or a function | |
51 | + * returning the file name. | |
52 | + * | |
53 | + * Defaults to `.gz` | |
54 | + */ | |
55 | + fileName?: string | StringMappingOption; | |
56 | +} | ... | ... |
build/script/changelog.ts
0 → 100644
1 | +++ a/build/script/changelog.ts | |
1 | +// #!/usr/bin/env node | |
2 | + | |
3 | +import { sh } from 'tasksfile'; | |
4 | +import chalk from 'chalk'; | |
5 | + | |
6 | +const createChangeLog = async () => { | |
7 | + try { | |
8 | + let cmd = `conventional-changelog -p angular -i CHANGELOG.md -s -r 0 `; | |
9 | + // let cmd = `conventional-changelog -p angular -i CHANGELOG.md -s -r 0 `; | |
10 | + // if (shell.which('git')) { | |
11 | + // cmd += '&& git add CHANGELOG.md'; | |
12 | + // } | |
13 | + await sh(cmd, { | |
14 | + async: true, | |
15 | + nopipe: true, | |
16 | + }); | |
17 | + | |
18 | + await sh('prettier --write **/CHANGELOG.md ', { | |
19 | + async: true, | |
20 | + nopipe: true, | |
21 | + }); | |
22 | + console.log( | |
23 | + chalk.blue.bold('**************** ') + | |
24 | + chalk.green.bold('CHANGE_LOG generated successfully!') + | |
25 | + chalk.blue.bold(' ****************') | |
26 | + ); | |
27 | + } catch (error) { | |
28 | + console.log( | |
29 | + chalk.blue.red('**************** ') + | |
30 | + chalk.green.red('CHANGE_LOG generated error\n' + error) + | |
31 | + chalk.blue.red(' ****************') | |
32 | + ); | |
33 | + process.exit(1); | |
34 | + } | |
35 | +}; | |
36 | +createChangeLog(); | |
37 | +module.exports = { | |
38 | + createChangeLog, | |
39 | +}; | ... | ... |
build/script/postinstall.ts
0 → 100644
build/script/preserve.ts
0 → 100644
1 | +++ a/build/script/preserve.ts | |
1 | +// 是否需要更新依赖,防止package.json更新了依赖,其他人获取代码后没有install | |
2 | + | |
3 | +import path from 'path'; | |
4 | +import fs from 'fs-extra'; | |
5 | +import { isEqual } from 'lodash'; | |
6 | +import chalk from 'chalk'; | |
7 | +import { sh } from 'tasksfile'; | |
8 | + | |
9 | +const resolve = (dir: string) => { | |
10 | + return path.resolve(process.cwd(), dir); | |
11 | +}; | |
12 | + | |
13 | +let NEED_INSTALL = false; | |
14 | + | |
15 | +fs.mkdirp(resolve('build/.cache')); | |
16 | +function checkPkgUpdate() { | |
17 | + const pkg = require('../../package.json'); | |
18 | + const { dependencies, devDependencies } = pkg; | |
19 | + const depsFile = resolve('build/.cache/deps.json'); | |
20 | + if (!fs.pathExistsSync(depsFile)) { | |
21 | + NEED_INSTALL = true; | |
22 | + return; | |
23 | + } | |
24 | + const depsJson = require('../.cache/deps.json'); | |
25 | + | |
26 | + if (!isEqual(depsJson, { dependencies, devDependencies })) { | |
27 | + NEED_INSTALL = true; | |
28 | + } | |
29 | +} | |
30 | +checkPkgUpdate(); | |
31 | + | |
32 | +(async () => { | |
33 | + if (NEED_INSTALL) { | |
34 | + console.log( | |
35 | + chalk.blue.bold('**************** ') + | |
36 | + chalk.red.bold('检测到依赖变化,正在安装依赖(Tip: 项目首次运行也会执行)!') + | |
37 | + chalk.blue.bold(' ****************') | |
38 | + ); | |
39 | + try { | |
40 | + // 从代码执行貌似不会自动读取.npmrc 所以手动加上源地址 | |
41 | + // await run('yarn install --registry=https://registry.npm.taobao.org ', { | |
42 | + await sh('yarn install ', { | |
43 | + async: true, | |
44 | + nopipe: true, | |
45 | + }); | |
46 | + console.log( | |
47 | + chalk.blue.bold('**************** ') + | |
48 | + chalk.green.bold('依赖安装成功,正在运行!') + | |
49 | + chalk.blue.bold(' ****************') | |
50 | + ); | |
51 | + | |
52 | + const pkg = require('../../package.json'); | |
53 | + const { dependencies, devDependencies } = pkg; | |
54 | + const depsFile = resolve('build/.cache/deps.json'); | |
55 | + const deps = { dependencies, devDependencies }; | |
56 | + if (!fs.pathExistsSync(depsFile)) { | |
57 | + fs.writeFileSync(depsFile, JSON.stringify(deps)); | |
58 | + } else { | |
59 | + const depsFile = resolve('build/.cache/deps.json'); | |
60 | + const depsJson = require('../.cache/deps.json'); | |
61 | + if (!isEqual(depsJson, deps)) { | |
62 | + fs.writeFileSync(depsFile, JSON.stringify(deps)); | |
63 | + } | |
64 | + } | |
65 | + } catch (error) {} | |
66 | + } | |
67 | +})(); | ... | ... |
build/script/preview.ts
0 → 100644
1 | +++ a/build/script/preview.ts | |
1 | +import chalk from 'chalk'; | |
2 | +import Koa from 'koa'; | |
3 | +import inquirer from 'inquirer'; | |
4 | +import { sh } from 'tasksfile'; | |
5 | +import staticServer from 'koa-static'; | |
6 | +import portfinder from 'portfinder'; | |
7 | +import { resolve } from 'path'; | |
8 | +import viteConfig from '../../vite.config'; | |
9 | +import { getIPAddress } from '../utils'; | |
10 | + | |
11 | +const BUILD = 1; | |
12 | +const NO_BUILD = 2; | |
13 | + | |
14 | +// 启动服务器 | |
15 | +const startApp = () => { | |
16 | + const port = 9680; | |
17 | + portfinder.basePort = port; | |
18 | + const app = new Koa(); | |
19 | + // const connect = require('connect'); | |
20 | + // const serveStatic = require('serve-static'); | |
21 | + // const app = connect(); | |
22 | + | |
23 | + app.use(staticServer(resolve(process.cwd(), viteConfig.outDir || 'dist'))); | |
24 | + | |
25 | + portfinder.getPort(async (err, port) => { | |
26 | + if (err) { | |
27 | + throw err; | |
28 | + } else { | |
29 | + // const publicPath = process.env.BASE_URL; | |
30 | + app.listen(port, function () { | |
31 | + const empty = ' '; | |
32 | + const common = `The preview program is already running: | |
33 | + - LOCAL: http://localhost:${port}/ | |
34 | + - NETWORK: http://${getIPAddress()}:${port}/ | |
35 | + `; | |
36 | + console.log(chalk.cyan('\n' + empty + common)); | |
37 | + }); | |
38 | + } | |
39 | + }); | |
40 | +}; | |
41 | + | |
42 | +const preview = async () => { | |
43 | + const prompt = inquirer.prompt({ | |
44 | + type: 'list', | |
45 | + message: 'Please select a preview method', | |
46 | + name: 'type', | |
47 | + choices: [ | |
48 | + { | |
49 | + name: 'Preview after packaging', | |
50 | + value: BUILD, | |
51 | + }, | |
52 | + { | |
53 | + name: `No packaging, preview directly (need to have dist file after packaging)`, | |
54 | + value: NO_BUILD, | |
55 | + }, | |
56 | + ], | |
57 | + }); | |
58 | + const { type } = await prompt; | |
59 | + if (type === BUILD) { | |
60 | + await sh('npm run build', { | |
61 | + async: true, | |
62 | + nopipe: true, | |
63 | + }); | |
64 | + } | |
65 | + startApp(); | |
66 | +}; | |
67 | + | |
68 | +(() => { | |
69 | + preview(); | |
70 | +})(); | ... | ... |
build/tsconfig.json
0 → 100644
1 | +++ a/build/tsconfig.json | |
1 | +{ | |
2 | + "compilerOptions": { | |
3 | + "target": "esnext", | |
4 | + "module": "commonjs", | |
5 | + "moduleResolution": "node", | |
6 | + "strict": true, | |
7 | + "forceConsistentCasingInFileNames": true, | |
8 | + "jsx": "react", | |
9 | + "baseUrl": ".", | |
10 | + "esModuleInterop": true, | |
11 | + "noUnusedLocals": true, | |
12 | + "noUnusedParameters": true, | |
13 | + "experimentalDecorators": true, | |
14 | + "lib": ["dom", "esnext"], | |
15 | + "incremental": true, | |
16 | + "skipLibCheck": true | |
17 | + } | |
18 | +} | ... | ... |
build/utils.ts
0 → 100644
1 | +++ a/build/utils.ts | |
1 | +import fs from 'fs'; | |
2 | +import { networkInterfaces } from 'os'; | |
3 | +import dotenv from 'dotenv'; | |
4 | + | |
5 | +export const isFunction = (arg: unknown): arg is (...args: any[]) => any => | |
6 | + typeof arg === 'function'; | |
7 | +export const isRegExp = (arg: unknown): arg is RegExp => | |
8 | + Object.prototype.toString.call(arg) === '[object RegExp]'; | |
9 | + | |
10 | +/* | |
11 | + * Read all files in the specified folder, filter through regular rules, and return file path array | |
12 | + * @param root Specify the folder path | |
13 | + * [@param] reg Regular expression for filtering files, optional parameters | |
14 | + * Note: It can also be deformed to check whether the file path conforms to regular rules. The path can be a folder or a file. The path that does not exist is also fault-tolerant. | |
15 | + */ | |
16 | +export function readAllFile(root: string, reg: RegExp) { | |
17 | + let resultArr: string[] = []; | |
18 | + try { | |
19 | + if (fs.existsSync(root)) { | |
20 | + const stat = fs.lstatSync(root); | |
21 | + if (stat.isDirectory()) { | |
22 | + // dir | |
23 | + const files = fs.readdirSync(root); | |
24 | + files.forEach(function (file) { | |
25 | + const t = readAllFile(root + '/' + file, reg); | |
26 | + resultArr = resultArr.concat(t); | |
27 | + }); | |
28 | + } else { | |
29 | + if (reg !== undefined) { | |
30 | + if (isFunction(reg.test) && reg.test(root)) { | |
31 | + resultArr.push(root); | |
32 | + } | |
33 | + } else { | |
34 | + resultArr.push(root); | |
35 | + } | |
36 | + } | |
37 | + } | |
38 | + } catch (error) {} | |
39 | + | |
40 | + return resultArr; | |
41 | +} | |
42 | + | |
43 | +export function getIPAddress() { | |
44 | + let interfaces = networkInterfaces(); | |
45 | + for (let devName in interfaces) { | |
46 | + let iFace = interfaces[devName]; | |
47 | + if (!iFace) return; | |
48 | + for (let i = 0; i < iFace.length; i++) { | |
49 | + let alias = iFace[i]; | |
50 | + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { | |
51 | + return alias.address; | |
52 | + } | |
53 | + } | |
54 | + } | |
55 | + | |
56 | + return ''; | |
57 | +} | |
58 | + | |
59 | +export function isDevFn(): boolean { | |
60 | + return process.env.NODE_ENV === 'development'; | |
61 | +} | |
62 | + | |
63 | +export function isProdFn(): boolean { | |
64 | + return process.env.NODE_ENV === 'production'; | |
65 | +} | |
66 | + | |
67 | +export function isReportMode(): boolean { | |
68 | + return process.env.REPORT === 'true'; | |
69 | +} | |
70 | + | |
71 | +export function loadEnv() { | |
72 | + const env = process.env.NODE_ENV; | |
73 | + const ret: any = {}; | |
74 | + const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env', ,]; | |
75 | + envList.forEach((e) => { | |
76 | + dotenv.config({ | |
77 | + path: e, | |
78 | + }); | |
79 | + }); | |
80 | + | |
81 | + for (const envName of Object.keys(process.env)) { | |
82 | + const realName = (process.env as any)[envName].replace(/\\n/g, '\n'); | |
83 | + ret[envName] = realName; | |
84 | + process.env[envName] = realName; | |
85 | + } | |
86 | + return ret; | |
87 | +} | ... | ... |
commitlint.config.js
0 → 100644
1 | +++ a/commitlint.config.js | |
1 | +module.exports = { | |
2 | + ignores: [(commit) => commit.includes('init')], | |
3 | + extends: ['@commitlint/config-conventional'], | |
4 | + parserPreset: { | |
5 | + parserOpts: { | |
6 | + headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\(](.*)[\)\)])?[\:\:] (.*)/, | |
7 | + headerCorrespondence: ['type', 'scope', 'subject'], | |
8 | + referenceActions: [ | |
9 | + 'close', | |
10 | + 'closes', | |
11 | + 'closed', | |
12 | + 'fix', | |
13 | + 'fixes', | |
14 | + 'fixed', | |
15 | + 'resolve', | |
16 | + 'resolves', | |
17 | + 'resolved', | |
18 | + ], | |
19 | + issuePrefixes: ['#'], | |
20 | + noteKeywords: ['BREAKING CHANGE', '不兼容变更'], | |
21 | + fieldPattern: /^-(.*?)-$/, | |
22 | + revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./, | |
23 | + revertCorrespondence: ['header', 'hash'], | |
24 | + warn() {}, | |
25 | + mergePattern: null, | |
26 | + mergeCorrespondence: null, | |
27 | + }, | |
28 | + }, | |
29 | + rules: { | |
30 | + 'body-leading-blank': [2, 'always'], | |
31 | + 'footer-leading-blank': [1, 'always'], | |
32 | + 'header-max-length': [2, 'always', 108], | |
33 | + 'subject-empty': [2, 'never'], | |
34 | + 'type-empty': [2, 'never'], | |
35 | + 'type-enum': [ | |
36 | + 2, | |
37 | + 'always', | |
38 | + [ | |
39 | + 'feat', | |
40 | + 'fix', | |
41 | + 'perf', | |
42 | + 'style', | |
43 | + 'docs', | |
44 | + 'test', | |
45 | + 'refactor', | |
46 | + 'build', | |
47 | + 'ci', | |
48 | + 'chore', | |
49 | + 'revert', | |
50 | + 'wip', | |
51 | + 'workflow', | |
52 | + ], | |
53 | + ], | |
54 | + }, | |
55 | +}; | ... | ... |
index.html
0 → 100644
1 | +++ a/index.html | |
1 | +<!DOCTYPE html> | |
2 | +<html lang="en"> | |
3 | + <head> | |
4 | + <meta charset="UTF-8" /> | |
5 | + <link rel="icon" href="/favicon.ico" /> | |
6 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
7 | + <title>Vue Vben admin 2.0</title> | |
8 | + </head> | |
9 | + <body> | |
10 | + <div id="app"></div> | |
11 | + <script type="module" src="/src/main.ts"></script> | |
12 | + </body> | |
13 | +</html> | ... | ... |
lint-staged.config.js
0 → 100644
1 | +++ a/lint-staged.config.js | |
1 | +module.exports = { | |
2 | + '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], | |
3 | + '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], | |
4 | + 'package.json': ['prettier --write'], | |
5 | + '*.vue': ['prettier --write', 'stylelint --fix', 'git add .'], | |
6 | + '*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write', 'git add .'], | |
7 | + '*.md': ['prettier --write'], | |
8 | +}; | ... | ... |
mock/_createProductionServer.ts
0 → 100644
1 | +++ a/mock/_createProductionServer.ts | |
1 | +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; | |
2 | +import userMock from './sys/user'; | |
3 | +import menuMock from './sys/menu'; | |
4 | + | |
5 | +export function setupProdMockServer() { | |
6 | + createProdMockServer([...userMock, ...menuMock]); | |
7 | +} | ... | ... |
mock/_util.ts
0 → 100644
1 | +++ a/mock/_util.ts | |
1 | +// Interface data format used to return a unified format | |
2 | + | |
3 | +export function resultSuccess<T = any>(result: T, { message = 'ok' } = {}) { | |
4 | + return { | |
5 | + code: 0, | |
6 | + result, | |
7 | + message, | |
8 | + type: 'success', | |
9 | + }; | |
10 | +} | |
11 | + | |
12 | +export function resultPageSuccess<T = any>(items: T[], total: number, { message = 'ok' } = {}) { | |
13 | + return { | |
14 | + code: 0, | |
15 | + result: { | |
16 | + items, | |
17 | + total, | |
18 | + }, | |
19 | + message, | |
20 | + type: 'success', | |
21 | + }; | |
22 | +} | |
23 | + | |
24 | +export function resultError(message = 'Request failed', { code = -1, result = null } = {}) { | |
25 | + return { | |
26 | + code, | |
27 | + result, | |
28 | + message, | |
29 | + type: 'error', | |
30 | + }; | |
31 | +} | |
32 | + | |
33 | +export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] { | |
34 | + let offset = (pageNo - 1) * pageSize; | |
35 | + return offset + pageSize >= array.length | |
36 | + ? array.slice(offset, array.length) | |
37 | + : array.slice(offset, offset + pageSize); | |
38 | +} | ... | ... |
mock/sys/menu.ts
0 → 100644
1 | +++ a/mock/sys/menu.ts | |
1 | +import { resultSuccess } from '../_util'; | |
2 | +import { MockMethod } from 'vite-plugin-mock'; | |
3 | + | |
4 | +const dashboardRoute = { | |
5 | + layout: { | |
6 | + path: '/dashboard', | |
7 | + name: 'Dashboard', | |
8 | + component: 'PAGE_LAYOUT', | |
9 | + redirect: '/dashboard/welcome', | |
10 | + meta: { | |
11 | + icon: 'ant-design:home-outlined', | |
12 | + title: 'Dashboard', | |
13 | + }, | |
14 | + }, | |
15 | + routes: [ | |
16 | + { | |
17 | + path: '/welcome', | |
18 | + name: 'Welcome', | |
19 | + component: '/dashboard/welcome/index.vue', | |
20 | + meta: { | |
21 | + title: '欢迎页', | |
22 | + affix: true, | |
23 | + }, | |
24 | + }, | |
25 | + ], | |
26 | +}; | |
27 | + | |
28 | +const frontRoute = { | |
29 | + path: '/front', | |
30 | + name: 'PermissionFrontDemo', | |
31 | + meta: { | |
32 | + title: '基于前端权限', | |
33 | + }, | |
34 | + children: [ | |
35 | + { | |
36 | + path: 'page', | |
37 | + component: '/demo/permission/front/index.vue', | |
38 | + meta: { | |
39 | + title: '页面权限', | |
40 | + }, | |
41 | + }, | |
42 | + { | |
43 | + path: 'btn', | |
44 | + component: '/demo/permission/front/Btn.vue', | |
45 | + meta: { | |
46 | + title: '按钮权限', | |
47 | + }, | |
48 | + }, | |
49 | + { | |
50 | + path: 'auth-pageA', | |
51 | + component: '/demo/permission/front/AuthPageA.vue', | |
52 | + meta: { | |
53 | + title: '权限测试页A', | |
54 | + }, | |
55 | + }, | |
56 | + { | |
57 | + path: 'auth-pageB', | |
58 | + component: '/demo/permission/front/AuthPageB.vue', | |
59 | + meta: { | |
60 | + title: '权限测试页B', | |
61 | + }, | |
62 | + }, | |
63 | + ], | |
64 | +}; | |
65 | +const backRoute = { | |
66 | + path: '/back', | |
67 | + name: 'PermissionBackDemo', | |
68 | + meta: { | |
69 | + title: '基于后台权限', | |
70 | + }, | |
71 | + children: [ | |
72 | + { | |
73 | + path: 'page', | |
74 | + component: 'demo/permission/back/index.vue', | |
75 | + meta: { | |
76 | + title: '页面权限', | |
77 | + }, | |
78 | + }, | |
79 | + { | |
80 | + path: 'btn', | |
81 | + component: '/demo/permission/back/Btn.vue', | |
82 | + meta: { | |
83 | + title: '按钮权限', | |
84 | + }, | |
85 | + }, | |
86 | + ], | |
87 | +}; | |
88 | +const authRoute = { | |
89 | + layout: { | |
90 | + path: '/permission', | |
91 | + name: 'Permission', | |
92 | + component: 'PAGE_LAYOUT', | |
93 | + redirect: '/permission/front/page', | |
94 | + meta: { | |
95 | + icon: 'ant-design:home-outlined', | |
96 | + title: '权限管理', | |
97 | + }, | |
98 | + }, | |
99 | + | |
100 | + routes: [frontRoute, backRoute], | |
101 | +}; | |
102 | + | |
103 | +const authRoute1 = { | |
104 | + layout: { | |
105 | + path: '/permission', | |
106 | + name: 'Permission', | |
107 | + component: 'PAGE_LAYOUT', | |
108 | + redirect: '/permission/front/page', | |
109 | + meta: { | |
110 | + icon: 'ant-design:home-outlined', | |
111 | + title: '权限管理', | |
112 | + }, | |
113 | + }, | |
114 | + | |
115 | + routes: [backRoute], | |
116 | +}; | |
117 | +export default [ | |
118 | + { | |
119 | + url: '/api/getMenuListById', | |
120 | + timeout: 1000, | |
121 | + method: 'get', | |
122 | + response: ({ query }) => { | |
123 | + const { id } = query; | |
124 | + if (!id || id === '1') { | |
125 | + return resultSuccess([dashboardRoute, authRoute]); | |
126 | + } | |
127 | + if (id === '2') { | |
128 | + return resultSuccess([dashboardRoute, authRoute1]); | |
129 | + } | |
130 | + }, | |
131 | + }, | |
132 | +] as MockMethod[]; | ... | ... |
mock/sys/user.ts
0 → 100644
1 | +++ a/mock/sys/user.ts | |
1 | +import { MockMethod } from 'vite-plugin-mock'; | |
2 | +import { resultError, resultSuccess } from '../_util'; | |
3 | + | |
4 | +function createFakeUserList() { | |
5 | + return [ | |
6 | + { | |
7 | + userId: '1', | |
8 | + username: 'vben', | |
9 | + realName: 'Vben', | |
10 | + desc: 'manager', | |
11 | + password: '123456', | |
12 | + token: 'fakeToken1', | |
13 | + role: { | |
14 | + roleName: 'Super Admin', | |
15 | + value: 'super', | |
16 | + }, | |
17 | + }, | |
18 | + { | |
19 | + userId: '2', | |
20 | + username: 'test', | |
21 | + password: '123456', | |
22 | + realName: 'test user', | |
23 | + desc: 'tester', | |
24 | + token: 'fakeToken2', | |
25 | + role: { | |
26 | + roleName: 'Tester', | |
27 | + value: 'test', | |
28 | + }, | |
29 | + }, | |
30 | + ]; | |
31 | +} | |
32 | + | |
33 | +const fakeCodeList: any = { | |
34 | + '1': ['1000', '3000', '5000'], | |
35 | + | |
36 | + '2': ['2000', '4000', '6000'], | |
37 | +}; | |
38 | +export default [ | |
39 | + // mock user login | |
40 | + { | |
41 | + url: '/api/login', | |
42 | + timeout: 1000, | |
43 | + method: 'post', | |
44 | + response: ({ body }) => { | |
45 | + const { username, password } = body; | |
46 | + const checkUser = createFakeUserList().find( | |
47 | + (item) => item.username === username && password === item.password | |
48 | + ); | |
49 | + if (!checkUser) { | |
50 | + return resultError('Incorrect account or password!'); | |
51 | + } | |
52 | + const { userId, username: _username, token, realName, desc, role } = checkUser; | |
53 | + return resultSuccess({ | |
54 | + role, | |
55 | + userId, | |
56 | + username: _username, | |
57 | + token, | |
58 | + realName, | |
59 | + desc, | |
60 | + }); | |
61 | + }, | |
62 | + }, | |
63 | + { | |
64 | + url: '/api/getUserInfoById', | |
65 | + timeout: 200, | |
66 | + method: 'get', | |
67 | + response: ({ query }) => { | |
68 | + const { userId } = query; | |
69 | + const checkUser = createFakeUserList().find((item) => item.userId === userId); | |
70 | + if (!checkUser) { | |
71 | + return resultError('The corresponding user information was not obtained!'); | |
72 | + } | |
73 | + return resultSuccess(checkUser); | |
74 | + }, | |
75 | + }, | |
76 | + { | |
77 | + url: '/api/getPermCodeByUserId', | |
78 | + timeout: 200, | |
79 | + method: 'get', | |
80 | + response: ({ query }) => { | |
81 | + const { userId } = query; | |
82 | + if (!userId) { | |
83 | + return resultError('userId is not null!'); | |
84 | + } | |
85 | + const codeList = fakeCodeList[userId]; | |
86 | + | |
87 | + return resultSuccess(codeList); | |
88 | + }, | |
89 | + }, | |
90 | +] as MockMethod[]; | ... | ... |
package.json
0 → 100644
1 | +++ a/package.json | |
1 | +{ | |
2 | + "name": "vben-admin-2.0", | |
3 | + "version": "2.0.0-beta.1", | |
4 | + "scripts": { | |
5 | + "bootstrap": "yarn install", | |
6 | + "serve": "ts-node --project ./build/tsconfig.json ./build/script/preserve && cross-env NODE_ENV=development vite", | |
7 | + "build": "cross-env NODE_ENV=production vite build ", | |
8 | + "report": "cross-env REPORT=true yarn build ", | |
9 | + "build:no-cache": "yarn clean:cache && yarn build", | |
10 | + "preview": "ts-node --project ./build/tsconfig.json ./build/script/preview", | |
11 | + "log": "ts-node --project ./build/tsconfig.json ./build/script/changelog", | |
12 | + "gen:gz": "ts-node --project build/tsconfig.build.json ./build/gzip/index.ts ", | |
13 | + "clean:cache": "npx rimraf node_modules/.cache/ && npx rimraf node_modules/.vite_opt_cache", | |
14 | + "clean:lib": "npx rimraf node_modules", | |
15 | + "ls-lint": "npx ls-lint", | |
16 | + "lint:eslint": "eslint --fix --ext \"src/**/*.{vue,less,css,scss}\"", | |
17 | + "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", | |
18 | + "lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", | |
19 | + "reinstall": "npx rimraf node_modules && npx rimraf yarn.lock && npx rimraf package.lock.json && yarn run bootstrap", | |
20 | + "postinstall": "ts-node --project ./build/tsconfig.json ./build/script/postinstall" | |
21 | + }, | |
22 | + "dependencies": { | |
23 | + "@iconify/iconify": "^2.0.0-rc.1", | |
24 | + "ant-design-vue": "^2.0.0-beta.10", | |
25 | + "axios": "^0.20.0", | |
26 | + "lodash-es": "^4.17.15", | |
27 | + "mockjs": "^1.1.0", | |
28 | + "nprogress": "^0.2.0", | |
29 | + "path-to-regexp": "^6.1.0", | |
30 | + "qrcode": "^1.4.4", | |
31 | + "vue": "^3.0.0", | |
32 | + "vue-i18n": "^9.0.0-beta.3", | |
33 | + "vue-router": "^4.0.0-beta.12", | |
34 | + "vuex": "^4.0.0-beta.4", | |
35 | + "vuex-module-decorators": "^1.0.1", | |
36 | + "zxcvbn": "^4.4.2" | |
37 | + }, | |
38 | + "devDependencies": { | |
39 | + "@commitlint/cli": "^11.0.0", | |
40 | + "@commitlint/config-conventional": "^11.0.0", | |
41 | + "@iconify/json": "^1.1.233", | |
42 | + "@ls-lint/ls-lint": "^1.9.2", | |
43 | + "@purge-icons/generated": "^0.4.1", | |
44 | + "@types/fs-extra": "^9.0.1", | |
45 | + "@types/inquirer": "^7.3.1", | |
46 | + "@types/koa-static": "^4.0.1", | |
47 | + "@types/lodash-es": "^4.17.3", | |
48 | + "@types/mockjs": "^1.0.3", | |
49 | + "@types/nprogress": "^0.2.0", | |
50 | + "@types/qrcode": "^1.3.5", | |
51 | + "@types/rollup-plugin-visualizer": "^2.6.0", | |
52 | + "@types/shelljs": "^0.8.8", | |
53 | + "@types/zxcvbn": "^4.4.0", | |
54 | + "@typescript-eslint/eslint-plugin": "^4.2.0", | |
55 | + "@typescript-eslint/parser": "^4.2.0", | |
56 | + "@vue/compiler-sfc": "^3.0.0", | |
57 | + "autoprefixer": "^9.8.6", | |
58 | + "babel-plugin-import": "^1.13.0", | |
59 | + "commitizen": "^4.2.1", | |
60 | + "conventional-changelog-cli": "^2.1.0", | |
61 | + "cross-env": "^7.0.2", | |
62 | + "dotenv": "^8.2.0", | |
63 | + "eslint": "^7.10.0", | |
64 | + "eslint-config-prettier": "^6.12.0", | |
65 | + "eslint-plugin-prettier": "^3.1.4", | |
66 | + "eslint-plugin-vue": "^7.0.0-beta.4", | |
67 | + "fs-extra": "^9.0.1", | |
68 | + "husky": "^4.3.0", | |
69 | + "inquirer": "^7.3.3", | |
70 | + "koa-static": "^5.0.0", | |
71 | + "less": "^3.12.2", | |
72 | + "lint-staged": "^10.4.0", | |
73 | + "ora": "^5.1.0", | |
74 | + "portfinder": "^1.0.28", | |
75 | + "postcss-import": "^12.0.1", | |
76 | + "prettier": "^2.1.2", | |
77 | + "rimraf": "^3.0.2", | |
78 | + "rollup-plugin-analyzer": "^3.3.0", | |
79 | + "rollup-plugin-visualizer": "^4.1.1", | |
80 | + "shelljs": "^0.8.4", | |
81 | + "stylelint": "^13.7.2", | |
82 | + "stylelint-config-prettier": "^8.0.2", | |
83 | + "stylelint-config-standard": "^20.0.0", | |
84 | + "stylelint-order": "^4.1.0", | |
85 | + "tailwindcss": "^1.8.10", | |
86 | + "tasksfile": "^5.1.1", | |
87 | + "ts-node": "^9.0.0", | |
88 | + "typescript": "^4.0.3", | |
89 | + "vite": "^1.0.0-rc.4", | |
90 | + "vite-jsx": "^1.0.5", | |
91 | + "vite-plugin-mock": "^1.0.2", | |
92 | + "vite-plugin-purge-icons": "^0.4.1", | |
93 | + "vue-eslint-parser": "^7.1.0" | |
94 | + }, | |
95 | + "husky": { | |
96 | + "hooks": { | |
97 | + "pre-commit": "ls-lint && lint-staged", | |
98 | + "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" | |
99 | + } | |
100 | + }, | |
101 | + "engines": { | |
102 | + "node": ">=12.0.0" | |
103 | + } | |
104 | +} | ... | ... |
postcss.config.js
0 → 100755
prettier.config.js
0 → 100644
1 | +++ a/prettier.config.js | |
1 | +module.exports = { | |
2 | + printWidth: 100, | |
3 | + tabWidth: 2, | |
4 | + useTabs: false, | |
5 | + semi: true, | |
6 | + vueIndentScriptAndStyle: true, | |
7 | + singleQuote: true, | |
8 | + quoteProps: 'as-needed', | |
9 | + bracketSpacing: true, | |
10 | + trailingComma: 'es5', | |
11 | + jsxBracketSameLine: false, | |
12 | + jsxSingleQuote: false, | |
13 | + arrowParens: 'always', | |
14 | + insertPragma: false, | |
15 | + requirePragma: false, | |
16 | + proseWrap: 'never', | |
17 | + htmlWhitespaceSensitivity: 'strict', | |
18 | + endOfLine: 'lf', | |
19 | + rangeStart: 0, | |
20 | + overrides: [ | |
21 | + { | |
22 | + files: '*.md', | |
23 | + options: { | |
24 | + tabWidth: 2, | |
25 | + }, | |
26 | + }, | |
27 | + ], | |
28 | +}; | ... | ... |
public/favicon.ico
0 → 100644
No preview for this file type
src/App.vue
0 → 100644
1 | +++ a/src/App.vue | |
1 | +<template> | |
2 | + <ConfigProvider | |
3 | + :locale="zhCN" | |
4 | + :renderEmpty="renderEmpty" | |
5 | + :transformCellText="transformCellText" | |
6 | + v-bind="lockOn" | |
7 | + > | |
8 | + <router-view /> | |
9 | + </ConfigProvider> | |
10 | +</template> | |
11 | + | |
12 | +<script lang="ts"> | |
13 | + import { defineComponent } from 'vue'; | |
14 | + import { ConfigProvider } from 'ant-design-vue'; | |
15 | + import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; | |
16 | + | |
17 | + import zhCN from 'ant-design-vue/es/locale/zh_CN'; | |
18 | + import moment from 'moment'; | |
19 | + import 'moment/locale/zh-cn'; | |
20 | + | |
21 | + import { useConfigProvider, useInitAppConfigStore, useListenerNetWork } from './useApp'; | |
22 | + import { useLockPage } from '/@/hooks/web/useLockPage'; | |
23 | + moment.locale('zh-cn'); | |
24 | + export default defineComponent({ | |
25 | + name: 'App', | |
26 | + components: { ConfigProvider }, | |
27 | + setup() { | |
28 | + useInitAppConfigStore(); | |
29 | + useListenerNetWork(); | |
30 | + createBreakpointListen(); | |
31 | + const { renderEmpty, transformCellText } = useConfigProvider(); | |
32 | + const { on: lockOn } = useLockPage(); | |
33 | + | |
34 | + return { | |
35 | + renderEmpty, | |
36 | + transformCellText, | |
37 | + zhCN, | |
38 | + lockOn, | |
39 | + }; | |
40 | + }, | |
41 | + }); | |
42 | +</script> | ... | ... |
src/api/sys/menu.ts
0 → 100644
1 | +++ a/src/api/sys/menu.ts | |
1 | +import { defHttp } from '/@/utils/http/axios'; | |
2 | + | |
3 | +import { getMenuListByIdParams, getMenuListByIdParamsResultModel } from './model/menuModel'; | |
4 | + | |
5 | +enum Api { | |
6 | + GetMenuListById = '/getMenuListById', | |
7 | +} | |
8 | + | |
9 | +/** | |
10 | + * @description: 根据id获取用户菜单 | |
11 | + */ | |
12 | +export function getMenuListById(params: getMenuListByIdParams) { | |
13 | + return defHttp.request<getMenuListByIdParamsResultModel>({ | |
14 | + url: Api.GetMenuListById, | |
15 | + method: 'GET', | |
16 | + params, | |
17 | + }); | |
18 | +} | ... | ... |
src/api/sys/model/menuModel.ts
0 → 100644
1 | +++ a/src/api/sys/model/menuModel.ts | |
1 | +import { RouteMeta } from '/@/router/types'; | |
2 | +export interface RouteItem { | |
3 | + path: string; | |
4 | + component: any; | |
5 | + meta: RouteMeta; | |
6 | + name?: string; | |
7 | + alias?: string | string[]; | |
8 | + redirect?: string; | |
9 | + caseSensitive?: boolean; | |
10 | + children?: RouteItem[]; | |
11 | +} | |
12 | + | |
13 | +/** | |
14 | + * @description: 获取菜单接口 | |
15 | + */ | |
16 | +export interface getMenuListByIdParams { | |
17 | + id: number | string; | |
18 | +} | |
19 | + | |
20 | +/** | |
21 | + * @description: 获取菜单返回值 | |
22 | + */ | |
23 | +export type getMenuListByIdParamsResultModel = RouteItem[]; | ... | ... |
src/api/sys/model/userModel.ts
0 → 100644
1 | +++ a/src/api/sys/model/userModel.ts | |
1 | +/** | |
2 | + * @description: Login interface parameters | |
3 | + */ | |
4 | +export interface LoginParams { | |
5 | + username: string; | |
6 | + password: string; | |
7 | +} | |
8 | + | |
9 | +/** | |
10 | + * @description: Get user information | |
11 | + */ | |
12 | +export interface GetUserInfoByUserIdParams { | |
13 | + userId: string | number; | |
14 | +} | |
15 | + | |
16 | +export interface RoleInfo { | |
17 | + roleName: string; | |
18 | + value: string; | |
19 | +} | |
20 | + | |
21 | +/** | |
22 | + * @description: Login interface return value | |
23 | + */ | |
24 | +export interface LoginResultModel { | |
25 | + userId: string | number; | |
26 | + token: string; | |
27 | + role: RoleInfo; | |
28 | +} | |
29 | + | |
30 | +/** | |
31 | + * @description: Get user information return value | |
32 | + */ | |
33 | +export interface GetUserInfoByUserIdModel { | |
34 | + role: RoleInfo; | |
35 | + // 用户id | |
36 | + userId: string | number; | |
37 | + // 用户名 | |
38 | + username: string; | |
39 | + // 真实名字 | |
40 | + realName: string; | |
41 | + // 介绍 | |
42 | + desc?: string; | |
43 | +} | ... | ... |
src/api/sys/user.ts
0 → 100644
1 | +++ a/src/api/sys/user.ts | |
1 | +import { defHttp } from '/@/utils/http/axios'; | |
2 | +import { | |
3 | + LoginParams, | |
4 | + LoginResultModel, | |
5 | + GetUserInfoByUserIdParams, | |
6 | + GetUserInfoByUserIdModel, | |
7 | +} from './model/userModel'; | |
8 | + | |
9 | +enum Api { | |
10 | + Login = '/login', | |
11 | + GetUserInfoById = '/getUserInfoById', | |
12 | + GetPermCodeByUserId = '/getPermCodeByUserId', | |
13 | +} | |
14 | + | |
15 | +/** | |
16 | + * @description: user login api | |
17 | + */ | |
18 | +export function loginApi(params: LoginParams) { | |
19 | + return defHttp.request<LoginResultModel>( | |
20 | + { | |
21 | + url: Api.Login, | |
22 | + method: 'POST', | |
23 | + params, | |
24 | + }, | |
25 | + { | |
26 | + errorMessageMode: 'modal', | |
27 | + } | |
28 | + ); | |
29 | +} | |
30 | + | |
31 | +/** | |
32 | + * @description: getUserInfoById | |
33 | + */ | |
34 | +export function getUserInfoById(params: GetUserInfoByUserIdParams) { | |
35 | + return defHttp.request<GetUserInfoByUserIdModel>({ | |
36 | + url: Api.GetUserInfoById, | |
37 | + method: 'GET', | |
38 | + params, | |
39 | + }); | |
40 | +} | |
41 | + | |
42 | +export function getPermCodeByUserId(params: GetUserInfoByUserIdParams) { | |
43 | + return defHttp.request<string[]>({ | |
44 | + url: Api.GetPermCodeByUserId, | |
45 | + method: 'GET', | |
46 | + params, | |
47 | + }); | |
48 | +} | ... | ... |
src/assets/images/dashboard/wokb/approve.png
0 → 100644
2.53 KB
src/assets/images/dashboard/wokb/attendance.png
0 → 100644
2.35 KB
src/assets/images/dashboard/wokb/datashow1.png
0 → 100644
5.04 KB
src/assets/images/dashboard/wokb/datashow2.png
0 → 100644
4.77 KB
src/assets/images/dashboard/wokb/datashow3.png
0 → 100644
4.93 KB
src/assets/images/dashboard/wokb/datashow4.png
0 → 100644
5.53 KB
src/assets/images/dashboard/wokb/leave.png
0 → 100644
2.78 KB
src/assets/images/dashboard/wokb/meal.png
0 → 100644
2.39 KB
src/assets/images/dashboard/wokb/overtime.png
0 → 100644
3.15 KB
src/assets/images/dashboard/wokb/performance.png
0 → 100644
2.57 KB
src/assets/images/dashboard/wokb/stamp.png
0 → 100644
1.97 KB
src/assets/images/dashboard/wokb/travel.png
0 → 100644
2.44 KB
src/assets/images/dashboard/wokb/wokb.png
0 → 100644
190 KB
src/assets/images/exception/404.png
0 → 100644
14.6 KB
src/assets/images/exception/500.png
0 → 100644
15.3 KB
src/assets/images/exception/net-work.png
0 → 100644
9.6 KB
src/assets/images/header.jpg
0 → 100644
1.04 MB
src/assets/images/layout/menu-mix.svg
0 → 100644
1 | +++ a/src/assets/images/layout/menu-mix.svg | |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" | |
3 | + xmlns="http://www.w3.org/2000/svg" | |
4 | + xmlns:xlink="http://www.w3.org/1999/xlink"> | |
5 | + <defs> | |
6 | + <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> | |
7 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
8 | + <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
9 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> | |
10 | + <feMerge> | |
11 | + <feMergeNode in="shadowMatrixOuter1"></feMergeNode> | |
12 | + <feMergeNode in="SourceGraphic"></feMergeNode> | |
13 | + </feMerge> | |
14 | + </filter> | |
15 | + <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> | |
16 | + <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> | |
17 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
18 | + <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
19 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> | |
20 | + </filter> | |
21 | + </defs> | |
22 | + <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |
23 | + <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)"> | |
24 | + <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)"> | |
25 | + <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)"> | |
26 | + <mask id="mask-3" fill="white"> | |
27 | + <use xlink:href="#path-2"></use> | |
28 | + </mask> | |
29 | + <g id="Rectangle-18"> | |
30 | + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> | |
31 | + <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> | |
32 | + </g> | |
33 | + <rect id="Rectangle-18" fill="#fff" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> | |
34 | + <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> | |
35 | + </g> | |
36 | + </g> | |
37 | + </g> | |
38 | + </g> | |
39 | +</svg> | ... | ... |
src/assets/images/layout/menu-sidebar.svg
0 → 100644
1 | +++ a/src/assets/images/layout/menu-sidebar.svg | |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" | |
3 | + xmlns="http://www.w3.org/2000/svg" | |
4 | + xmlns:xlink="http://www.w3.org/1999/xlink"> | |
5 | + <defs> | |
6 | + <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> | |
7 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
8 | + <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
9 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> | |
10 | + <feMerge> | |
11 | + <feMergeNode in="shadowMatrixOuter1"></feMergeNode> | |
12 | + <feMergeNode in="SourceGraphic"></feMergeNode> | |
13 | + </feMerge> | |
14 | + </filter> | |
15 | + <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> | |
16 | + <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> | |
17 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
18 | + <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
19 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> | |
20 | + </filter> | |
21 | + </defs> | |
22 | + <g width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |
23 | + <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)"> | |
24 | + <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)"> | |
25 | + <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)"> | |
26 | + <mask id="mask-3" fill="white"> | |
27 | + <use xlink:href="#path-2"></use> | |
28 | + </mask> | |
29 | + <g id="Rectangle-18"> | |
30 | + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> | |
31 | + <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> | |
32 | + </g> | |
33 | + <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> | |
34 | + <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> | |
35 | + </g> | |
36 | + </g> | |
37 | + </g> | |
38 | + </g> | |
39 | +</svg> | ... | ... |
src/assets/images/layout/menu-top.svg
0 → 100644
1 | +++ a/src/assets/images/layout/menu-top.svg | |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" | |
3 | + xmlns="http://www.w3.org/2000/svg" | |
4 | + xmlns:xlink="http://www.w3.org/1999/xlink"> | |
5 | + | |
6 | + <defs> | |
7 | + <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> | |
8 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
9 | + <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
10 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> | |
11 | + <feMerge> | |
12 | + <feMergeNode in="shadowMatrixOuter1"></feMergeNode> | |
13 | + <feMergeNode in="SourceGraphic"></feMergeNode> | |
14 | + </feMerge> | |
15 | + </filter> | |
16 | + <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> | |
17 | + <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> | |
18 | + <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> | |
19 | + <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> | |
20 | + <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> | |
21 | + </filter> | |
22 | + </defs> | |
23 | + <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | |
24 | + <g id="setting-copy-2" transform="translate(-1254.000000, -337.000000)"> | |
25 | + <g id="Group-8" transform="translate(1167.000000, 0.000000)"> | |
26 | + <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 338.000000)"> | |
27 | + <mask id="mask-3" fill="white"> | |
28 | + <use xlink:href="#path-2"></use> | |
29 | + </mask> | |
30 | + <g id="Rectangle-18"> | |
31 | + <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> | |
32 | + <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> | |
33 | + </g> | |
34 | + <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> | |
35 | + </g> | |
36 | + </g> | |
37 | + </g> | |
38 | + </g> | |
39 | +</svg> | ... | ... |
src/assets/images/loading.svg
0 → 100644
1 | +++ a/src/assets/images/loading.svg | |
1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
2 | +<svg viewBox="0 0 200 200" version="1.1" | |
3 | + xmlns="http://www.w3.org/2000/svg" | |
4 | + xmlns:xlink="http://www.w3.org/1999/xlink"> | |
5 | + <style type="text/css"> | |
6 | + .left-linear { | |
7 | + fill: url(#left-linear); | |
8 | + } | |
9 | + | |
10 | + .right-linear { | |
11 | + fill: url(#right-linear); | |
12 | + } | |
13 | + | |
14 | + .top { | |
15 | + fill: #64acff; | |
16 | + } | |
17 | + | |
18 | + .bottom { | |
19 | + fill: #9dbfe4; | |
20 | + } | |
21 | + @keyframes load { | |
22 | + 0% { | |
23 | + transform: rotate(-360deg); | |
24 | + } | |
25 | + | |
26 | + 100% { | |
27 | + transform: rotate(0); | |
28 | + } | |
29 | + } | |
30 | + | |
31 | + .load { | |
32 | + animation: load 1.4s linear infinite; | |
33 | + transform-origin: center center; | |
34 | + } | |
35 | + | |
36 | + svg { | |
37 | + display: block; | |
38 | + } | |
39 | + | |
40 | + .tip { | |
41 | + display: block; | |
42 | + min-width: 100px; | |
43 | + margin-top: 4px; | |
44 | + font-size: 13px; | |
45 | + color: #303133; | |
46 | + text-align: left; | |
47 | + } | |
48 | + </style> | |
49 | + <circle cx="97" cy="97" r="81" stroke-width="16" stroke="#327fd8" fill="none"></circle> | |
50 | + <g class="load"> | |
51 | + <!--右半圆环--> | |
52 | + <linearGradient id="left-linear" gradientUnits="userSpaceOnUse" x1="50" y1="0" x2="100" y2="180"> | |
53 | + <stop offset="0" style="stop-color: #64acff;" /> | |
54 | + <stop offset="1" style="stop-color: #9DBFE4;" /> | |
55 | + </linearGradient> | |
56 | + <path class="left-linear" d="M20,100c0-44.1,35.9-80,80-80V0C44.8,0,0,44.8,0,100s44.8,100,100,100v-20C55.9,180,20,144.1,20,100z" /> | |
57 | + <!--左半圆环--> | |
58 | + <circle class="bottom" cx="100" cy="190" r="10" /> | |
59 | + <linearGradient id="right-linear" gradientUnits="userSpaceOnUse" x1="100" y1="120" x2="100" y2="180"> | |
60 | + <stop offset="0" style="stop-color: transparent;" /> | |
61 | + <stop offset="1" style="stop-color: transparent;" /> | |
62 | + </linearGradient> | |
63 | + <path class="right-linear" d="M100,0v20c44.1,0,80,35.9,80,80c0,44.1-35.9,80-80,80v20c55.2,0,100-44.8,100-100S155.2,0,100,0z" /> | |
64 | + <!--左半圆环--> | |
65 | + <circle class="top" cx="100" cy="10" r="10" /> | |
66 | + </g> | |
67 | +</svg> | ... | ... |
src/assets/images/lock-page.jpg
0 → 100644
218 KB
src/assets/images/lock-page.png
0 → 100644
372 KB
src/assets/images/login/login-bg.png
0 → 100644
180 KB
src/assets/images/login/login-in.png
0 → 100644
91.5 KB
src/assets/images/logo.png
0 → 100644
3.95 KB
src/assets/images/no-data.png
0 → 100644
15.1 KB
src/assets/images/page_null.png
0 → 100644
9.9 KB
src/assets/images/qq.jpeg
0 → 100644
111 KB
src/assets/images/sidebar/dark-mini.png
0 → 100644
8.35 KB
src/assets/images/sidebar/dark.png
0 → 100644
24.8 KB
src/assets/images/sidebar/light-mini.png
0 → 100644
8.72 KB
src/assets/images/sidebar/light.png
0 → 100644
23.7 KB
src/assets/svg/preview/p-rotate.svg
0 → 100644
1 | +++ a/src/assets/svg/preview/p-rotate.svg | |
1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306944988" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1820" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M1464.3 279.7" p-id="1821" fill="#ffffff"></path><path d="M512 960c-60.5 0-119.1-11.9-174.4-35.2-53.4-22.6-101.3-54.9-142.4-96s-73.4-89-96-142.4C75.9 631.1 64 572.5 64 512s11.9-119.1 35.2-174.4c22.6-53.4 54.9-101.3 96-142.4s89-73.4 142.4-96C392.9 75.9 451.5 64 512 64s119.1 11.9 174.4 35.2c53.4 22.6 101.3 54.9 142.4 96s73.4 89 96 142.4C948.1 392.9 960 451.5 960 512c0 19.1-15.5 34.6-34.6 34.6s-34.6-15.5-34.6-34.6c0-51.2-10-100.8-29.8-147.4-19.1-45.1-46.4-85.6-81.2-120.4C745 209.4 704.5 182 659.4 163c-46.7-19.7-96.3-29.8-147.4-29.8-51.2 0-100.8 10-147.4 29.8-45.1 19.1-85.6 46.4-120.4 81.2S182 319.5 163 364.6c-19.7 46.7-29.8 96.3-29.8 147.4 0 51.2 10 100.8 29.8 147.4 19.1 45.1 46.4 85.6 81.2 120.4C279 814.6 319.5 842 364.6 861c46.7 19.7 96.3 29.8 147.4 29.8 64.6 0 128.4-16.5 184.4-47.8 54.4-30.4 100.9-74.1 134.6-126.6 10.3-16.1 31.7-20.8 47.8-10.4 16.1 10.3 20.8 31.7 10.4 47.8-39.8 62-94.8 113.7-159.1 149.6-66.2 37-141.7 56.6-218.1 56.6z" p-id="1822" fill="#ffffff"></path><path d="M924 552c-19.8 0-36-16.2-36-36V228c0-19.8 16.2-36 36-36s36 16.2 36 36v288c0 19.8-16.2 36-36 36zM275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1823" fill="#ffffff"></path></svg> | |
0 | 2 | \ No newline at end of file | ... | ... |
src/assets/svg/preview/resume.svg
0 → 100644
1 | +++ a/src/assets/svg/preview/resume.svg | |
1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307154239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7317" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="7318" fill="#ffffff"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="7319" fill="#ffffff"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="7320" fill="#ffffff"></path></svg> | |
0 | 2 | \ No newline at end of file | ... | ... |
src/assets/svg/preview/scale.svg
0 → 100644
1 | +++ a/src/assets/svg/preview/scale.svg | |
1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307195033" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8116" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M887.081 904.791a25.8 25.8 0 0 1-18.376-7.619L705.618 734.075l-4.163 3.369c-58.255 47.18-131.522 73.16-206.32 73.16-181.07 0-328.377-147.308-328.377-328.367 0-181.068 147.308-328.376 328.377-328.376 181.063 0 328.376 147.308 328.376 328.376 0 77.072-27.412 152.07-77.169 211.17l-3.522 4.173 162.719 162.744a25.846 25.846 0 0 1 7.639 18.432 26.081 26.081 0 0 1-26.051 26.045l-0.046-0.01zM495.13 205.957c-152.336 0-276.27 123.935-276.27 276.27 0 152.33 123.934 276.27 276.27 276.27 152.34 0 276.275-123.94 276.275-276.27 0-152.335-123.935-276.27-276.275-276.27z" fill="#ffffff" p-id="8117"></path><path d="M626.545 508.355h-262.83a26.127 26.127 0 0 1 0-52.255h262.83a26.127 26.127 0 0 1 0 52.255z" fill="#ffffff" p-id="8118"></path><path d="M495.13 639.77a26.127 26.127 0 0 1-26.128-26.128v-262.83a26.127 26.127 0 0 1 52.255 0v262.835a26.127 26.127 0 0 1-26.127 26.123z" fill="#ffffff" p-id="8119"></path></svg> | |
0 | 2 | \ No newline at end of file | ... | ... |
src/assets/svg/preview/unrotate.svg
0 → 100644
1 | +++ a/src/assets/svg/preview/unrotate.svg | |
1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306911635" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M924.8 337.6c-22.6-53.4-54.9-101.3-96-142.4s-89-73.4-142.4-96C631.1 75.9 572.5 64 512 64S392.9 75.9 337.6 99.2c-53.4 22.6-101.3 54.9-142.4 96-22.4 22.4-42.2 46.8-59.2 73.1V228c0-19.8-16.2-36-36-36s-36 16.2-36 36v288c0 19.8 16.2 36 36 36s36-16.2 36-36v-50.2c4.2-34.8 13.2-68.7 27-101.2 19.1-45.1 46.4-85.6 81.2-120.4C279 209.4 319.5 182 364.6 163c46.7-19.7 96.3-29.8 147.4-29.8 51.2 0 100.8 10 147.4 29.8 45.1 19.1 85.6 46.4 120.4 81.2C814.6 279 842 319.5 861 364.6c19.7 46.7 29.8 96.3 29.8 147.4 0 51.2-10 100.8-29.8 147.4-19.1 45.1-46.4 85.6-81.2 120.4C745 814.6 704.5 842 659.4 861c-46.7 19.7-96.3 29.8-147.4 29.8-64.6 0-128.4-16.5-184.4-47.8-54.4-30.4-100.9-74.1-134.6-126.6-10.3-16.1-31.7-20.8-47.8-10.4-16.1 10.3-20.8 31.7-10.4 47.8 39.8 62 94.8 113.7 159.1 149.6 66.2 37 141.7 56.6 218.1 56.6 60.5 0 119.1-11.9 174.4-35.2 53.4-22.6 101.3-54.9 142.4-96 41.1-41.1 73.4-89 96-142.4C948.1 631.1 960 572.5 960 512s-11.9-119.1-35.2-174.4z" p-id="1353" fill="#ffffff"></path><path d="M275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1354" fill="#ffffff"></path></svg> | |
0 | 2 | \ No newline at end of file | ... | ... |
src/assets/svg/preview/unscale.svg
0 → 100644
1 | +++ a/src/assets/svg/preview/unscale.svg | |
1 | +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595308005241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z" fill="#ffffff" p-id="9879"></path></svg> | |
0 | 2 | \ No newline at end of file | ... | ... |
src/components/Authority/index.tsx
0 → 100644
1 | +++ a/src/components/Authority/index.tsx | |
1 | +import { defineComponent, PropType, computed, unref } from 'vue'; | |
2 | + | |
3 | +import { PermissionModeEnum } from '/@/enums/appEnum'; | |
4 | +import { RoleEnum } from '/@/enums/roleEnum'; | |
5 | +import { usePermission } from '/@/hooks/web/usePermission'; | |
6 | +import { appStore } from '/@/store/modules/app'; | |
7 | +import { getSlot } from '/@/utils/helper/tsxHelper'; | |
8 | + | |
9 | +export default defineComponent({ | |
10 | + name: 'Authority', | |
11 | + props: { | |
12 | + // 指定角色可见 | |
13 | + value: { | |
14 | + type: [Number, Array, String] as PropType<RoleEnum | RoleEnum[]>, | |
15 | + default: '', | |
16 | + }, | |
17 | + }, | |
18 | + setup(props, { slots }) { | |
19 | + const getModeRef = computed(() => { | |
20 | + return appStore.getProjectConfig.permissionMode; | |
21 | + }); | |
22 | + /** | |
23 | + * 渲染角色按钮 | |
24 | + */ | |
25 | + function renderRoleAuth() { | |
26 | + const { value } = props; | |
27 | + if (!value) { | |
28 | + return getSlot(slots, 'default'); | |
29 | + } | |
30 | + const { hasPermission } = usePermission(); | |
31 | + return hasPermission(value) ? getSlot(slots, 'default') : null; | |
32 | + } | |
33 | + | |
34 | + /** | |
35 | + * 渲染编码按钮 | |
36 | + * 这里只判断是否包含,具体实现可以根据项目自行写逻辑 | |
37 | + */ | |
38 | + function renderCodeAuth() { | |
39 | + const { value } = props; | |
40 | + if (!value) { | |
41 | + return getSlot(slots, 'default'); | |
42 | + } | |
43 | + const { hasPermission } = usePermission(); | |
44 | + return hasPermission(value) ? getSlot(slots, 'default') : null; | |
45 | + } | |
46 | + return () => { | |
47 | + const mode = unref(getModeRef); | |
48 | + // 基于角色渲染 | |
49 | + if (mode === PermissionModeEnum.ROLE) { | |
50 | + return renderRoleAuth(); | |
51 | + } | |
52 | + // 基于后台编码渲染 | |
53 | + if (mode === PermissionModeEnum.BACK) { | |
54 | + return renderCodeAuth(); | |
55 | + } | |
56 | + return getSlot(slots, 'default'); | |
57 | + }; | |
58 | + }, | |
59 | +}); | ... | ... |
src/components/Basic/index.ts
0 → 100644
src/components/Basic/src/BasicArrow.vue
0 → 100644
1 | +++ a/src/components/Basic/src/BasicArrow.vue | |
1 | +<template> | |
2 | + <span :class="getClass"> | |
3 | + <RightOutlined /> | |
4 | + </span> | |
5 | +</template> | |
6 | +<script lang="ts"> | |
7 | + import type { PropType } from 'vue'; | |
8 | + | |
9 | + import { defineComponent, computed } from 'vue'; | |
10 | + | |
11 | + import { RightOutlined } from '@ant-design/icons-vue'; | |
12 | + | |
13 | + export default defineComponent({ | |
14 | + name: 'BaseArrow', | |
15 | + components: { RightOutlined }, | |
16 | + props: { | |
17 | + // Expand contract, expand by default | |
18 | + expand: { | |
19 | + type: Boolean as PropType<boolean>, | |
20 | + default: true, | |
21 | + }, | |
22 | + }, | |
23 | + setup(props) { | |
24 | + const getClass = computed(() => { | |
25 | + const preCls = 'base-arrow'; | |
26 | + const cls = [preCls]; | |
27 | + | |
28 | + props.expand && cls.push(`${preCls}__active`); | |
29 | + return cls; | |
30 | + }); | |
31 | + | |
32 | + return { | |
33 | + getClass, | |
34 | + }; | |
35 | + }, | |
36 | + }); | |
37 | +</script> | |
38 | +<style lang="less" scoped> | |
39 | + .base-arrow { | |
40 | + transform: rotate(-90deg) !important; | |
41 | + transition: all 0.3s ease 0.1s; | |
42 | + transform-origin: center center; | |
43 | + | |
44 | + &.right { | |
45 | + transform: rotate(0deg); | |
46 | + } | |
47 | + | |
48 | + &__active { | |
49 | + transform: rotate(90deg) !important; | |
50 | + transition: all 0.3s ease 0.1s !important; | |
51 | + } | |
52 | + } | |
53 | +</style> | ... | ... |
src/components/Basic/src/BasicEmpty.vue
0 → 100644
1 | +++ a/src/components/Basic/src/BasicEmpty.vue | |
1 | +<template> | |
2 | + <Empty :image="image" :description="description" /> | |
3 | +</template> | |
4 | +<script lang="ts"> | |
5 | + import { defineComponent } from 'vue'; | |
6 | + import { Empty } from 'ant-design-vue'; | |
7 | + | |
8 | + import emptySrc from '/@/assets/images/page_null.png'; | |
9 | + | |
10 | + export default defineComponent({ | |
11 | + extends: Empty as any, | |
12 | + components: { Empty }, | |
13 | + props: { | |
14 | + description: { | |
15 | + type: String, | |
16 | + default: '暂无内容', | |
17 | + }, | |
18 | + image: { | |
19 | + type: String, | |
20 | + default: emptySrc, | |
21 | + required: false, | |
22 | + }, | |
23 | + }, | |
24 | + setup() { | |
25 | + return {}; | |
26 | + }, | |
27 | + }); | |
28 | +</script> | ... | ... |
src/components/Basic/src/BasicHelp.less
0 → 100644
1 | +++ a/src/components/Basic/src/BasicHelp.less | |
1 | +@import (reference) '../../../design/index.less'; | |
2 | + | |
3 | +.base-help { | |
4 | + display: inline-block; | |
5 | + font-size: 14px; | |
6 | + color: @text-color-help-dark; | |
7 | + cursor: pointer; | |
8 | + | |
9 | + &:hover { | |
10 | + color: @primary-color; | |
11 | + } | |
12 | + | |
13 | + &__wrap { | |
14 | + p { | |
15 | + margin-bottom: 0; | |
16 | + } | |
17 | + } | |
18 | +} | ... | ... |
src/components/Basic/src/BasicHelp.tsx
0 → 100644
1 | +++ a/src/components/Basic/src/BasicHelp.tsx | |
1 | +import type { PropType } from 'vue'; | |
2 | + | |
3 | +import { Tooltip } from 'ant-design-vue'; | |
4 | +import { InfoCircleOutlined } from '@ant-design/icons-vue'; | |
5 | +import { defineComponent, computed, unref } from 'vue'; | |
6 | + | |
7 | +import { getPopupContainer } from '/@/utils'; | |
8 | + | |
9 | +import { isString, isArray } from '/@/utils/is'; | |
10 | +import { getSlot } from '/@/utils/helper/tsxHelper'; | |
11 | +import './BasicHelp.less'; | |
12 | +export default defineComponent({ | |
13 | + name: 'BaseHelp', | |
14 | + props: { | |
15 | + // max-width | |
16 | + maxWidth: { | |
17 | + type: String as PropType<string>, | |
18 | + default: '600px', | |
19 | + }, | |
20 | + // Whether to display the serial number | |
21 | + showIndex: { | |
22 | + type: Boolean as PropType<boolean>, | |
23 | + default: false, | |
24 | + }, | |
25 | + // Text list | |
26 | + text: { | |
27 | + type: [Array, String] as PropType<string[] | string>, | |
28 | + }, | |
29 | + // color | |
30 | + color: { | |
31 | + type: String as PropType<string>, | |
32 | + default: '#ffffff', | |
33 | + }, | |
34 | + fontSize: { | |
35 | + type: String as PropType<string>, | |
36 | + default: '14px', | |
37 | + }, | |
38 | + absolute: { | |
39 | + type: Boolean as PropType<boolean>, | |
40 | + default: false, | |
41 | + }, | |
42 | + // 定位 | |
43 | + position: { | |
44 | + type: [Object] as PropType<any>, | |
45 | + default: () => ({ | |
46 | + position: 'absolute', | |
47 | + left: 0, | |
48 | + bottom: 0, | |
49 | + }), | |
50 | + }, | |
51 | + }, | |
52 | + setup(props, { slots }) { | |
53 | + const getOverlayStyleRef = computed(() => { | |
54 | + return { | |
55 | + maxWidth: props.maxWidth, | |
56 | + }; | |
57 | + }); | |
58 | + const getWrapStyleRef = computed(() => { | |
59 | + return { | |
60 | + color: props.color, | |
61 | + fontSize: props.fontSize, | |
62 | + }; | |
63 | + }); | |
64 | + const getMainStyleRef = computed(() => { | |
65 | + return props.absolute ? props.position : {}; | |
66 | + }); | |
67 | + | |
68 | + /** | |
69 | + * @description: 渲染内容 | |
70 | + */ | |
71 | + const renderTitle = () => { | |
72 | + const list = props.text; | |
73 | + if (isString(list)) { | |
74 | + return <p>{list}</p>; | |
75 | + } | |
76 | + if (isArray(list)) { | |
77 | + return list.map((item, index) => { | |
78 | + return ( | |
79 | + <p key={item}> | |
80 | + {props.showIndex ? `${index + 1}. ` : ''} | |
81 | + {item} | |
82 | + </p> | |
83 | + ); | |
84 | + }); | |
85 | + } | |
86 | + return null; | |
87 | + }; | |
88 | + return () => ( | |
89 | + <Tooltip | |
90 | + title={(<div style={unref(getWrapStyleRef)}>{renderTitle()}</div>) as any} | |
91 | + placement="right" | |
92 | + overlayStyle={unref(getOverlayStyleRef)} | |
93 | + autoAdjustOverflow={true} | |
94 | + overlayClassName="base-help__wrap" | |
95 | + getPopupContainer={() => getPopupContainer()} | |
96 | + > | |
97 | + {{ | |
98 | + default: () => ( | |
99 | + <span class="base-help" style={unref(getMainStyleRef)}> | |
100 | + {getSlot(slots) || <InfoCircleOutlined />} | |
101 | + </span> | |
102 | + ), | |
103 | + }} | |
104 | + </Tooltip> | |
105 | + ); | |
106 | + }, | |
107 | +}); | ... | ... |
src/components/Basic/src/BasicTitle.vue
0 → 100644
1 | +++ a/src/components/Basic/src/BasicTitle.vue | |
1 | +<template> | |
2 | + <span class="base-title" :class="{ 'show-span': showSpan && $slots.default }"> | |
3 | + <slot /> | |
4 | + <BaseHelp class="base-title__help" v-if="helpMessage" :text="helpMessage" /> | |
5 | + </span> | |
6 | +</template> | |
7 | +<script lang="ts"> | |
8 | + import type { PropType } from 'vue'; | |
9 | + | |
10 | + import { defineComponent } from 'vue'; | |
11 | + | |
12 | + export default defineComponent({ | |
13 | + name: 'BaseTitle', | |
14 | + props: { | |
15 | + helpMessage: { | |
16 | + type: [String, Array] as PropType<string | string[]>, | |
17 | + default: '', | |
18 | + }, | |
19 | + showSpan: { | |
20 | + type: Boolean as PropType<boolean>, | |
21 | + default: true, | |
22 | + }, | |
23 | + }, | |
24 | + setup() { | |
25 | + return {}; | |
26 | + }, | |
27 | + }); | |
28 | +</script> | |
29 | +<style lang="less" scoped> | |
30 | + @import (reference) '../../../design/index.less'; | |
31 | + | |
32 | + .base-title { | |
33 | + position: relative; | |
34 | + display: flex; | |
35 | + padding-left: 7px; | |
36 | + font-size: 16px; | |
37 | + font-weight: 700; | |
38 | + line-height: 24px; | |
39 | + color: @text-color-base; | |
40 | + | |
41 | + .unselect(); | |
42 | + | |
43 | + &.show-span::before { | |
44 | + position: absolute; | |
45 | + top: 4px; | |
46 | + left: 0; | |
47 | + width: 3px; | |
48 | + height: 16px; | |
49 | + margin-right: 4px; | |
50 | + background: @primary-color; | |
51 | + content: ''; | |
52 | + } | |
53 | + | |
54 | + &__help { | |
55 | + margin-left: 10px; | |
56 | + } | |
57 | + } | |
58 | +</style> | ... | ... |
src/components/Breadcrumb/Breadcrumb.vue
0 → 100644
1 | +++ a/src/components/Breadcrumb/Breadcrumb.vue | |
1 | +<template> | |
2 | + <div ref="breadcrumbRef" class="breadcrumb"> | |
3 | + <slot /> | |
4 | + </div> | |
5 | +</template> | |
6 | + | |
7 | +<script lang="ts"> | |
8 | + import type { PropType } from 'vue'; | |
9 | + import { defineComponent, provide, ref } from 'vue'; | |
10 | + | |
11 | + export default defineComponent({ | |
12 | + name: 'Breadcrumb', | |
13 | + props: { | |
14 | + separator: { | |
15 | + type: String as PropType<string>, | |
16 | + default: '/', | |
17 | + }, | |
18 | + separatorClass: { | |
19 | + type: String as PropType<string>, | |
20 | + default: '', | |
21 | + }, | |
22 | + }, | |
23 | + setup(props) { | |
24 | + const breadcrumbRef = ref<Nullable<HTMLElement>>(null); | |
25 | + | |
26 | + provide('breadcrumb', props); | |
27 | + | |
28 | + return { | |
29 | + breadcrumbRef, | |
30 | + }; | |
31 | + }, | |
32 | + }); | |
33 | +</script> | |
34 | +<style lang="less"> | |
35 | + @import (reference) '../../design/index.less'; | |
36 | + | |
37 | + .breadcrumb { | |
38 | + height: @header-height; | |
39 | + padding-right: 20px; | |
40 | + font-size: 14px; | |
41 | + line-height: @header-height; | |
42 | + // line-height: 1; | |
43 | + | |
44 | + &::after, | |
45 | + &::before { | |
46 | + display: table; | |
47 | + content: ''; | |
48 | + } | |
49 | + | |
50 | + &::after { | |
51 | + clear: both; | |
52 | + } | |
53 | + | |
54 | + &__separator { | |
55 | + margin: 0 9px; | |
56 | + font-weight: 700; | |
57 | + color: @breadcrumb-item-normal-color; | |
58 | + | |
59 | + &[class*='icon'] { | |
60 | + margin: 0 6px; | |
61 | + font-weight: 400; | |
62 | + } | |
63 | + } | |
64 | + | |
65 | + &__item { | |
66 | + float: left; | |
67 | + } | |
68 | + | |
69 | + &__inner { | |
70 | + color: @breadcrumb-item-normal-color; | |
71 | + | |
72 | + &.is-link, | |
73 | + a { | |
74 | + font-weight: 700; | |
75 | + color: @text-color-base; | |
76 | + text-decoration: none; | |
77 | + transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); | |
78 | + } | |
79 | + | |
80 | + a:hover, | |
81 | + &.is-link:hover { | |
82 | + color: @primary-color; | |
83 | + cursor: pointer; | |
84 | + } | |
85 | + } | |
86 | + | |
87 | + &__item:last-child .breadcrumb__inner, | |
88 | + &__item:last-child &__inner a, | |
89 | + &__item:last-child &__inner a:hover, | |
90 | + &__item:last-child &__inner:hover { | |
91 | + font-weight: 400; | |
92 | + color: @breadcrumb-item-normal-color; | |
93 | + cursor: text; | |
94 | + } | |
95 | + | |
96 | + &__item:last-child &__separator { | |
97 | + display: none; | |
98 | + } | |
99 | + } | |
100 | +</style> | ... | ... |
src/components/Breadcrumb/BreadcrumbItem.vue
0 → 100644
1 | +++ a/src/components/Breadcrumb/BreadcrumbItem.vue | |
1 | +<template> | |
2 | + <span class="breadcrumb__item"> | |
3 | + <span ref="linkRef" :class="['breadcrumb__inner', to || isLink ? 'is-link' : '']"> | |
4 | + <slot /> | |
5 | + </span> | |
6 | + <i v-if="separatorClass" class="breadcrumb__separator" :class="separatorClass"></i> | |
7 | + <span v-else class="breadcrumb__separator">{{ separator }}</span> | |
8 | + </span> | |
9 | +</template> | |
10 | + | |
11 | +<script lang="ts"> | |
12 | + import { defineComponent, inject, ref, onMounted, unref } from 'vue'; | |
13 | + import { useRouter } from 'vue-router'; | |
14 | + import { useEvent } from '/@/hooks/event/useEvent'; | |
15 | + | |
16 | + export default defineComponent({ | |
17 | + name: 'BreadcrumbItem', | |
18 | + props: { | |
19 | + to: { | |
20 | + type: [String, Object], | |
21 | + default: '', | |
22 | + }, | |
23 | + replace: { | |
24 | + type: Boolean, | |
25 | + default: false, | |
26 | + }, | |
27 | + isLink: { | |
28 | + type: Boolean, | |
29 | + default: false, | |
30 | + }, | |
31 | + }, | |
32 | + setup(props) { | |
33 | + const linkRef = ref<Nullable<HTMLElement>>(null); | |
34 | + const parent = inject('breadcrumb') as { | |
35 | + separator: string; | |
36 | + separatorClass: string; | |
37 | + }; | |
38 | + const { push, replace } = useRouter(); | |
39 | + | |
40 | + onMounted(() => { | |
41 | + const link = unref(linkRef); | |
42 | + if (!link) return; | |
43 | + useEvent({ | |
44 | + el: link, | |
45 | + listener: () => { | |
46 | + const { to } = props; | |
47 | + if (!props.to) return; | |
48 | + props.replace ? replace(to) : push(to); | |
49 | + }, | |
50 | + name: 'click', | |
51 | + wait: 0, | |
52 | + }); | |
53 | + }); | |
54 | + | |
55 | + return { | |
56 | + linkRef, | |
57 | + separator: parent.separator && parent.separator, | |
58 | + separatorClass: parent.separatorClass && parent.separatorClass, | |
59 | + }; | |
60 | + }, | |
61 | + }); | |
62 | +</script> | ... | ... |
src/components/Button/index.vue
0 → 100644
1 | +++ a/src/components/Button/index.vue | |
1 | +<template> | |
2 | + <Button v-bind="getBindValue" :class="[getColor, $attrs.class]"> | |
3 | + <template v-slot:[item] v-for="item in Object.keys($slots)"> | |
4 | + <slot :name="item" /> | |
5 | + </template> | |
6 | + </Button> | |
7 | +</template> | |
8 | +<script lang="ts"> | |
9 | + import { PropType } from 'vue'; | |
10 | + | |
11 | + import { defineComponent, computed, unref } from 'vue'; | |
12 | + import { Button } from 'ant-design-vue'; | |
13 | + // import { extendSlots } from '/@/utils/helper/tsxHelper'; | |
14 | + import { useThrottle } from '/@/hooks/core/useThrottle'; | |
15 | + import { isFunction } from '/@/utils/is'; | |
16 | + export default defineComponent({ | |
17 | + name: 'AButton', | |
18 | + inheritAttrs: false, | |
19 | + components: { Button }, | |
20 | + props: { | |
21 | + // 按钮类型 | |
22 | + type: { | |
23 | + type: String as PropType<'primary' | 'default' | 'danger' | 'dashed' | 'link'>, | |
24 | + default: 'default', | |
25 | + }, | |
26 | + // 节流防抖类型 throttle debounce | |
27 | + throttle: { | |
28 | + type: String as PropType<'throttle' | 'debounce'>, | |
29 | + default: 'throttle', | |
30 | + }, | |
31 | + color: { | |
32 | + type: String as PropType<'error' | 'warning' | 'success'>, | |
33 | + }, | |
34 | + // 防抖节流时间 | |
35 | + throttleTime: { | |
36 | + type: Number as PropType<number>, | |
37 | + default: 0, | |
38 | + }, | |
39 | + loading: { | |
40 | + type: Boolean as PropType<boolean>, | |
41 | + default: false, | |
42 | + }, | |
43 | + disabled: { | |
44 | + type: Boolean as PropType<boolean>, | |
45 | + default: false, | |
46 | + }, | |
47 | + }, | |
48 | + setup(props, { attrs }) { | |
49 | + const getListeners = computed(() => { | |
50 | + const { throttle, throttleTime = 0 } = props; | |
51 | + // 是否开启节流防抖 | |
52 | + const throttleType = throttle!.toLowerCase(); | |
53 | + const isDebounce = throttleType === 'debounce'; | |
54 | + const openThrottle = ['throttle', 'debounce'].includes(throttleType) && throttleTime > 0; | |
55 | + | |
56 | + const on: { | |
57 | + onClick?: Fn; | |
58 | + } = {}; | |
59 | + | |
60 | + if (attrs.onClick && isFunction(attrs.onClick) && openThrottle) { | |
61 | + const [handler] = useThrottle(attrs.onClick as any, throttleTime!, { | |
62 | + debounce: isDebounce, | |
63 | + immediate: true, | |
64 | + }); | |
65 | + on.onClick = handler; | |
66 | + } | |
67 | + | |
68 | + return { | |
69 | + ...attrs, | |
70 | + ...on, | |
71 | + }; | |
72 | + }); | |
73 | + | |
74 | + const getColor = computed(() => { | |
75 | + const res: string[] = []; | |
76 | + const { color, disabled } = props; | |
77 | + color && res.push(`ant-btn-${color}`); | |
78 | + disabled && res.push('is-disabled'); | |
79 | + return res; | |
80 | + }); | |
81 | + | |
82 | + const getBindValue = computed((): any => { | |
83 | + return { ...unref(getListeners), ...props }; | |
84 | + }); | |
85 | + return { getBindValue, getColor }; | |
86 | + }, | |
87 | + }); | |
88 | +</script> | ... | ... |
src/components/Button/types.ts
0 → 100644
1 | +++ a/src/components/Button/types.ts | |
1 | +import { VNodeChild } from 'vue'; | |
2 | + | |
3 | +export interface BasicButtonProps { | |
4 | + /** | |
5 | + * can be set to primary ghost dashed danger(added in 2.7) or omitted (meaning default) | |
6 | + * @default 'default' | |
7 | + * @type string | |
8 | + */ | |
9 | + type?: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'; | |
10 | + | |
11 | + /** | |
12 | + * set the original html type of button | |
13 | + * @default 'button' | |
14 | + * @type string | |
15 | + */ | |
16 | + htmlType?: 'button' | 'submit' | 'reset' | 'menu'; | |
17 | + | |
18 | + /** | |
19 | + * set the icon of button | |
20 | + * @type string | |
21 | + */ | |
22 | + icon?: VNodeChild | JSX.Element; | |
23 | + | |
24 | + /** | |
25 | + * can be set to circle or circle-outline or omitted | |
26 | + * @type string | |
27 | + */ | |
28 | + shape?: 'circle' | 'circle-outline'; | |
29 | + | |
30 | + /** | |
31 | + * can be set to small large or omitted | |
32 | + * @default 'default' | |
33 | + * @type string | |
34 | + */ | |
35 | + size?: 'small' | 'large' | 'default'; | |
36 | + | |
37 | + /** | |
38 | + * set the loading status of button | |
39 | + * @default false | |
40 | + * @type boolean | { delay: number } | |
41 | + */ | |
42 | + loading?: boolean | { delay: number }; | |
43 | + | |
44 | + /** | |
45 | + * disabled state of button | |
46 | + * @default false | |
47 | + * @type boolean | |
48 | + */ | |
49 | + disabled?: boolean; | |
50 | + | |
51 | + /** | |
52 | + * make background transparent and invert text and border colors, added in 2.7 | |
53 | + * @default false | |
54 | + * @type boolean | |
55 | + */ | |
56 | + ghost?: boolean; | |
57 | + | |
58 | + /** | |
59 | + * option to fit button width to its parent width | |
60 | + * @default false | |
61 | + * @type boolean | |
62 | + */ | |
63 | + block?: boolean; | |
64 | + | |
65 | + onClick?: (e?: Event) => void; | |
66 | +} | ... | ... |
src/components/ClickOutSide/index.vue
0 → 100644
1 | +++ a/src/components/ClickOutSide/index.vue | |
1 | +<template> | |
2 | + <div ref="wrapRef"><slot /></div> | |
3 | +</template> | |
4 | +<script lang="ts"> | |
5 | + import type { Ref } from 'vue'; | |
6 | + import { defineComponent, ref } from 'vue'; | |
7 | + | |
8 | + import { useClickOutside } from '/@/hooks/web/useClickOutside'; | |
9 | + | |
10 | + export default defineComponent({ | |
11 | + name: 'ClickOutSide', | |
12 | + | |
13 | + setup(_, { emit }) { | |
14 | + const wrapRef = ref<Nullable<HTMLDivElement | null>>(null); | |
15 | + useClickOutside(wrapRef as Ref<HTMLDivElement>, () => { | |
16 | + emit('clickOutside'); | |
17 | + }); | |
18 | + return { wrapRef }; | |
19 | + }, | |
20 | + }); | |
21 | +</script> | ... | ... |
src/components/Container/index.ts
0 → 100644
1 | +++ a/src/components/Container/index.ts | |
1 | +export { default as ScrollContainer } from './src/ScrollContainer.vue'; | |
2 | +export { default as CollapseContainer } from './src/collapse/CollapseContainer.vue'; | |
3 | +export { default as LazyContainer } from './src/LazyContainer'; | |
4 | + | |
5 | +export * from './src/types.d'; | ... | ... |
src/components/Container/src/LazyContainer.less
0 → 100644
1 | +++ a/src/components/Container/src/LazyContainer.less | |
1 | +.lazy-container-enter { | |
2 | + opacity: 0; | |
3 | +} | |
4 | + | |
5 | +.lazy-container-enter-to { | |
6 | + opacity: 1; | |
7 | +} | |
8 | + | |
9 | +.lazy-container-enter-from, | |
10 | +.lazy-container-enter-active { | |
11 | + position: absolute; | |
12 | + top: 0; | |
13 | + width: 100%; | |
14 | + transition: opacity 0.3s 0.2s; | |
15 | +} | |
16 | + | |
17 | +.lazy-container-leave { | |
18 | + opacity: 1; | |
19 | +} | |
20 | + | |
21 | +.lazy-container-leave-to { | |
22 | + opacity: 0; | |
23 | +} | |
24 | + | |
25 | +.lazy-container-leave-active { | |
26 | + transition: opacity 0.5s; | |
27 | +} | ... | ... |
src/components/Container/src/LazyContainer.tsx
0 → 100644
1 | +++ a/src/components/Container/src/LazyContainer.tsx | |
1 | +import type { PropType } from 'vue'; | |
2 | + | |
3 | +import { | |
4 | + defineComponent, | |
5 | + reactive, | |
6 | + onMounted, | |
7 | + ref, | |
8 | + unref, | |
9 | + onUnmounted, | |
10 | + TransitionGroup, | |
11 | +} from 'vue'; | |
12 | + | |
13 | +import { Skeleton } from 'ant-design-vue'; | |
14 | +import { useRaf } from '/@/hooks/event/useRaf'; | |
15 | +import { useTimeout } from '/@/hooks/core/useTimeout'; | |
16 | +import { getListeners, getSlot } from '/@/utils/helper/tsxHelper'; | |
17 | + | |
18 | +import './LazyContainer.less'; | |
19 | + | |
20 | +interface State { | |
21 | + isInit: boolean; | |
22 | + loading: boolean; | |
23 | + intersectionObserverInstance: IntersectionObserver | null; | |
24 | +} | |
25 | +export default defineComponent({ | |
26 | + name: 'LazyContainer', | |
27 | + emits: ['before-init', 'init'], | |
28 | + props: { | |
29 | + // 等待时间,如果指定了时间,不论可见与否,在指定时间之后自动加载 | |
30 | + timeout: { | |
31 | + type: Number as PropType<number>, | |
32 | + default: 8000, | |
33 | + // default: 8000, | |
34 | + }, | |
35 | + // 组件所在的视口,如果组件是在页面容器内滚动,视口就是该容器 | |
36 | + viewport: { | |
37 | + type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>, | |
38 | + default: () => null, | |
39 | + }, | |
40 | + // 预加载阈值, css单位 | |
41 | + threshold: { | |
42 | + type: String as PropType<string>, | |
43 | + default: '0px', | |
44 | + }, | |
45 | + | |
46 | + // 视口的滚动方向, vertical代表垂直方向,horizontal代表水平方向 | |
47 | + direction: { | |
48 | + type: String as PropType<'vertical' | 'horizontal'>, | |
49 | + default: 'vertical', | |
50 | + }, | |
51 | + // 包裹组件的外层容器的标签名 | |
52 | + tag: { | |
53 | + type: String as PropType<string>, | |
54 | + default: 'div', | |
55 | + }, | |
56 | + | |
57 | + maxWaitingTime: { | |
58 | + type: Number as PropType<number>, | |
59 | + default: 80, | |
60 | + }, | |
61 | + | |
62 | + // 是否在不可见的时候销毁 | |
63 | + autoDestory: { | |
64 | + type: Boolean as PropType<boolean>, | |
65 | + default: false, | |
66 | + }, | |
67 | + | |
68 | + // transition name | |
69 | + transitionName: { | |
70 | + type: String as PropType<string>, | |
71 | + default: 'lazy-container', | |
72 | + }, | |
73 | + }, | |
74 | + setup(props, { attrs, emit, slots }) { | |
75 | + const elRef = ref<any>(null); | |
76 | + const state = reactive<State>({ | |
77 | + isInit: false, | |
78 | + loading: false, | |
79 | + intersectionObserverInstance: null, | |
80 | + }); | |
81 | + | |
82 | + // If there is a set delay time, it will be executed immediately | |
83 | + function immediateInit() { | |
84 | + const { timeout } = props; | |
85 | + timeout && | |
86 | + useTimeout(() => { | |
87 | + init(); | |
88 | + }, timeout); | |
89 | + } | |
90 | + | |
91 | + function init() { | |
92 | + // At this point, the skeleton component is about to be switched | |
93 | + emit('before-init'); | |
94 | + // At this point you can prepare to load the resources of the lazy-loaded component | |
95 | + state.loading = true; | |
96 | + | |
97 | + requestAnimationFrameFn(() => { | |
98 | + state.isInit = true; | |
99 | + emit('init'); | |
100 | + }); | |
101 | + } | |
102 | + function requestAnimationFrameFn(callback: () => any) { | |
103 | + // Prevent waiting too long without executing the callback | |
104 | + // Set the maximum waiting time | |
105 | + useTimeout(() => { | |
106 | + if (state.isInit) { | |
107 | + return; | |
108 | + } | |
109 | + callback(); | |
110 | + }, props.maxWaitingTime || 80); | |
111 | + | |
112 | + const { requestAnimationFrame } = useRaf(); | |
113 | + | |
114 | + return requestAnimationFrame; | |
115 | + } | |
116 | + function initIntersectionObserver() { | |
117 | + const { timeout, direction, threshold, viewport } = props; | |
118 | + if (timeout) { | |
119 | + return; | |
120 | + } | |
121 | + // According to the scrolling direction to construct the viewport margin, used to load in advance | |
122 | + let rootMargin; | |
123 | + switch (direction) { | |
124 | + case 'vertical': | |
125 | + rootMargin = `${threshold} 0px`; | |
126 | + break; | |
127 | + case 'horizontal': | |
128 | + rootMargin = `0px ${threshold}`; | |
129 | + break; | |
130 | + } | |
131 | + try { | |
132 | + // Observe the intersection of the viewport and the component container | |
133 | + state.intersectionObserverInstance = new window.IntersectionObserver(intersectionHandler, { | |
134 | + rootMargin, | |
135 | + root: viewport, | |
136 | + threshold: [0, Number.MIN_VALUE, 0.01], | |
137 | + }); | |
138 | + | |
139 | + const el = unref(elRef); | |
140 | + | |
141 | + state.intersectionObserverInstance.observe(el.$el); | |
142 | + } catch (e) { | |
143 | + init(); | |
144 | + } | |
145 | + } | |
146 | + // Cross-condition change handling function | |
147 | + function intersectionHandler(entries: any[]) { | |
148 | + const isIntersecting = entries[0].isIntersecting || entries[0].intersectionRatio; | |
149 | + if (isIntersecting) { | |
150 | + init(); | |
151 | + if (state.intersectionObserverInstance) { | |
152 | + const el = unref(elRef); | |
153 | + state.intersectionObserverInstance.unobserve(el.$el); | |
154 | + } | |
155 | + } | |
156 | + // else { | |
157 | + // const { autoDestory } = props; | |
158 | + // autoDestory && destory(); | |
159 | + // } | |
160 | + } | |
161 | + // function destory() { | |
162 | + // emit('beforeDestory'); | |
163 | + // state.loading = false; | |
164 | + // nextTick(() => { | |
165 | + // emit('destory'); | |
166 | + // }); | |
167 | + // } | |
168 | + | |
169 | + immediateInit(); | |
170 | + onMounted(() => { | |
171 | + initIntersectionObserver(); | |
172 | + }); | |
173 | + onUnmounted(() => { | |
174 | + // Cancel the observation before the component is destroyed | |
175 | + if (state.intersectionObserverInstance) { | |
176 | + const el = unref(elRef); | |
177 | + state.intersectionObserverInstance.unobserve(el.$el); | |
178 | + } | |
179 | + }); | |
180 | + | |
181 | + function renderContent() { | |
182 | + const { isInit, loading } = state; | |
183 | + if (isInit) { | |
184 | + return <div key="component">{getSlot(slots, 'default', { loading })}</div>; | |
185 | + } | |
186 | + if (slots.skeleton) { | |
187 | + return <div key="skeleton">{getSlot(slots, 'skeleton') || <Skeleton />}</div>; | |
188 | + } | |
189 | + return null; | |
190 | + } | |
191 | + return () => { | |
192 | + const { tag, transitionName } = props; | |
193 | + return ( | |
194 | + <TransitionGroup ref={elRef} name={transitionName} tag={tag} {...getListeners(attrs)}> | |
195 | + {() => renderContent()} | |
196 | + </TransitionGroup> | |
197 | + ); | |
198 | + }; | |
199 | + }, | |
200 | +}); | ... | ... |
src/components/Container/src/ScrollContainer.vue
0 → 100644
1 | +++ a/src/components/Container/src/ScrollContainer.vue | |
1 | +<template> | |
2 | + <Scrollbar | |
3 | + ref="scrollbarRef" | |
4 | + :wrapClass="`scrollbar__wrap`" | |
5 | + :viewClass="`scrollbar__view`" | |
6 | + class="scroll-container" | |
7 | + > | |
8 | + <slot /> | |
9 | + </Scrollbar> | |
10 | +</template> | |
11 | + | |
12 | +<script lang="ts"> | |
13 | + // component | |
14 | + import { defineComponent, ref, unref, nextTick } from 'vue'; | |
15 | + import { Scrollbar } from '/@/components/Scrollbar'; | |
16 | + | |
17 | + // hook | |
18 | + import { useScrollTo } from '/@/hooks/event/useScrollTo'; | |
19 | + | |
20 | + export default defineComponent({ | |
21 | + name: 'ScrollContainer', | |
22 | + components: { Scrollbar }, | |
23 | + setup() { | |
24 | + const scrollbarRef = ref<RefInstanceType<any>>(null); | |
25 | + | |
26 | + function scrollTo(to: number, duration = 500) { | |
27 | + const scrollbar = unref(scrollbarRef); | |
28 | + if (!scrollbar) return; | |
29 | + nextTick(() => { | |
30 | + const { start } = useScrollTo({ | |
31 | + el: unref(scrollbar.$.wrap), | |
32 | + to, | |
33 | + duration, | |
34 | + }); | |
35 | + start(); | |
36 | + }); | |
37 | + } | |
38 | + | |
39 | + function getScrollWrap() { | |
40 | + const scrollbar = unref(scrollbarRef); | |
41 | + if (!scrollbar) return null; | |
42 | + return scrollbar.$.wrap; | |
43 | + } | |
44 | + | |
45 | + function scrollBottom() { | |
46 | + const scrollbar = unref(scrollbarRef); | |
47 | + if (!scrollbar) return; | |
48 | + nextTick(() => { | |
49 | + const scrollHeight = scrollbar.$.wrap.scrollHeight as number; | |
50 | + const { start } = useScrollTo({ | |
51 | + el: unref(scrollbar.$.wrap), | |
52 | + to: scrollHeight, | |
53 | + }); | |
54 | + start(); | |
55 | + }); | |
56 | + } | |
57 | + return { | |
58 | + scrollbarRef, | |
59 | + scrollTo, | |
60 | + scrollBottom, | |
61 | + getScrollWrap, | |
62 | + }; | |
63 | + }, | |
64 | + }); | |
65 | +</script> | |
66 | +<style lang="less"> | |
67 | + .scroll-container { | |
68 | + width: 100%; | |
69 | + height: 100%; | |
70 | + | |
71 | + .scrollbar__wrap { | |
72 | + margin-bottom: 18px !important; | |
73 | + overflow-x: hidden; | |
74 | + } | |
75 | + | |
76 | + .scrollbar__view { | |
77 | + box-sizing: border-box; | |
78 | + } | |
79 | + } | |
80 | +</style> | ... | ... |
src/components/Container/src/collapse/CollapseContainer.vue
0 → 100644
1 | +++ a/src/components/Container/src/collapse/CollapseContainer.vue | |
1 | +<template> | |
2 | + <div class="collapse-container p-2 bg:white rounded-sm"> | |
3 | + <CollapseHeader v-bind="$props" :show="show" @expand="handleExpand" /> | |
4 | + <CollapseTransition :enable="canExpan"> | |
5 | + <Skeleton v-if="loading" /> | |
6 | + <div class="collapse-container__body" v-else v-show="show"> | |
7 | + <LazyContainer :timeout="lazyTime" v-if="lazy"> | |
8 | + <slot /> | |
9 | + <template v-slot:skeleton> | |
10 | + <slot name="lazySkeleton" /> | |
11 | + </template> | |
12 | + </LazyContainer> | |
13 | + <slot /> | |
14 | + </div> | |
15 | + </CollapseTransition> | |
16 | + </div> | |
17 | +</template> | |
18 | +<script lang="ts"> | |
19 | + import type { PropType } from 'vue'; | |
20 | + | |
21 | + import { defineComponent, ref, unref } from 'vue'; | |
22 | + // component | |
23 | + import { CollapseTransition } from '/@/components/Transition/index'; | |
24 | + import CollapseHeader from './CollapseHeader.vue'; | |
25 | + import { Skeleton } from 'ant-design-vue'; | |
26 | + | |
27 | + import LazyContainer from '../LazyContainer'; | |
28 | + | |
29 | + import { triggerWindowResize } from '/@/utils/event/triggerWindowResizeEvent'; | |
30 | + // hook | |
31 | + import { useTimeout } from '/@/hooks/core/useTimeout'; | |
32 | + export default defineComponent({ | |
33 | + components: { Skeleton, LazyContainer, CollapseHeader, CollapseTransition }, | |
34 | + name: 'CollapseContainer', | |
35 | + props: { | |
36 | + // 标题 | |
37 | + title: { | |
38 | + type: String as PropType<string>, | |
39 | + default: '', | |
40 | + }, | |
41 | + // 是否可以展开 | |
42 | + canExpan: { | |
43 | + type: Boolean as PropType<boolean>, | |
44 | + default: true, | |
45 | + }, | |
46 | + // 标题右侧温馨提醒 | |
47 | + helpMessage: { | |
48 | + type: [Array, String] as PropType<string[] | string>, | |
49 | + default: '', | |
50 | + }, | |
51 | + // 展开收缩的时候是否触发window.resize, | |
52 | + // 可以适应表格和表单,当表单收缩起来,表格触发resize 自适应高度 | |
53 | + triggerWindowResize: { | |
54 | + type: Boolean as PropType<boolean>, | |
55 | + default: false, | |
56 | + }, | |
57 | + loading: { | |
58 | + type: Boolean as PropType<boolean>, | |
59 | + default: false, | |
60 | + }, | |
61 | + // 延时加载 | |
62 | + lazy: { | |
63 | + type: Boolean as PropType<boolean>, | |
64 | + default: false, | |
65 | + }, | |
66 | + // 延时加载时间 | |
67 | + lazyTime: { | |
68 | + type: Number as PropType<number>, | |
69 | + default: 3000, | |
70 | + }, | |
71 | + }, | |
72 | + setup(props) { | |
73 | + const showRef = ref(true); | |
74 | + /** | |
75 | + * @description: 处理开展事件 | |
76 | + */ | |
77 | + function handleExpand() { | |
78 | + const hasShow = !unref(showRef); | |
79 | + showRef.value = hasShow; | |
80 | + | |
81 | + if (props.triggerWindowResize) { | |
82 | + // 这里200毫秒是因为展开有动画, | |
83 | + useTimeout(triggerWindowResize, 200); | |
84 | + } | |
85 | + } | |
86 | + return { | |
87 | + show: showRef, | |
88 | + handleExpand, | |
89 | + }; | |
90 | + }, | |
91 | + }); | |
92 | +</script> | |
93 | +<style lang="less"> | |
94 | + .collapse-container { | |
95 | + padding: 10px; | |
96 | + background: #fff; | |
97 | + border-radius: 8px; | |
98 | + transition: all 0.3s ease-in-out; | |
99 | + | |
100 | + &.no-shadow { | |
101 | + box-shadow: none; | |
102 | + } | |
103 | + | |
104 | + &__header { | |
105 | + display: flex; | |
106 | + height: 32px; | |
107 | + margin-bottom: 10px; | |
108 | + justify-content: space-between; | |
109 | + align-items: center; | |
110 | + } | |
111 | + | |
112 | + &__action { | |
113 | + display: flex; | |
114 | + align-items: center; | |
115 | + } | |
116 | + } | |
117 | +</style> | ... | ... |
src/components/Container/src/collapse/CollapseHeader.vue
0 → 100644
1 | +++ a/src/components/Container/src/collapse/CollapseHeader.vue | |
1 | +<template> | |
2 | + <div class="collapse-container__header"> | |
3 | + <BasicTitle :helpMessage="$attrs.helpMessage"> | |
4 | + <template v-if="$attrs.title"> | |
5 | + {{ $attrs.title }} | |
6 | + </template> | |
7 | + <template v-else> | |
8 | + <slot name="title" /> | |
9 | + </template> | |
10 | + </BasicTitle> | |
11 | + | |
12 | + <div class="collapse-container__action"> | |
13 | + <slot name="action" /> | |
14 | + <BasicArrow v-if="$attrs.canExpan" :expand="$attrs.show" @click="handleExpand" /> | |
15 | + </div> | |
16 | + </div> | |
17 | +</template> | |
18 | +<script lang="ts"> | |
19 | + import { defineComponent } from 'vue'; | |
20 | + import { BasicArrow } from '/@/components/Basic'; | |
21 | + import { BasicTitle } from '/@/components/Basic'; | |
22 | + export default defineComponent({ | |
23 | + inheritAttrs: false, | |
24 | + components: { BasicArrow, BasicTitle }, | |
25 | + setup(_, { emit }) { | |
26 | + function handleExpand() { | |
27 | + emit('expand'); | |
28 | + } | |
29 | + return { handleExpand }; | |
30 | + }, | |
31 | + }); | |
32 | +</script> | ... | ... |
src/components/Container/src/types.d.ts
0 → 100644
1 | +++ a/src/components/Container/src/types.d.ts | |
1 | +export type ScrollType = 'default' | 'main'; | |
2 | + | |
3 | +export interface CollapseContainerOptions { | |
4 | + canExpand?: boolean; | |
5 | + title?: string; | |
6 | + helpMessage?: Array<any> | string; | |
7 | +} | |
8 | +export interface ScrollContainerOptions { | |
9 | + enableScroll?: boolean; | |
10 | + type?: ScrollType; | |
11 | +} | |
12 | + | |
13 | +export type ScrollActionType = RefType<{ | |
14 | + scrollBottom: () => void; | |
15 | + getScrollWrap: () => Nullable<HTMLElement>; | |
16 | + scrollTo: (top: number) => void; | |
17 | +}>; | ... | ... |
src/components/ContextMenu/index.ts
0 → 100644
1 | +++ a/src/components/ContextMenu/index.ts | |
1 | +import contextMenuVue from './src/index'; | |
2 | +import { isClient } from '/@/utils/is'; | |
3 | +import { Options, Props } from './src/types'; | |
4 | +import { createApp } from 'vue'; | |
5 | +const menuManager: { | |
6 | + doms: Element[]; | |
7 | + resolve: Fn; | |
8 | +} = { | |
9 | + doms: [], | |
10 | + resolve: () => {}, | |
11 | +}; | |
12 | +export const createContextMenu = function (options: Options) { | |
13 | + const { event } = options || {}; | |
14 | + try { | |
15 | + event.preventDefault(); | |
16 | + } catch (e) { | |
17 | + console.log(e); | |
18 | + } | |
19 | + | |
20 | + if (!isClient) return; | |
21 | + return new Promise((resolve) => { | |
22 | + const wrapDom = document.createElement('div'); | |
23 | + const propsData: Partial<Props> = {}; | |
24 | + if (options.styles !== undefined) propsData.styles = options.styles; | |
25 | + if (options.items !== undefined) propsData.items = options.items; | |
26 | + if (options.event !== undefined) { | |
27 | + propsData.customEvent = event; | |
28 | + propsData.axis = { x: event.clientX, y: event.clientY }; | |
29 | + } | |
30 | + createApp(contextMenuVue, propsData).mount(wrapDom); | |
31 | + const bodyClick = function () { | |
32 | + menuManager.resolve(''); | |
33 | + }; | |
34 | + const contextMenuDom = wrapDom.children[0]; | |
35 | + menuManager.doms.push(contextMenuDom); | |
36 | + const remove = function () { | |
37 | + menuManager.doms.forEach((dom: Element) => { | |
38 | + try { | |
39 | + document.body.removeChild(dom); | |
40 | + } catch (error) {} | |
41 | + }); | |
42 | + document.body.removeEventListener('click', bodyClick); | |
43 | + document.body.removeEventListener('scroll', bodyClick); | |
44 | + try { | |
45 | + (wrapDom as any) = null; | |
46 | + } catch (error) {} | |
47 | + }; | |
48 | + menuManager.resolve = function (...arg: any) { | |
49 | + resolve(arg[0]); | |
50 | + remove(); | |
51 | + }; | |
52 | + remove(); | |
53 | + document.body.appendChild(contextMenuDom); | |
54 | + document.body.addEventListener('click', bodyClick); | |
55 | + document.body.addEventListener('scroll', bodyClick); | |
56 | + }); | |
57 | +}; | |
58 | +export const unMountedContextMenu = function () { | |
59 | + if (menuManager) { | |
60 | + menuManager.resolve(''); | |
61 | + menuManager.doms = []; | |
62 | + } | |
63 | +}; | |
64 | + | |
65 | +export * from './src/types'; | ... | ... |
src/components/ContextMenu/src/index.less
0 → 100644
1 | +++ a/src/components/ContextMenu/src/index.less | |
1 | +@import (reference) '../../../design/index.less'; | |
2 | + | |
3 | +.context-menu { | |
4 | + position: fixed; | |
5 | + top: 0; | |
6 | + left: 0; | |
7 | + z-index: 1500; | |
8 | + display: block; | |
9 | + width: 156px; | |
10 | + min-width: 10rem; | |
11 | + margin: 0; | |
12 | + list-style: none; | |
13 | + background-color: #fff; | |
14 | + border: 1px solid rgba(0, 0, 0, 0.08); | |
15 | + border-radius: 0.25rem; | |
16 | + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1), | |
17 | + 0 1px 5px 0 rgba(0, 0, 0, 0.06); | |
18 | + background-clip: padding-box; | |
19 | + user-select: none; | |
20 | + | |
21 | + &.hidden { | |
22 | + display: none !important; | |
23 | + } | |
24 | + | |
25 | + &__item { | |
26 | + a { | |
27 | + display: inline-block; | |
28 | + width: 100%; | |
29 | + padding: 10px 14px; | |
30 | + | |
31 | + &:hover { | |
32 | + color: @text-color-base; | |
33 | + background: #eee; | |
34 | + } | |
35 | + } | |
36 | + | |
37 | + &.disabled { | |
38 | + a { | |
39 | + color: @disabled-color; | |
40 | + cursor: not-allowed; | |
41 | + | |
42 | + &:hover { | |
43 | + color: @disabled-color; | |
44 | + background: unset; | |
45 | + } | |
46 | + } | |
47 | + } | |
48 | + } | |
49 | +} | ... | ... |
src/components/ContextMenu/src/index.tsx
0 → 100644
1 | +++ a/src/components/ContextMenu/src/index.tsx | |
1 | +import { | |
2 | + defineComponent, | |
3 | + nextTick, | |
4 | + onMounted, | |
5 | + reactive, | |
6 | + computed, | |
7 | + ref, | |
8 | + unref, | |
9 | + onUnmounted, | |
10 | +} from 'vue'; | |
11 | +import { props } from './props'; | |
12 | +import Icon from '/@/components/Icon'; | |
13 | +import type { ContextMenuItem } from './types'; | |
14 | +import './index.less'; | |
15 | +const prefixCls = 'context-menu'; | |
16 | +export default defineComponent({ | |
17 | + name: 'ContextMenu', | |
18 | + props, | |
19 | + setup(props) { | |
20 | + const wrapRef = ref<Nullable<HTMLDivElement>>(null); | |
21 | + const state = reactive({ | |
22 | + show: false, | |
23 | + }); | |
24 | + onMounted(() => { | |
25 | + nextTick(() => { | |
26 | + state.show = true; | |
27 | + }); | |
28 | + }); | |
29 | + onUnmounted(() => { | |
30 | + const el = unref(wrapRef); | |
31 | + el && document.body.removeChild(el); | |
32 | + }); | |
33 | + const getStyle = computed(() => { | |
34 | + const { axis, items, styles, width } = props; | |
35 | + const { x, y } = axis || { x: 0, y: 0 }; | |
36 | + const menuHeight = (items || []).length * 40; | |
37 | + const menuWidth = width; | |
38 | + const body = document.body; | |
39 | + return { | |
40 | + ...(styles as any), | |
41 | + width: `${width}px`, | |
42 | + left: (body.clientWidth < x + menuWidth ? x - menuWidth : x) + 'px', | |
43 | + top: (body.clientHeight < y + menuHeight ? y - menuHeight : y) + 'px', | |
44 | + }; | |
45 | + }); | |
46 | + function handleAction(item: ContextMenuItem, e: MouseEvent) { | |
47 | + const { handler, disabled } = item; | |
48 | + if (disabled) { | |
49 | + return; | |
50 | + } | |
51 | + state.show = false; | |
52 | + if (e) { | |
53 | + e.stopPropagation(); | |
54 | + e.preventDefault(); | |
55 | + } | |
56 | + | |
57 | + handler && handler(); | |
58 | + } | |
59 | + function renderContent(item: ContextMenuItem) { | |
60 | + const { icon, label } = item; | |
61 | + | |
62 | + const { showIcon } = props; | |
63 | + return ( | |
64 | + <span style="display: inline-block; width: 100%;"> | |
65 | + {showIcon && icon && <Icon class="mr-2" icon={icon} />} | |
66 | + <span>{label}</span> | |
67 | + </span> | |
68 | + ); | |
69 | + } | |
70 | + function renderMenuItem(items: ContextMenuItem[]) { | |
71 | + return items.map((item) => { | |
72 | + const { disabled, label } = item; | |
73 | + | |
74 | + return ( | |
75 | + <li class={`${prefixCls}__item ${disabled ? 'disabled' : ''}`} key={label}> | |
76 | + <a onClick={handleAction.bind(null, item)}>{renderContent(item)}</a> | |
77 | + </li> | |
78 | + ); | |
79 | + }); | |
80 | + } | |
81 | + return () => { | |
82 | + const { items } = props; | |
83 | + return ( | |
84 | + <ul class={[prefixCls, !state.show && 'hidden']} ref={wrapRef} style={unref(getStyle)}> | |
85 | + {renderMenuItem(items)} | |
86 | + </ul> | |
87 | + ); | |
88 | + }; | |
89 | + }, | |
90 | +}); | ... | ... |
src/components/ContextMenu/src/props.ts
0 → 100644
1 | +++ a/src/components/ContextMenu/src/props.ts | |
1 | +import type { PropType } from 'vue'; | |
2 | +import type { Axis, ContextMenuItem } from './types'; | |
3 | +export const props = { | |
4 | + width: { | |
5 | + type: Number as PropType<number>, | |
6 | + default: 180, | |
7 | + }, | |
8 | + customEvent: { | |
9 | + type: Object as PropType<Event>, | |
10 | + default: null, | |
11 | + }, | |
12 | + // 样式 | |
13 | + styles: { | |
14 | + type: Object as PropType<any>, | |
15 | + default: null, | |
16 | + }, | |
17 | + showIcon: { | |
18 | + // 是否显示icon | |
19 | + type: Boolean as PropType<boolean>, | |
20 | + default: true, | |
21 | + }, | |
22 | + axis: { | |
23 | + // 鼠标右键点击的位置 | |
24 | + type: Object as PropType<Axis>, | |
25 | + default() { | |
26 | + return { x: 0, y: 0 }; | |
27 | + }, | |
28 | + }, | |
29 | + items: { | |
30 | + // 最重要的列表,没有的话直接不显示 | |
31 | + type: Array as PropType<ContextMenuItem[]>, | |
32 | + default() { | |
33 | + return []; | |
34 | + }, | |
35 | + }, | |
36 | + resolve: { | |
37 | + type: Function as PropType<any>, | |
38 | + default: null, | |
39 | + }, | |
40 | +}; | ... | ... |
src/components/ContextMenu/src/types.ts
0 → 100644
1 | +++ a/src/components/ContextMenu/src/types.ts | |
1 | +export interface Axis { | |
2 | + x: number; | |
3 | + y: number; | |
4 | +} | |
5 | + | |
6 | +export interface ContextMenuItem { | |
7 | + label: string; | |
8 | + icon?: string; | |
9 | + disabled?: boolean; | |
10 | + handler?: Fn; | |
11 | + divider?: boolean; | |
12 | + children?: ContextMenuItem[]; | |
13 | +} | |
14 | +export interface Options { | |
15 | + event: MouseEvent; | |
16 | + icon?: string; | |
17 | + styles?: any; | |
18 | + items?: ContextMenuItem[]; | |
19 | +} | |
20 | + | |
21 | +export type Props = { | |
22 | + resolve?: (...arg: any) => void; | |
23 | + event?: MouseEvent; | |
24 | + styles?: any; | |
25 | + items: ContextMenuItem[]; | |
26 | + customEvent?: MouseEvent; | |
27 | + axis?: Axis; | |
28 | + width?: number; | |
29 | + showIcon?: boolean; | |
30 | +}; | ... | ... |