Commit 2f6253cfb601c0a429ade1a272f5812a55d363af

Authored by 陈文彬
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
  1 +++ a/.editorconfig
  1 +root = true
  2 +
  3 +[*]
  4 +charset=utf-8
  5 +end_of_line=lf
  6 +insert_final_newline=false
  7 +indent_style=space
  8 +indent_size=2
  9 +
  10 +[*.yml]
  11 +indent_style = space
  12 +indent_size = 2
  13 +
  14 +[*.md]
  15 +trim_trailing_whitespace = false
.env 0 → 100644
  1 +++ a/.env
.env.development 0 → 100644
  1 +++ a/.env.development
  1 +VITE_USE_MOCK=true
.env.production 0 → 100644
  1 +++ a/.env.production
  1 +VITE_USE_MOCK=true
.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
  1 +++ a/.gitignore
  1 +node_modules
  2 +.DS_Store
  3 +dist
  4 +*.local
  5 +.npmrc
  6 +.cache
.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
  1 +++ a/.prettierignore
  1 +/dist/*
  2 +.local
  3 +.output.js
  4 +/node_modules/**
  5 +
  6 +**/*.svg
  7 +**/*.sh
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
  1 +++ a/build/script/postinstall.ts
  1 +import { exec, which } from 'shelljs';
  2 +
  3 +function ignoreCaseGit() {
  4 + try {
  5 + if (which('git')) {
  6 + exec('git config core.ignorecase false ');
  7 + }
  8 + } catch (error) {}
  9 +}
  10 +ignoreCaseGit();
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
  1 +++ a/postcss.config.js
  1 +const path = require('path');
  2 +module.exports = {
  3 + plugins: [require('tailwindcss'), require('autoprefixer'), require('postcss-import')],
  4 +};
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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 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
  1 +++ a/src/components/Basic/index.ts
  1 +export { default as BasicArrow } from './src/BasicArrow.vue';
  2 +export { default as BasicHelp } from './src/BasicHelp';
  3 +export { default as BasicTitle } from './src/BasicTitle.vue';
  4 +export { default as BasicEmpty } from './src/BasicEmpty.vue';
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 +};