├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── cli-helper.js ├── index.html ├── jsconfig.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public └── logo.svg ├── src ├── App.vue ├── assets │ └── styles │ │ ├── index.scss │ │ └── tailwind.css ├── components │ ├── Calendar │ │ ├── DateBody.vue │ │ ├── Tooltip.vue │ │ ├── heatmap.js │ │ └── index.vue │ ├── CustomTitle │ │ └── index.vue │ ├── Menu │ │ └── index.vue │ ├── PopperContainer │ │ ├── clickoutside.js │ │ ├── dropdown.vue │ │ └── index.vue │ ├── RightToolbar │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── Timeline │ │ └── index.vue │ ├── Waterfall │ │ ├── WaterfallItem.vue │ │ └── index.vue │ ├── element-ui │ │ ├── PopperJS.js │ │ ├── el-table-column │ │ │ └── index.vue │ │ ├── el-table │ │ │ ├── affix.vue │ │ │ ├── affix2.vue │ │ │ ├── config.jsx │ │ │ ├── directive │ │ │ │ ├── index.js │ │ │ │ └── ripple.js │ │ │ ├── dropdown.js │ │ │ ├── filter-panel.vue │ │ │ ├── index.vue │ │ │ ├── layout-observer.js │ │ │ ├── store │ │ │ │ ├── current.js │ │ │ │ ├── expand.js │ │ │ │ ├── helper.js │ │ │ │ ├── index.js │ │ │ │ ├── tree.js │ │ │ │ └── watcher.js │ │ │ ├── table-body.jsx │ │ │ ├── table-footer.jsx │ │ │ ├── table-header.jsx │ │ │ ├── table-layout.js │ │ │ ├── table-row.jsx │ │ │ ├── table.vue │ │ │ └── util.js │ │ ├── el-volume │ │ │ ├── el-volume-item.vue │ │ │ └── index.vue │ │ ├── index.js │ │ └── popper.js │ └── scrollPanel │ │ └── index.vue ├── directives │ └── attr.js ├── layout │ ├── components │ │ ├── asider.vue │ │ ├── header.vue │ │ └── main.vue │ └── index.vue ├── main.js ├── mixins │ └── table-header-drag.js ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ └── app.js ├── utils │ └── index.js └── views │ ├── calendar │ └── index.vue │ ├── camera │ └── index.vue │ ├── chart │ └── index.vue │ ├── client │ └── index.vue │ ├── color │ ├── ColorBlock.vue │ ├── ColorControl.vue │ ├── ColorPanel.vue │ ├── ColorRow.vue │ ├── ColorTooltip.vue │ └── index.vue │ ├── dashboard │ └── index.vue │ ├── operate │ └── index.vue │ ├── other │ ├── data.json │ └── index.vue │ ├── test │ └── index.vue │ ├── test2 │ └── index.vue │ ├── user │ └── index.vue │ └── waterfall │ └── index.vue ├── tailwind.config.js ├── vite-plugin-build-legacy.js ├── vite.config.js └── vitejs + vue2测试目录 /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV = 'development' -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV = 'production' -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | package.json 2 | public 3 | **/assets/svg/*.svg 4 | **/assets/**/*.json -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { defineConfig } = require('eslint-define-config') 3 | module.exports = defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | ecmaVersion: 2020, 13 | sourceType: 'module', 14 | jsxPragma: 'React', 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | extends: [ 20 | 'eslint:recommended', // eslint 推荐配置 21 | 'prettier', // 配置 prettier 插件 与 eslint-config-prettier 插件冲突 22 | 'plugin:prettier/recommended', // 配置 prettier 插件 23 | ], 24 | rules: { 25 | 'vue/max-attributes-per-line': 'off', // 关闭每行最大属性限制 26 | 'vue/singleline-html-element-content-newline': 'off', // 关闭单行元素内容之前和之后强制换行 27 | 'vue/multiline-html-element-content-newline': 'off', // 关闭多行元素内容之前和之后强制换行 28 | 'vue/require-prop-type-constructor': 'off', // 关闭要求prop类型构造函数 29 | 'vue/multi-word-component-names': 'off', // 关闭多个单词组成的组件名必须使用短横线连接 30 | 'vue/no-v-html': 'off', 31 | 'vue/no-unused-vars': 'off', 32 | 'vue/no-mutating-props': 'off', // 关闭禁止修改props 33 | 'vue/no-unused-components': 'off', // 关闭禁止出现未使用的组件 34 | 'vue/no-reserved-component-names': 'off', // 关闭禁止使用保留字命名组件 35 | 'vue/no-side-effects-in-computed-properties': 'off', // 关闭禁止在计算属性中对属性修改 36 | 'accessor-pairs': 2, 37 | 'no-trailing-spaces': 'off', 38 | 'arrow-spacing': [ 39 | 2, 40 | { 41 | before: true, 42 | after: true, 43 | }, 44 | ], 45 | 'block-spacing': [2, 'always'], 46 | 'brace-style': [ 47 | 2, 48 | '1tbs', 49 | { 50 | allowSingleLine: true, 51 | }, 52 | ], 53 | camelcase: [ 54 | 0, 55 | { 56 | properties: 'always', 57 | }, 58 | ], 59 | 'comma-dangle': [0, 'always'], // 对象字面量项尾不能有逗号 默认 never 允许拖尾逗号 always必须有拖尾逗号 60 | 'comma-spacing': [ 61 | 2, 62 | { 63 | before: false, 64 | after: true, 65 | }, 66 | ], 67 | 'comma-style': [2, 'last'], 68 | 'constructor-super': 2, 69 | curly: [0, 'multi-line'], // "all" | "multi" | "multi-line" | "multi-or-nest" | undefined” // 强制所有控制语句使用一致的括号风格 70 | 'dot-location': [2, 'property'], 71 | 'eol-last': 2, 72 | eqeqeq: 'off', // 关闭必须使用全等 73 | 'generator-star-spacing': [ 74 | 2, 75 | { 76 | before: true, 77 | after: true, 78 | }, 79 | ], 80 | 'handle-callback-err': [2, '^(err|error)$'], 81 | indent: [ 82 | 0, 83 | 2, 84 | { 85 | SwitchCase: 1, 86 | }, 87 | ], 88 | 'jsx-quotes': [0, 'prefer-single'], 89 | 'key-spacing': [ 90 | 2, 91 | { 92 | beforeColon: false, 93 | afterColon: true, 94 | }, 95 | ], 96 | 'keyword-spacing': [ 97 | 2, 98 | { 99 | before: true, 100 | after: true, 101 | }, 102 | ], 103 | 'new-cap': [ 104 | 'off', 105 | { 106 | newIsCap: true, 107 | capIsNew: false, 108 | }, 109 | ], 110 | 'new-parens': 2, 111 | 'no-array-constructor': 0, 112 | 'no-caller': 2, 113 | 'no-console': 'off', 114 | 'no-class-assign': 2, 115 | 'no-cond-assign': 2, 116 | 'no-const-assign': 2, 117 | 'no-control-regex': 0, 118 | 'no-delete-var': 2, 119 | 'no-dupe-args': 2, 120 | 'no-dupe-class-members': 2, 121 | 'no-dupe-keys': 2, 122 | 'no-duplicate-case': 2, 123 | 'no-empty-character-class': 2, 124 | 'no-empty-pattern': 'off', // 关闭禁止使用空解构模式 125 | 'no-eval': 2, 126 | 'no-ex-assign': 2, 127 | 'no-extend-native': 0, 128 | 'no-extra-bind': 2, 129 | 'no-extra-boolean-cast': 0, 130 | 'no-extra-parens': [2, 'functions'], 131 | 'no-fallthrough': 2, 132 | 'no-floating-decimal': 2, 133 | 'no-func-assign': 2, 134 | 'no-implied-eval': 0, 135 | 'no-inner-declarations': [2, 'functions'], 136 | 'no-invalid-regexp': 2, 137 | 'no-irregular-whitespace': 2, 138 | 'no-iterator': 2, 139 | 'no-label-var': 2, 140 | 'no-labels': [ 141 | 0, 142 | { 143 | allowLoop: false, 144 | allowSwitch: false, 145 | }, 146 | ], 147 | 'no-lone-blocks': 2, 148 | 'no-mixed-spaces-and-tabs': 2, 149 | 'no-multi-spaces': 2, 150 | 'no-multi-str': 2, 151 | 'no-multiple-empty-lines': [ 152 | 2, 153 | { 154 | max: 1, 155 | }, 156 | ], 157 | 'no-native-reassign': 2, 158 | 'no-negated-in-lhs': 2, 159 | 'no-new-object': 2, 160 | 'no-new-require': 2, 161 | 'no-new-symbol': 2, 162 | 'no-new-wrappers': 2, 163 | 'no-obj-calls': 2, 164 | 'no-octal': 2, 165 | 'no-octal-escape': 2, 166 | 'no-path-concat': 2, 167 | 'no-proto': 2, 168 | 'no-redeclare': 2, 169 | 'no-regex-spaces': 2, 170 | 'no-return-assign': [2, 'except-parens'], 171 | 'no-self-assign': 2, 172 | 'no-self-compare': 2, 173 | 'no-sequences': 0, 174 | 'no-shadow-restricted-names': 2, 175 | 'no-spaced-func': 2, 176 | 'no-sparse-arrays': 2, 177 | 'no-this-before-super': 2, 178 | 'no-throw-literal': 2, 179 | 'no-undef': 2, 180 | 'no-undef-init': 2, 181 | 'no-unexpected-multiline': 2, 182 | 'no-unmodified-loop-condition': 2, 183 | 'no-unneeded-ternary': [ 184 | 2, 185 | { 186 | defaultAssignment: false, 187 | }, 188 | ], 189 | 'no-unreachable': 2, 190 | 'no-unsafe-finally': 2, 191 | 'no-unused-vars': [ 192 | 0, 193 | { 194 | vars: 'all', 195 | args: 'none', 196 | }, 197 | ], 198 | 'no-useless-call': 0, 199 | 'no-useless-computed-key': 2, 200 | 'no-useless-constructor': 2, 201 | 'no-useless-escape': 0, 202 | 'no-whitespace-before-property': 2, 203 | 'no-with': 2, 204 | 'one-var': [ 205 | 2, 206 | { 207 | initialized: 'never', 208 | }, 209 | ], 210 | 'operator-linebreak': [ 211 | 2, 212 | 'after', 213 | { 214 | overrides: { 215 | '?': 'before', 216 | ':': 'before', 217 | }, 218 | }, 219 | ], 220 | 'padded-blocks': [2, 'never'], 221 | quotes: [ 222 | 2, 223 | 'single', 224 | { 225 | avoidEscape: true, 226 | allowTemplateLiterals: true, 227 | }, 228 | ], 229 | semi: 'off', 230 | 'prettier/prettier': ['error', { semi: false }], 231 | 'semi-spacing': [ 232 | 2, 233 | { 234 | before: false, 235 | after: true, 236 | }, 237 | ], 238 | 'space-before-blocks': [2, 'always'], 239 | 'space-before-function-paren': [0, 'never'], 240 | 'space-in-parens': [2, 'never'], 241 | 'space-infix-ops': 2, 242 | 'space-unary-ops': [ 243 | 2, 244 | { 245 | words: true, 246 | nonwords: false, 247 | }, 248 | ], 249 | 'spaced-comment': [ 250 | 2, 251 | 'always', 252 | { 253 | markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','], 254 | }, 255 | ], 256 | 'template-curly-spacing': [2, 'never'], 257 | 'use-isnan': 2, 258 | 'valid-typeof': 2, 259 | 'wrap-iife': [2, 'any'], 260 | 'yield-star-spacing': [2, 'both'], 261 | yoda: [2, 'never'], 262 | 'prefer-const': 2, 263 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 264 | 'object-curly-spacing': [ 265 | 2, 266 | 'always', 267 | { 268 | objectsInObjects: true, // 对象套对象是否必须空格 269 | }, 270 | ], 271 | 'array-bracket-spacing': [2, 'never'], 272 | }, 273 | }) 274 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .history 3 | .ignore 4 | 5 | ./dist* 6 | 7 | package-lock.json 8 | yarn.lock 9 | pnpm-lock.yaml 10 | 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | public 4 | dist 5 | .history 6 | .jsconfig 7 | .tsconfig -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // .prettierrc.js 2 | module.exports = { 3 | printWidth: 350, // 每行代码长度(默认80) 4 | overrides: [ 5 | { 6 | files: 'src/components/Menu/index.vue', 7 | options: { 8 | printWidth: 80, 9 | }, 10 | }, 11 | ], 12 | tabWidth: 2, // 每个tab相当于多少个空格(默认2)ab进行缩进(默认false) 13 | useTabs: false, // 是否使用tab 14 | semi: false, // 声明结尾使用分号(默认true) 15 | vueIndentScriptAndStyle: false, 16 | singleQuote: true, // 使用单引号(默认false) 17 | quoteProps: 'as-needed', // 对象的key仅在必要时用引号(默认as-needed) 18 | bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) 19 | trailingComma: 'all', // 多行使用拖尾逗号(默认none) 20 | jsxSingleQuote: false, // 在jsx中使用单引号(默认false) 21 | arrowParens: 'avoid', // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 22 | insertPragma: false, // 是否插入Pragma标记 23 | requirePragma: false, // 是否要求Pragma标记 24 | proseWrap: 'never', // 是否换行 25 | endOfLine: 'auto', 26 | rangeStart: 0, // 每个文件格式化的范围是文件的全部内容 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "regex-previewer.enableCodeLens": false 3 | } -------------------------------------------------------------------------------- /cli-helper.js: -------------------------------------------------------------------------------- 1 | import readline from 'readline' 2 | 3 | function clearScreen() { 4 | const repeatCount = process.stdout.rows - 2 5 | const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : '' 6 | console.log(blank) 7 | readline.cursorTo(process.stdout, 0, 0) 8 | readline.clearScreenDown(process.stdout) 9 | } 10 | 11 | function formatter(open, close, replace = open) { 12 | return input => { 13 | const string = '' + input 14 | const index = string.indexOf(close, open.length) 15 | return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close 16 | } 17 | } 18 | 19 | function replaceClose(string, close, replace, index) { 20 | const start = string.substring(0, index) + replace 21 | const end = string.substring(index + close.length) 22 | const nextIndex = end.indexOf(close) 23 | return ~nextIndex ? start + replaceClose(end, close, replace, nextIndex) : start + end 24 | } 25 | 26 | function createColors() { 27 | return { 28 | reset: s => `\x1b[0m${s}\x1b[0m`, 29 | bold: formatter('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'), 30 | dim: formatter('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'), 31 | italic: formatter('\x1b[3m', '\x1b[23m'), 32 | underline: formatter('\x1b[4m', '\x1b[24m'), 33 | inverse: formatter('\x1b[7m', '\x1b[27m'), 34 | hidden: formatter('\x1b[8m', '\x1b[28m'), 35 | strikethrough: formatter('\x1b[9m', '\x1b[29m'), 36 | black: formatter('\x1b[30m', '\x1b[39m'), 37 | red: formatter('\x1b[31m', '\x1b[39m'), 38 | green: formatter('\x1b[32m', '\x1b[39m'), 39 | yellow: formatter('\x1b[33m', '\x1b[39m'), 40 | blue: formatter('\x1b[34m', '\x1b[39m'), 41 | magenta: formatter('\x1b[35m', '\x1b[39m'), 42 | cyan: formatter('\x1b[36m', '\x1b[39m'), 43 | white: formatter('\x1b[37m', '\x1b[39m'), 44 | gray: formatter('\x1b[90m', '\x1b[39m'), 45 | bgBlack: formatter('\x1b[40m', '\x1b[49m'), 46 | bgRed: formatter('\x1b[41m', '\x1b[49m', '\x1b[22m\x1b[1m'), 47 | bgGreen: formatter('\x1b[42m', '\x1b[49m'), 48 | bgYellow: formatter('\x1b[43m', '\x1b[49m'), 49 | bgBlue: formatter('\x1b[44m', '\x1b[49m'), 50 | bgMagenta: formatter('\x1b[45m', '\x1b[49m'), 51 | bgCyan: formatter('\x1b[46m', '\x1b[49m'), 52 | bgWhite: formatter('\x1b[47m', '\x1b[49m'), 53 | } 54 | } 55 | 56 | export { clearScreen, createColors } 57 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= title %> 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "esnext", 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "skipLibCheck": true 23 | }, 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-exapmle", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "vite", 8 | "build": "vite build", 9 | "lint": "eslint --ext .js,.vue src", 10 | "prettier": "prettier -w \"src/**/*.{vue,js,jsx,ts,tsx,css,scss,less}\" -w \"./*.{js,json,html}\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@fullcalendar/bootstrap5": "^6.1.15", 17 | "@fullcalendar/core": "^6.1.15", 18 | "@fullcalendar/daygrid": "^6.1.15", 19 | "@fullcalendar/interaction": "^6.1.15", 20 | "@fullcalendar/list": "^6.1.15", 21 | "@fullcalendar/multimonth": "^6.1.15", 22 | "@fullcalendar/resource": "^6.1.15", 23 | "@fullcalendar/resource-timeline": "^6.1.15", 24 | "@fullcalendar/timegrid": "^6.1.15", 25 | "@fullcalendar/vue": "^6.1.15", 26 | "@vue/shared": "^3.4.38", 27 | "animate.css": "^4.1.1", 28 | "axios": "^1.5.0", 29 | "bootstrap": "^5.3.3", 30 | "bootstrap-icons": "^1.11.3", 31 | "dayjs": "^1.11.13", 32 | "echarts": "^5.5.1", 33 | "echarts-wordcloud": "^2.1.0", 34 | "element-ui": "^2.15.14", 35 | "heatmap.js": "^2.0.5", 36 | "js-cookie": "^3.0.5", 37 | "lodash-es": "^4.17.21", 38 | "masonry-layout": "^4.2.2", 39 | "sortablejs": "^1.15.2", 40 | "tinycolor2": "^1.6.0", 41 | "vue": "^2.7.16", 42 | "vue-router": "^3.6.5", 43 | "vuex": "^4.1.0" 44 | }, 45 | "devDependencies": { 46 | "@vitejs/plugin-vue2": "^2.3.1", 47 | "@vitejs/plugin-vue2-jsx": "^1.1.1", 48 | "autoprefixer": "^10.4.19", 49 | "eslint": "^8.57.0", 50 | "eslint-config-prettier": "^9.1.0", 51 | "eslint-define-config": "^2.1.0", 52 | "eslint-plugin-cypress": "^2.15.1", 53 | "eslint-plugin-prettier": "^5.1.3", 54 | "eslint-plugin-vue": "^9.24.0", 55 | "mockjs": "^1.1.0", 56 | "postcss": "^8.4.31", 57 | "prettier": "^3.2.5", 58 | "sass": "^1.72.0", 59 | "tailwindcss": "^3.3.3", 60 | "vite": "^4.5.3", 61 | "vite-plugin-ejs": "^1.7.0", 62 | "vite-plugin-eslint": "^1.8.1", 63 | "vite-plugin-html-template": "^1.2.0", 64 | "vite-plugin-mock": "^3.0.2", 65 | "vite-plugin-svg-icons": "^2.0.1", 66 | "vue-template-compiler": "^2.7.16" 67 | }, 68 | "engines": { 69 | "node": "14.x" 70 | } 71 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // .prettierrc.js 2 | module.exports = { 3 | printWidth: 80, // 每行代码长度(默认80) 4 | tabWidth: 2, // 每个tab相当于多少个空格(默认2)ab进行缩进(默认false) 5 | useTabs: false, // 是否使用tab 6 | semi: true, // 声明结尾使用分号(默认true) 7 | vueIndentScriptAndStyle: false, 8 | singleQuote: true, // 使用单引号(默认false) 9 | quoteProps: 'as-needed', // 对象的key仅在必要时用引号(默认as-needed) 10 | bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) 11 | trailingComma: 'all', // 多行使用拖尾逗号(默认none) 12 | jsxSingleQuote: false, // 在jsx中使用单引号(默认false) 13 | arrowParens: 'avoid', // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 14 | insertPragma: false, // 是否插入Pragma标记 15 | requirePragma: false, // 是否要求Pragma标记 16 | proseWrap: 'never', // 是否换行 17 | endOfLine: 'auto', 18 | rangeStart: 0, // 每个文件格式化的范围是文件的全部内容 19 | jsxBracketSameLine: true, // 在jsx中把'>' 是否单独放一行 20 | } 21 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | width: 100%; 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/components/Calendar/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 42 | 43 | 72 | -------------------------------------------------------------------------------- /src/components/Calendar/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /src/components/CustomTitle/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 50 | -------------------------------------------------------------------------------- /src/components/Menu/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | 35 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/components/PopperContainer/clickoutside.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { on } from 'element-ui/src/utils/dom' 3 | 4 | const nodeList = [] 5 | const ctx = '@@clickoutsideContext' 6 | 7 | let startClick 8 | let seed = 0 9 | 10 | !Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e)) 11 | 12 | !Vue.prototype.$isServer && 13 | on(document, 'mouseup', e => { 14 | nodeList.forEach(node => node[ctx].documentHandler(e, startClick)) 15 | }) 16 | 17 | function createDocumentHandler(el, binding, vnode) { 18 | return function (mouseup = {}, mousedown = {}) { 19 | if (!vnode || !vnode.context || !mouseup.target || !mousedown.target || el.contains(mouseup.target) || el.contains(mousedown.target) || el === mouseup.target || (vnode.context.popperElm && (vnode.context.popperElm.contains(mouseup.target) || vnode.context.popperElm.contains(mousedown.target)))) return 20 | 21 | if (binding.expression && el[ctx].methodName && vnode.context[el[ctx].methodName]) { 22 | vnode.context[el[ctx].methodName]({ el, mousedown, mouseup }) 23 | } else { 24 | el[ctx].bindingFn && el[ctx].bindingFn({ el, mousedown, mouseup }) 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * v-clickoutside 31 | * @desc 点击元素外面才会触发的事件 32 | * @example 33 | * ```vue 34 | *
35 | * ``` 36 | */ 37 | export default { 38 | bind(el, binding, vnode) { 39 | nodeList.push(el) 40 | const id = seed++ 41 | el[ctx] = { 42 | id, 43 | documentHandler: createDocumentHandler(el, binding, vnode), 44 | methodName: binding.expression, 45 | bindingFn: binding.value, 46 | } 47 | }, 48 | 49 | update(el, binding, vnode) { 50 | el[ctx].documentHandler = createDocumentHandler(el, binding, vnode) 51 | el[ctx].methodName = binding.expression 52 | el[ctx].bindingFn = binding.value 53 | }, 54 | 55 | unbind(el) { 56 | const len = nodeList.length 57 | 58 | for (let i = 0; i < len; i++) { 59 | if (nodeList[i][ctx].id === el[ctx].id) { 60 | nodeList.splice(i, 1) 61 | break 62 | } 63 | } 64 | delete el[ctx] 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /src/components/PopperContainer/dropdown.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 62 | 63 | 79 | -------------------------------------------------------------------------------- /src/components/PopperContainer/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 90 | 91 | 100 | -------------------------------------------------------------------------------- /src/components/RightToolbar/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 151 | 152 | 157 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 34 | 35 | 52 | -------------------------------------------------------------------------------- /src/components/Timeline/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 31 | 32 | 42 | 43 | 201 | -------------------------------------------------------------------------------- /src/components/Waterfall/WaterfallItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 81 | 82 | 90 | -------------------------------------------------------------------------------- /src/components/Waterfall/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 74 | 75 | 80 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table-column/index.vue: -------------------------------------------------------------------------------- 1 | 303 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/affix.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 274 | 275 | 281 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/affix2.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 181 | 182 | 187 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/config.jsx: -------------------------------------------------------------------------------- 1 | import { getPropByPath } from 'element-ui/src/utils/util' 2 | 3 | export const cellStarts = { 4 | default: { 5 | order: '', 6 | }, 7 | selection: { 8 | width: 48, 9 | minWidth: 48, 10 | realWidth: 48, 11 | order: '', 12 | className: 'el-table-column--selection', 13 | }, 14 | expand: { 15 | width: 48, 16 | minWidth: 48, 17 | realWidth: 48, 18 | order: '', 19 | }, 20 | index: { 21 | width: 48, 22 | minWidth: 48, 23 | realWidth: 48, 24 | order: '', 25 | }, 26 | } 27 | 28 | // 这些选项不应该被覆盖 29 | export const cellForced = { 30 | selection: { 31 | renderHeader: function (h, { store }) { 32 | return 0 && !this.isAllSelected} on-input={this.toggleAllSelection} value={this.isAllSelected} /> 33 | }, 34 | renderCell: function (h, { row, column, isSelected, store, $index }) { 35 | return ( 36 | event.stopPropagation()} 38 | value={isSelected} 39 | disabled={column.selectable ? !column.selectable.call(null, row, $index) : false} 40 | on-input={() => { 41 | store.commit('rowSelectedChanged', row) 42 | }} 43 | /> 44 | ) 45 | }, 46 | sortable: false, 47 | resizable: false, 48 | }, 49 | index: { 50 | renderHeader: function (h, { column }) { 51 | return column.label || '#' 52 | }, 53 | renderCell: function (h, { $index, column }) { 54 | let i = $index + 1 55 | const index = column.index 56 | 57 | if (typeof index === 'number') { 58 | i = $index + index 59 | } else if (typeof index === 'function') { 60 | i = index($index) 61 | } 62 | 63 | return
{i}
64 | }, 65 | sortable: false, 66 | }, 67 | expand: { 68 | renderHeader: function (h, { column }) { 69 | return column.label || '' 70 | }, 71 | renderCell: function (h, { row, store, isExpanded }) { 72 | const classes = ['el-table__expand-icon'] 73 | if (isExpanded) { 74 | classes.push('el-table__expand-icon--expanded') 75 | } 76 | const callback = function (e) { 77 | e.stopPropagation() 78 | store.toggleRowExpansion(row) 79 | } 80 | return ( 81 |
82 | 83 |
84 | ) 85 | }, 86 | sortable: false, 87 | resizable: false, 88 | className: 'el-table__expand-column', 89 | }, 90 | } 91 | 92 | export function defaultRenderCell(h, { row, column, $index }) { 93 | const property = column.property 94 | const value = property && getPropByPath(row, property).v 95 | if (column && column.formatter) { 96 | return column.formatter(row, column, value, $index) 97 | } 98 | return value 99 | } 100 | 101 | export function treeCellPrefix(h, { row, treeNode, store }) { 102 | if (!treeNode) return null 103 | const ele = [] 104 | const callback = function (e) { 105 | e.stopPropagation() 106 | store.loadOrToggle(row) 107 | } 108 | if (treeNode.indent) { 109 | ele.push() 110 | } 111 | if (typeof treeNode.expanded === 'boolean' && !treeNode.noLazyChildren) { 112 | const expandClasses = ['el-table__expand-icon', treeNode.expanded ? 'el-table__expand-icon--expanded' : ''] 113 | let iconClasses = ['el-icon-arrow-right'] 114 | if (treeNode.loading) { 115 | iconClasses = ['el-icon-loading'] 116 | } 117 | ele.push( 118 |
119 | 120 |
, 121 | ) 122 | } else { 123 | ele.push() 124 | } 125 | return ele 126 | } 127 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/directive/index.js: -------------------------------------------------------------------------------- 1 | export { default as Ripple } from './ripple' 2 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/dropdown.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | var dropdowns = [] 3 | 4 | !Vue.prototype.$isServer && 5 | document.addEventListener('click', function (event) { 6 | dropdowns.forEach(function (dropdown) { 7 | var target = event.target 8 | if (!dropdown || !dropdown.$el) return 9 | if (target === dropdown.$el || dropdown.$el.contains(target)) { 10 | return 11 | } 12 | dropdown.handleOutsideClick && dropdown.handleOutsideClick(event) 13 | }) 14 | }) 15 | 16 | export default { 17 | open(instance) { 18 | if (instance) { 19 | dropdowns.push(instance) 20 | } 21 | }, 22 | 23 | close(instance) { 24 | var index = dropdowns.indexOf(instance) 25 | if (index !== -1) { 26 | dropdowns.splice(instance, 1) 27 | } 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/filter-panel.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 178 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/layout-observer.js: -------------------------------------------------------------------------------- 1 | export default { 2 | created() { 3 | this.tableLayout.addObserver(this) 4 | }, 5 | 6 | destroyed() { 7 | this.tableLayout.removeObserver(this) 8 | }, 9 | 10 | computed: { 11 | tableLayout() { 12 | let layout = this.layout 13 | if (!layout && this.table) { 14 | layout = this.table.layout 15 | } 16 | if (!layout) { 17 | throw new Error('Can not find table layout.') 18 | } 19 | return layout 20 | }, 21 | }, 22 | 23 | mounted() { 24 | this.onColumnsChange(this.tableLayout) 25 | this.onScrollableChange(this.tableLayout) 26 | }, 27 | 28 | updated() { 29 | if (this.__updated__) return 30 | this.onColumnsChange(this.tableLayout) 31 | this.onScrollableChange(this.tableLayout) 32 | this.__updated__ = true 33 | }, 34 | 35 | methods: { 36 | onColumnsChange(layout) { 37 | const cols = this.$el.querySelectorAll('colgroup > col') 38 | if (!cols.length) return 39 | const flattenColumns = layout.getFlattenColumns() 40 | const columnsMap = {} 41 | flattenColumns.forEach(column => { 42 | columnsMap[column.id] = column 43 | }) 44 | for (let i = 0, j = cols.length; i < j; i++) { 45 | const col = cols[i] 46 | const name = col.getAttribute('name') 47 | const column = columnsMap[name] 48 | if (column) { 49 | col.setAttribute('width', column.realWidth || column.width) 50 | } 51 | } 52 | }, 53 | 54 | onScrollableChange(layout) { 55 | const cols = this.$el.querySelectorAll('colgroup > col[name=gutter]') 56 | for (let i = 0, j = cols.length; i < j; i++) { 57 | const col = cols[i] 58 | col.setAttribute('width', layout.scrollY ? layout.gutterWidth : '0') 59 | } 60 | const ths = this.$el.querySelectorAll('th.gutter') 61 | for (let i = 0, j = ths.length; i < j; i++) { 62 | const th = ths[i] 63 | th.style.width = layout.scrollY ? layout.gutterWidth + 'px' : '0' 64 | th.style.display = layout.scrollY ? '' : 'none' 65 | } 66 | }, 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/store/current.js: -------------------------------------------------------------------------------- 1 | import { arrayFind } from 'element-ui/src/utils/util' 2 | import { getRowIdentity } from '../util' 3 | 4 | export default { 5 | data() { 6 | return { 7 | states: { 8 | // 不可响应的,设置 currentRowKey 时,data 不一定存在,也许无法算出正确的 currentRow 9 | // 把该值缓存一下,当用户点击修改 currentRow 时,把该值重置为 null 10 | _currentRowKey: null, 11 | currentRow: null, 12 | }, 13 | } 14 | }, 15 | 16 | methods: { 17 | setCurrentRowKey(key) { 18 | this.assertRowKey() 19 | this.states._currentRowKey = key 20 | this.setCurrentRowByKey(key) 21 | }, 22 | 23 | restoreCurrentRowKey() { 24 | this.states._currentRowKey = null 25 | }, 26 | 27 | setCurrentRowByKey(key) { 28 | const { states } = this 29 | const { data = [], rowKey } = states 30 | let currentRow = null 31 | if (rowKey) { 32 | currentRow = arrayFind(data, item => getRowIdentity(item, rowKey) === key) 33 | } 34 | states.currentRow = currentRow 35 | }, 36 | 37 | updateCurrentRow(currentRow) { 38 | const { states, table } = this 39 | const oldCurrentRow = states.currentRow 40 | if (currentRow && currentRow !== oldCurrentRow) { 41 | states.currentRow = currentRow 42 | table.$emit('current-change', currentRow, oldCurrentRow) 43 | return 44 | } 45 | if (!currentRow && oldCurrentRow) { 46 | states.currentRow = null 47 | table.$emit('current-change', null, oldCurrentRow) 48 | } 49 | }, 50 | 51 | updateCurrentRowData() { 52 | const { states, table } = this 53 | const { rowKey, _currentRowKey } = states 54 | // data 为 null 时,解构时的默认值会被忽略 55 | const data = states.data || [] 56 | const oldCurrentRow = states.currentRow 57 | 58 | // 当 currentRow 不在 data 中时尝试更新数据 59 | if (data.indexOf(oldCurrentRow) === -1 && oldCurrentRow) { 60 | if (rowKey) { 61 | const currentRowKey = getRowIdentity(oldCurrentRow, rowKey) 62 | this.setCurrentRowByKey(currentRowKey) 63 | } else { 64 | states.currentRow = null 65 | } 66 | if (states.currentRow === null) { 67 | table.$emit('current-change', null, oldCurrentRow) 68 | } 69 | } else if (_currentRowKey) { 70 | // 把初始时下设置的 rowKey 转化成 rowData 71 | this.setCurrentRowByKey(_currentRowKey) 72 | this.restoreCurrentRowKey() 73 | } 74 | }, 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/store/expand.js: -------------------------------------------------------------------------------- 1 | import { toggleRowStatus, getKeysMap, getRowIdentity } from '../util' 2 | 3 | export default { 4 | data() { 5 | return { 6 | states: { 7 | defaultExpandAll: false, 8 | expandRows: [], 9 | }, 10 | } 11 | }, 12 | 13 | methods: { 14 | updateExpandRows() { 15 | const { data = [], rowKey, defaultExpandAll, expandRows } = this.states 16 | if (defaultExpandAll) { 17 | this.states.expandRows = data.slice() 18 | } else if (rowKey) { 19 | // TODO:这里的代码可以优化 20 | const expandRowsMap = getKeysMap(expandRows, rowKey) 21 | this.states.expandRows = data.reduce((prev, row) => { 22 | const rowId = getRowIdentity(row, rowKey) 23 | const rowInfo = expandRowsMap[rowId] 24 | if (rowInfo) { 25 | prev.push(row) 26 | } 27 | return prev 28 | }, []) 29 | } else { 30 | this.states.expandRows = [] 31 | } 32 | }, 33 | 34 | toggleRowExpansion(row, expanded) { 35 | const changed = toggleRowStatus(this.states.expandRows, row, expanded) 36 | if (changed) { 37 | this.table.$emit('expand-change', row, this.states.expandRows.slice()) 38 | this.scheduleLayout() 39 | } 40 | }, 41 | 42 | setExpandRowKeys(rowKeys) { 43 | this.assertRowKey() 44 | // TODO:这里的代码可以优化 45 | const { data, rowKey } = this.states 46 | const keysMap = getKeysMap(data, rowKey) 47 | this.states.expandRows = rowKeys.reduce((prev, cur) => { 48 | const info = keysMap[cur] 49 | if (info) { 50 | prev.push(info.row) 51 | } 52 | return prev 53 | }, []) 54 | }, 55 | 56 | isRowExpanded(row) { 57 | const { expandRows = [], rowKey } = this.states 58 | if (rowKey) { 59 | const expandMap = getKeysMap(expandRows, rowKey) 60 | return !!expandMap[getRowIdentity(row, rowKey)] 61 | } 62 | return expandRows.indexOf(row) !== -1 63 | }, 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/store/helper.js: -------------------------------------------------------------------------------- 1 | import Store from './index' 2 | import debounce from 'throttle-debounce/debounce' 3 | 4 | export function createStore(table, initialState = {}) { 5 | if (!table) { 6 | throw new Error('Table is required.') 7 | } 8 | 9 | const store = new Store() 10 | store.table = table 11 | // fix https://github.com/ElemeFE/element/issues/14075 12 | // related pr https://github.com/ElemeFE/element/pull/14146 13 | store.toggleAllSelection = debounce(10, store._toggleAllSelection) 14 | Object.keys(initialState).forEach(key => { 15 | store.states[key] = initialState[key] 16 | }) 17 | return store 18 | } 19 | 20 | export function mapStates(mapper) { 21 | const res = {} 22 | Object.keys(mapper).forEach(key => { 23 | const value = mapper[key] 24 | let fn 25 | if (typeof value === 'string') { 26 | fn = function () { 27 | return this.store.states[value] 28 | } 29 | } else if (typeof value === 'function') { 30 | fn = function () { 31 | return value.call(this, this.store.states) 32 | } 33 | } else { 34 | console.error('invalid value type') 35 | } 36 | if (fn) { 37 | res[key] = fn 38 | } 39 | }) 40 | return res 41 | } 42 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Watcher from './watcher' 3 | import { arrayFind } from 'element-ui/src/utils/util' 4 | 5 | Watcher.prototype.mutations = { 6 | setData(states, data) { 7 | const dataInstanceChanged = states._data !== data 8 | states._data = data 9 | 10 | this.execQuery() 11 | // 数据变化,更新部分数据。 12 | // 没有使用 computed,而是手动更新部分数据 https://github.com/vuejs/vue/issues/6660#issuecomment-331417140 13 | this.updateCurrentRowData() 14 | this.updateExpandRows() 15 | if (states.reserveSelection) { 16 | this.assertRowKey() 17 | this.updateSelectionByRowKey() 18 | } else { 19 | if (dataInstanceChanged) { 20 | this.clearSelection() 21 | } else { 22 | this.cleanSelection() 23 | } 24 | } 25 | this.updateAllSelected() 26 | 27 | this.updateTableScrollY() 28 | }, 29 | 30 | insertColumn(states, column, index, parent) { 31 | let array = states._columns 32 | if (parent) { 33 | array = parent.children 34 | if (!array) array = parent.children = [] 35 | } 36 | 37 | if (typeof index !== 'undefined') { 38 | array.splice(index, 0, column) 39 | } else { 40 | array.push(column) 41 | } 42 | 43 | if (column.type === 'selection') { 44 | states.selectable = column.selectable 45 | states.reserveSelection = column.reserveSelection 46 | } 47 | 48 | if (!column.insertColumnIndex) { 49 | column.insertColumnIndex = index 50 | } 51 | 52 | if (this.table.$ready) { 53 | this.updateColumns() // hack for dynamics insert column 54 | this.scheduleLayout() 55 | } 56 | }, 57 | 58 | removeColumn(states, column, parent) { 59 | let array = states._columns 60 | if (parent) { 61 | array = parent.children 62 | if (!array) array = parent.children = [] 63 | } 64 | if (array) { 65 | array.splice(array.indexOf(column), 1) 66 | } 67 | 68 | if (this.table.$ready) { 69 | this.updateColumns() // hack for dynamics remove column 70 | this.scheduleLayout() 71 | } 72 | }, 73 | 74 | sort(states, options) { 75 | const { prop, order, init } = options 76 | if (prop) { 77 | const column = arrayFind(states.columns, column => column.property === prop) 78 | if (column) { 79 | column.order = order 80 | this.updateSort(column, prop, order) 81 | this.commit('changeSortCondition', { init }) 82 | } 83 | } 84 | }, 85 | 86 | changeSortCondition(states, options) { 87 | // 修复 pr https://github.com/ElemeFE/element/pull/15012 导致的 bug 88 | const { sortingColumn: column, sortProp: prop, sortOrder: order } = states 89 | if (order === null) { 90 | states.sortingColumn = null 91 | states.sortProp = null 92 | } 93 | const ingore = { filter: true } 94 | this.execQuery(ingore) 95 | 96 | if (!options || !(options.silent || options.init)) { 97 | this.table.$emit('sort-change', { 98 | column, 99 | prop, 100 | order, 101 | }) 102 | } 103 | 104 | this.updateTableScrollY() 105 | }, 106 | 107 | filterChange(states, options) { 108 | const { column, values, silent } = options 109 | const newFilters = this.updateFilters(column, values) 110 | 111 | this.execQuery() 112 | 113 | if (!silent) { 114 | this.table.$emit('filter-change', newFilters) 115 | } 116 | 117 | this.updateTableScrollY() 118 | }, 119 | 120 | toggleAllSelection() { 121 | this.toggleAllSelection() 122 | }, 123 | 124 | rowSelectedChanged(states, row) { 125 | this.toggleRowSelection(row) 126 | this.updateAllSelected() 127 | }, 128 | 129 | setHoverRow(states, row) { 130 | states.hoverRow = row 131 | }, 132 | 133 | setCurrentRow(states, row) { 134 | this.updateCurrentRow(row) 135 | }, 136 | 137 | updateColumnsCell(states, cache) { 138 | if (!Array.isArray(cache)) return 139 | 140 | const { _columns } = states 141 | _columns.sort((item1, item2) => { 142 | const aa = cache.findIndex(elem => elem.label === item1.label) 143 | if (aa === -1) return 1 144 | const bb = cache.findIndex(elem => elem.label === item2.label) 145 | return aa - bb 146 | }) 147 | 148 | _columns.forEach(item => { 149 | const corresponding = cache.find(x => x.label === item.label) 150 | if (corresponding && corresponding.width && corresponding.width !== item.width) { 151 | item.width = corresponding.width 152 | item.realWidth = corresponding.width 153 | } 154 | }) 155 | 156 | this.scheduleLayout() 157 | this.updateColumns() 158 | }, 159 | } 160 | 161 | Watcher.prototype.commit = function (name, ...args) { 162 | const mutations = this.mutations 163 | if (mutations[name]) { 164 | mutations[name].apply(this, [this.states].concat(args)) 165 | } else { 166 | throw new Error(`Action not found: ${name}`) 167 | } 168 | } 169 | 170 | Watcher.prototype.updateTableScrollY = function () { 171 | Vue.nextTick(this.table.updateScrollY) 172 | } 173 | 174 | export default Watcher 175 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/store/tree.js: -------------------------------------------------------------------------------- 1 | import { walkTreeNode, getRowIdentity } from '../util' 2 | 3 | export default { 4 | data() { 5 | return { 6 | states: { 7 | // defaultExpandAll 存在于 expand.js 中,这里不重复添加 8 | // 在展开行中,expandRowKeys 会被转化成 expandRows,expandRowKeys 这个属性只是记录了 TreeTable 行的展开 9 | // TODO: 拆分为独立的 TreeTable,统一用法 10 | expandRowKeys: [], 11 | treeData: {}, 12 | indent: 16, 13 | lazy: false, 14 | lazyTreeNodeMap: {}, 15 | lazyColumnIdentifier: 'hasChildren', 16 | childrenColumnName: 'children', 17 | }, 18 | } 19 | }, 20 | 21 | computed: { 22 | // 嵌入型的数据,watch 无法是检测到变化 https://github.com/ElemeFE/element/issues/14998 23 | // TODO: 使用 computed 解决该问题,是否会造成性能问题? 24 | // @return { id: { level, children } } 25 | normalizedData() { 26 | if (!this.states.rowKey) return {} 27 | const data = this.states.data || [] 28 | return this.normalize(data) 29 | }, 30 | // @return { id: { children } } 31 | // 针对懒加载的情形,不处理嵌套数据 32 | normalizedLazyNode() { 33 | const { rowKey, lazyTreeNodeMap, lazyColumnIdentifier } = this.states 34 | const keys = Object.keys(lazyTreeNodeMap) 35 | const res = {} 36 | if (!keys.length) return res 37 | keys.forEach(key => { 38 | if (lazyTreeNodeMap[key].length) { 39 | const item = { children: [] } 40 | lazyTreeNodeMap[key].forEach(row => { 41 | const currentRowKey = getRowIdentity(row, rowKey) 42 | item.children.push(currentRowKey) 43 | if (row[lazyColumnIdentifier] && !res[currentRowKey]) { 44 | res[currentRowKey] = { children: [] } 45 | } 46 | }) 47 | res[key] = item 48 | } 49 | }) 50 | return res 51 | }, 52 | }, 53 | 54 | watch: { 55 | normalizedData: 'updateTreeData', 56 | normalizedLazyNode: 'updateTreeData', 57 | }, 58 | 59 | methods: { 60 | normalize(data) { 61 | const { childrenColumnName, lazyColumnIdentifier, rowKey, lazy } = this.states 62 | const res = {} 63 | walkTreeNode( 64 | data, 65 | (parent, children, level) => { 66 | const parentId = getRowIdentity(parent, rowKey) 67 | if (Array.isArray(children)) { 68 | res[parentId] = { 69 | children: children.map(row => getRowIdentity(row, rowKey)), 70 | level, 71 | } 72 | } else if (lazy) { 73 | // 当 children 不存在且 lazy 为 true,该节点即为懒加载的节点 74 | res[parentId] = { 75 | children: [], 76 | lazy: true, 77 | level, 78 | } 79 | } 80 | }, 81 | childrenColumnName, 82 | lazyColumnIdentifier, 83 | ) 84 | return res 85 | }, 86 | 87 | updateTreeData() { 88 | const nested = this.normalizedData 89 | const normalizedLazyNode = this.normalizedLazyNode 90 | const keys = Object.keys(nested) 91 | const newTreeData = {} 92 | if (keys.length) { 93 | const { treeData: oldTreeData, defaultExpandAll, expandRowKeys, lazy } = this.states 94 | const rootLazyRowKeys = [] 95 | const getExpanded = (oldValue, key) => { 96 | const included = defaultExpandAll || (expandRowKeys && expandRowKeys.indexOf(key) !== -1) 97 | return !!((oldValue && oldValue.expanded) || included) 98 | } 99 | // 合并 expanded 与 display,确保数据刷新后,状态不变 100 | keys.forEach(key => { 101 | const oldValue = oldTreeData[key] 102 | const newValue = { ...nested[key] } 103 | newValue.expanded = getExpanded(oldValue, key) 104 | if (newValue.lazy) { 105 | const { loaded = false, loading = false } = oldValue || {} 106 | newValue.loaded = !!loaded 107 | newValue.loading = !!loading 108 | rootLazyRowKeys.push(key) 109 | } 110 | newTreeData[key] = newValue 111 | }) 112 | // 根据懒加载数据更新 treeData 113 | const lazyKeys = Object.keys(normalizedLazyNode) 114 | if (lazy && lazyKeys.length && rootLazyRowKeys.length) { 115 | lazyKeys.forEach(key => { 116 | const oldValue = oldTreeData[key] 117 | const lazyNodeChildren = normalizedLazyNode[key].children 118 | if (rootLazyRowKeys.indexOf(key) !== -1) { 119 | // 懒加载的 root 节点,更新一下原有的数据,原来的 children 一定是空数组 120 | if (newTreeData[key].children.length !== 0) { 121 | throw new Error('[ElTable]children must be an empty array.') 122 | } 123 | newTreeData[key].children = lazyNodeChildren 124 | } else { 125 | const { loaded = false, loading = false } = oldValue || {} 126 | newTreeData[key] = { 127 | lazy: true, 128 | loaded: !!loaded, 129 | loading: !!loading, 130 | expanded: getExpanded(oldValue, key), 131 | children: lazyNodeChildren, 132 | level: '', 133 | } 134 | } 135 | }) 136 | } 137 | } 138 | this.states.treeData = newTreeData 139 | this.updateTableScrollY() 140 | }, 141 | 142 | updateTreeExpandKeys(value) { 143 | this.states.expandRowKeys = value 144 | this.updateTreeData() 145 | }, 146 | 147 | toggleTreeExpansion(row, expanded) { 148 | this.assertRowKey() 149 | 150 | const { rowKey, treeData } = this.states 151 | const id = getRowIdentity(row, rowKey) 152 | const data = id && treeData[id] 153 | if (id && data && 'expanded' in data) { 154 | const oldExpanded = data.expanded 155 | expanded = typeof expanded === 'undefined' ? !data.expanded : expanded 156 | treeData[id].expanded = expanded 157 | if (oldExpanded !== expanded) { 158 | this.table.$emit('expand-change', row, expanded) 159 | } 160 | this.updateTableScrollY() 161 | } 162 | }, 163 | 164 | loadOrToggle(row) { 165 | this.assertRowKey() 166 | const { lazy, treeData, rowKey } = this.states 167 | const id = getRowIdentity(row, rowKey) 168 | const data = treeData[id] 169 | if (lazy && data && 'loaded' in data && !data.loaded) { 170 | this.loadData(row, id, data) 171 | } else { 172 | this.toggleTreeExpansion(row) 173 | } 174 | }, 175 | 176 | loadData(row, key, treeNode) { 177 | const { load } = this.table 178 | const { treeData: rawTreeData } = this.states 179 | if (load && !rawTreeData[key].loaded) { 180 | rawTreeData[key].loading = true 181 | load(row, treeNode, data => { 182 | if (!Array.isArray(data)) { 183 | throw new Error('[ElTable] data must be an array') 184 | } 185 | const { lazyTreeNodeMap, treeData } = this.states 186 | treeData[key].loading = false 187 | treeData[key].loaded = true 188 | treeData[key].expanded = true 189 | if (data.length) { 190 | this.$set(lazyTreeNodeMap, key, data) 191 | } 192 | this.table.$emit('expand-change', row, true) 193 | }) 194 | } 195 | }, 196 | }, 197 | } 198 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/table-footer.jsx: -------------------------------------------------------------------------------- 1 | import LayoutObserver from './layout-observer' 2 | import { mapStates } from './store/helper' 3 | 4 | export default { 5 | name: 'ElTableFooter', 6 | 7 | mixins: [LayoutObserver], 8 | 9 | render(h) { 10 | let sums = [] 11 | if (this.summaryMethod) { 12 | sums = this.summaryMethod({ columns: this.columns, data: this.store.states.data }) 13 | } else { 14 | this.columns.forEach((column, index) => { 15 | if (index === 0) { 16 | sums[index] = this.sumText 17 | return 18 | } 19 | const values = this.store.states.data.map(item => Number(item[column.property])) 20 | const precisions = [] 21 | let notNumber = true 22 | values.forEach(value => { 23 | if (!isNaN(value)) { 24 | notNumber = false 25 | const decimal = ('' + value).split('.')[1] 26 | precisions.push(decimal ? decimal.length : 0) 27 | } 28 | }) 29 | const precision = Math.max.apply(null, precisions) 30 | if (!notNumber) { 31 | sums[index] = values.reduce((prev, curr) => { 32 | const value = Number(curr) 33 | if (!isNaN(value)) { 34 | return parseFloat((prev + curr).toFixed(Math.min(precision, 20))) 35 | } else { 36 | return prev 37 | } 38 | }, 0) 39 | } else { 40 | sums[index] = '' 41 | } 42 | }) 43 | } 44 | 45 | return ( 46 | 47 | 48 | {this.columns.map(column => ( 49 | 50 | ))} 51 | {this.hasGutter ? : ''} 52 | 53 | 54 | 55 | {this.columns.map((column, cellIndex) => ( 56 | 59 | ))} 60 | {this.hasGutter ? : ''} 61 | 62 | 63 | 64 | ) 65 | }, 66 | 67 | props: { 68 | fixed: String, 69 | store: { 70 | required: true, 71 | }, 72 | summaryMethod: Function, 73 | sumText: String, 74 | border: Boolean, 75 | defaultSort: { 76 | type: Object, 77 | default() { 78 | return { 79 | prop: '', 80 | order: '', 81 | } 82 | }, 83 | }, 84 | }, 85 | 86 | computed: { 87 | table() { 88 | return this.$parent 89 | }, 90 | 91 | hasGutter() { 92 | return !this.fixed && this.tableLayout.gutterWidth 93 | }, 94 | 95 | ...mapStates({ 96 | columns: 'columns', 97 | isAllSelected: 'isAllSelected', 98 | leftFixedLeafCount: 'fixedLeafColumnsLength', 99 | rightFixedLeafCount: 'rightFixedLeafColumnsLength', 100 | columnsCount: states => states.columns.length, 101 | leftFixedCount: states => states.fixedColumns.length, 102 | rightFixedCount: states => states.rightFixedColumns.length, 103 | }), 104 | }, 105 | 106 | methods: { 107 | isCellHidden(index, columns, column) { 108 | if (this.fixed === true || this.fixed === 'left') { 109 | return index >= this.leftFixedLeafCount 110 | } else if (this.fixed === 'right') { 111 | let before = 0 112 | for (let i = 0; i < index; i++) { 113 | before += columns[i].colSpan 114 | } 115 | return before < this.columnsCount - this.rightFixedLeafCount 116 | } else if (!this.fixed && column.fixed) { 117 | // hide cell when footer instance is not fixed and column is fixed 118 | return true 119 | } else { 120 | return index < this.leftFixedCount || index >= this.columnsCount - this.rightFixedCount 121 | } 122 | }, 123 | 124 | getRowClasses(column, cellIndex) { 125 | const classes = [column.id, column.align, column.labelClassName] 126 | if (column.className) { 127 | classes.push(column.className) 128 | } 129 | if (this.isCellHidden(cellIndex, this.columns, column)) { 130 | classes.push('is-hidden') 131 | } 132 | if (!column.children) { 133 | classes.push('is-leaf') 134 | } 135 | return classes 136 | }, 137 | }, 138 | } 139 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/table-row.jsx: -------------------------------------------------------------------------------- 1 | import { Ripple } from './directive' 2 | 3 | export default { 4 | name: 'ElTableRow', 5 | props: ['columns', 'row', 'index', 'isSelected', 'isExpanded', 'store', 'context', 'firstDefaultColumnIndex', 'treeRowData', 'treeIndent', 'columnsHidden', 'getSpan', 'getColspanRealWidth', 'getCellStyle', 'getCellClass', 'handleCellMouseLeave', 'handleCellMouseEnter', 'fixed'], 6 | directives: { 7 | Ripple, 8 | }, 9 | render() { 10 | const { columns, row, index: $index, store, context, firstDefaultColumnIndex, treeRowData, treeIndent, columnsHidden = [], isSelected, isExpanded } = this 11 | 12 | return ( 13 | 14 | {columns.map((column, cellIndex) => { 15 | const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex) 16 | if (!rowspan || !colspan) { 17 | return null 18 | } 19 | const columnData = { ...column } 20 | columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex) 21 | const data = { 22 | store, 23 | isSelected, 24 | isExpanded, 25 | _self: context, 26 | column: columnData, 27 | row, 28 | $index, 29 | } 30 | if (cellIndex === firstDefaultColumnIndex && treeRowData) { 31 | data.treeNode = { 32 | indent: treeRowData.level * treeIndent, 33 | level: treeRowData.level, 34 | } 35 | if (typeof treeRowData.expanded === 'boolean') { 36 | data.treeNode.expanded = treeRowData.expanded 37 | // 表明是懒加载 38 | if ('loading' in treeRowData) { 39 | data.treeNode.loading = treeRowData.loading 40 | } 41 | if ('noLazyChildren' in treeRowData) { 42 | data.treeNode.noLazyChildren = treeRowData.noLazyChildren 43 | } 44 | } 45 | } 46 | return ( 47 | this.handleCellMouseEnter($event, row)} on-mouseleave={this.handleCellMouseLeave}> 48 | {column.renderCell.call(this._renderProxy, this.$createElement, data, columnsHidden[cellIndex])} 49 | 50 | ) 51 | })} 52 | 53 | ) 54 | }, 55 | } 56 | -------------------------------------------------------------------------------- /src/components/element-ui/el-table/util.js: -------------------------------------------------------------------------------- 1 | import { getValueByPath } from 'element-ui/src/utils/util' 2 | 3 | export const getCell = function (event) { 4 | let cell = event.target 5 | 6 | while (cell && cell.tagName.toUpperCase() !== 'HTML') { 7 | if (cell.tagName.toUpperCase() === 'TD') { 8 | return cell 9 | } 10 | cell = cell.parentNode 11 | } 12 | 13 | return null 14 | } 15 | 16 | const isObject = function (obj) { 17 | return obj !== null && typeof obj === 'object' 18 | } 19 | 20 | export const orderBy = function (array, sortKey, reverse, sortMethod, sortBy) { 21 | if (!sortKey && !sortMethod && (!sortBy || (Array.isArray(sortBy) && !sortBy.length))) { 22 | return array 23 | } 24 | if (typeof reverse === 'string') { 25 | reverse = reverse === 'descending' ? -1 : 1 26 | } else { 27 | reverse = reverse && reverse < 0 ? -1 : 1 28 | } 29 | const getKey = sortMethod 30 | ? null 31 | : function (value, index) { 32 | if (sortBy) { 33 | if (!Array.isArray(sortBy)) { 34 | sortBy = [sortBy] 35 | } 36 | return sortBy.map(function (by) { 37 | if (typeof by === 'string') { 38 | return getValueByPath(value, by) 39 | } else { 40 | return by(value, index, array) 41 | } 42 | }) 43 | } 44 | if (sortKey !== '$key') { 45 | if (isObject(value) && '$value' in value) value = value.$value 46 | } 47 | return [isObject(value) ? getValueByPath(value, sortKey) : value] 48 | } 49 | const compare = function (a, b) { 50 | if (sortMethod) { 51 | return sortMethod(a.value, b.value) 52 | } 53 | for (let i = 0, len = a.key.length; i < len; i++) { 54 | if (a.key[i] < b.key[i]) { 55 | return -1 56 | } 57 | if (a.key[i] > b.key[i]) { 58 | return 1 59 | } 60 | } 61 | return 0 62 | } 63 | return array 64 | .map(function (value, index) { 65 | return { 66 | value: value, 67 | index: index, 68 | key: getKey ? getKey(value, index) : null, 69 | } 70 | }) 71 | .sort(function (a, b) { 72 | let order = compare(a, b) 73 | if (!order) { 74 | // make stable https://en.wikipedia.org/wiki/Sorting_algorithm#Stability 75 | order = a.index - b.index 76 | } 77 | return order * reverse 78 | }) 79 | .map(item => item.value) 80 | } 81 | 82 | export const getColumnById = function (table, columnId) { 83 | let column = null 84 | table.columns.forEach(function (item) { 85 | if (item.id === columnId) { 86 | column = item 87 | } 88 | }) 89 | return column 90 | } 91 | 92 | export const getColumnByKey = function (table, columnKey) { 93 | let column = null 94 | for (let i = 0; i < table.columns.length; i++) { 95 | const item = table.columns[i] 96 | if (item.columnKey === columnKey) { 97 | column = item 98 | break 99 | } 100 | } 101 | return column 102 | } 103 | 104 | export const getColumnByCell = function (table, cell) { 105 | const matches = (cell.className || '').match(/el-table_[^\s]+/gm) 106 | if (matches) { 107 | return getColumnById(table, matches[0]) 108 | } 109 | return null 110 | } 111 | 112 | export const getRowIdentity = (row, rowKey) => { 113 | if (!row) throw new Error('row is required when get row identity') 114 | if (typeof rowKey === 'string') { 115 | if (rowKey.indexOf('.') < 0) { 116 | return row[rowKey] 117 | } 118 | const key = rowKey.split('.') 119 | let current = row 120 | for (let i = 0; i < key.length; i++) { 121 | current = current[key[i]] 122 | } 123 | return current 124 | } else if (typeof rowKey === 'function') { 125 | return rowKey.call(null, row) 126 | } 127 | } 128 | 129 | export const getKeysMap = function (array, rowKey) { 130 | const arrayMap = {} 131 | ;(array || []).forEach((row, index) => { 132 | arrayMap[getRowIdentity(row, rowKey)] = { row, index } 133 | }) 134 | return arrayMap 135 | } 136 | 137 | function hasOwn(obj, key) { 138 | return Object.prototype.hasOwnProperty.call(obj, key) 139 | } 140 | 141 | export function mergeOptions(defaults, config) { 142 | const options = {} 143 | let key 144 | for (key in defaults) { 145 | options[key] = defaults[key] 146 | } 147 | for (key in config) { 148 | if (hasOwn(config, key)) { 149 | const value = config[key] 150 | if (typeof value !== 'undefined') { 151 | options[key] = value 152 | } 153 | } 154 | } 155 | return options 156 | } 157 | 158 | export function parseWidth(width) { 159 | if (width !== undefined) { 160 | width = parseInt(width, 10) 161 | if (isNaN(width)) { 162 | width = null 163 | } 164 | } 165 | return width 166 | } 167 | 168 | export function parseMinWidth(minWidth) { 169 | if (typeof minWidth !== 'undefined') { 170 | minWidth = parseWidth(minWidth) 171 | if (isNaN(minWidth)) { 172 | minWidth = 80 173 | } 174 | } 175 | return minWidth 176 | } 177 | 178 | export function parseHeight(height) { 179 | if (typeof height === 'number') { 180 | return height 181 | } 182 | if (typeof height === 'string') { 183 | if (/^\d+(?:px)?$/.test(height)) { 184 | return parseInt(height, 10) 185 | } else { 186 | return height 187 | } 188 | } 189 | return null 190 | } 191 | 192 | // https://github.com/reduxjs/redux/blob/master/src/compose.js 193 | export function compose(...funcs) { 194 | if (funcs.length === 0) { 195 | return arg => arg 196 | } 197 | if (funcs.length === 1) { 198 | return funcs[0] 199 | } 200 | return funcs.reduce( 201 | (a, b) => 202 | (...args) => 203 | a(b(...args)), 204 | ) 205 | } 206 | 207 | export function toggleRowStatus(statusArr, row, newVal) { 208 | let changed = false 209 | const index = statusArr.indexOf(row) 210 | const included = index !== -1 211 | 212 | const addRow = () => { 213 | statusArr.push(row) 214 | changed = true 215 | } 216 | const removeRow = () => { 217 | statusArr.splice(index, 1) 218 | changed = true 219 | } 220 | 221 | if (typeof newVal === 'boolean') { 222 | if (newVal && !included) { 223 | addRow() 224 | } else if (!newVal && included) { 225 | removeRow() 226 | } 227 | } else { 228 | if (included) { 229 | removeRow() 230 | } else { 231 | addRow() 232 | } 233 | } 234 | return changed 235 | } 236 | 237 | export function walkTreeNode(root, cb, childrenKey = 'children', lazyKey = 'hasChildren') { 238 | const isNil = array => !(Array.isArray(array) && array.length) 239 | 240 | function _walker(parent, children, level) { 241 | cb(parent, children, level) 242 | children.forEach(item => { 243 | if (item[lazyKey]) { 244 | cb(item, null, level + 1) 245 | return 246 | } 247 | const children = item[childrenKey] 248 | if (!isNil(children)) { 249 | _walker(item, children, level + 1) 250 | } 251 | }) 252 | } 253 | 254 | root.forEach(item => { 255 | if (item[lazyKey]) { 256 | cb(item, null, 0) 257 | return 258 | } 259 | const children = item[childrenKey] 260 | if (!isNil(children)) { 261 | _walker(item, children, 0) 262 | } 263 | }) 264 | } 265 | 266 | export function deepClone(source) { 267 | if (!source && typeof source !== 'object') { 268 | throw new Error('error arguments', 'deepClone') 269 | } 270 | const targetObj = source.constructor === Array ? [] : {} 271 | Object.keys(source).forEach(keys => { 272 | if (source[keys] && typeof source[keys] === 'object') { 273 | targetObj[keys] = deepClone(source[keys]) 274 | } else { 275 | targetObj[keys] = source[keys] 276 | } 277 | }) 278 | return targetObj 279 | } 280 | -------------------------------------------------------------------------------- /src/components/element-ui/el-volume/el-volume-item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 37 | 38 | 53 | -------------------------------------------------------------------------------- /src/components/element-ui/el-volume/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 19 | 20 | 50 | -------------------------------------------------------------------------------- /src/components/element-ui/index.js: -------------------------------------------------------------------------------- 1 | import ElTable from './el-table/index.vue' 2 | import ElTableColumn from './el-table-column' 3 | import ElVolume from './el-volume' 4 | import ElVolumeItem from './el-volume/el-volume-item' 5 | 6 | export default Vue => { 7 | Vue.component(ElTable.name, ElTable) 8 | Vue.component(ElTableColumn.name, ElTableColumn) 9 | Vue.component(ElVolume.name, ElVolume) 10 | Vue.component(ElVolumeItem.name, ElVolumeItem) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/element-ui/popper.js: -------------------------------------------------------------------------------- 1 | import { PopupManager } from 'element-ui/src/utils/popup' 2 | import PopperJS from './PopperJS' 3 | const stop = e => e.stopPropagation() 4 | 5 | /** 6 | * @param {HTMLElement} [reference=$refs.reference] - The reference element used to position the popper. 7 | * @param {HTMLElement} [popper=$refs.popper] - The HTML element used as popper, or a configuration used to generate the popper. 8 | * @param {String} [placement=button] - Placement of the popper accepted values: top(-start, -end), right(-start, -end), bottom(-start, -end), left(-start, -end) 9 | * @param {Number} [offset=0] - Amount of pixels the popper will be shifted (can be negative). 10 | * @param {Boolean} [visible=false] Visibility of the popup element. 11 | * @param {Boolean} [visible-arrow=false] Visibility of the arrow, no style. 12 | */ 13 | export default { 14 | props: { 15 | transformOrigin: { 16 | type: [Boolean, String], 17 | default: true, 18 | }, 19 | placement: { 20 | type: String, 21 | default: 'bottom', 22 | }, 23 | boundariesPadding: { 24 | type: Number, 25 | default: 5, 26 | }, 27 | reference: {}, 28 | popper: {}, 29 | offset: { 30 | default: 0, 31 | }, 32 | value: Boolean, 33 | visibleArrow: Boolean, 34 | arrowOffset: { 35 | type: Number, 36 | default: 35, 37 | }, 38 | appendToBody: { 39 | type: Boolean, 40 | default: true, 41 | }, 42 | popperOptions: { 43 | type: Object, 44 | default() { 45 | return { 46 | gpuAcceleration: false, 47 | } 48 | }, 49 | }, 50 | }, 51 | 52 | data() { 53 | return { 54 | showPopper: false, 55 | currentPlacement: '', 56 | } 57 | }, 58 | 59 | watch: { 60 | value: { 61 | immediate: true, 62 | handler(val) { 63 | this.showPopper = val 64 | this.$emit('input', val) 65 | }, 66 | }, 67 | 68 | showPopper(val) { 69 | if (this.disabled) return 70 | val ? this.updatePopper() : this.destroyPopper() 71 | this.$emit('input', val) 72 | }, 73 | }, 74 | 75 | methods: { 76 | createPopper() { 77 | if (this.$isServer) return 78 | this.currentPlacement = this.currentPlacement || this.placement 79 | if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(this.currentPlacement)) { 80 | return 81 | } 82 | 83 | const options = this.popperOptions 84 | 85 | const popper = (this.popperElm = this.popperElm || this.popper || this.$refs.popper) 86 | 87 | let reference = (this.referenceElm = this.referenceElm || this.reference || this.$refs.reference) 88 | console.log(reference) 89 | 90 | if (!reference && this.$slots.reference && this.$slots.reference[0]) { 91 | reference = this.referenceElm = this.$slots.reference[0].elm 92 | } 93 | 94 | if (!popper || !reference) return 95 | if (this.visibleArrow) this.appendArrow(popper) 96 | if (this.appendToBody) document.body.appendChild(this.popperElm) 97 | if (this.popperJS && this.popperJS.destroy) { 98 | this.popperJS.destroy() 99 | } 100 | 101 | options.placement = this.currentPlacement 102 | options.offset = this.offset 103 | options.arrowOffset = this.arrowOffset 104 | this.popperJS = new PopperJS(reference, popper, options) 105 | 106 | this.popperJS.onCreate(_ => { 107 | this.$emit('created', this) 108 | this.resetTransformOrigin() 109 | this.$nextTick(this.updatePopper) 110 | }) 111 | if (typeof options.onUpdate === 'function') { 112 | this.popperJS.onUpdate(options.onUpdate) 113 | } 114 | this.popperJS._popper.style.zIndex = PopupManager.nextZIndex() 115 | this.popperElm.addEventListener('click', stop) 116 | }, 117 | 118 | updatePopper() { 119 | const popperJS = this.popperJS 120 | if (popperJS) { 121 | popperJS.update() 122 | if (popperJS._popper) { 123 | popperJS._popper.style.zIndex = PopupManager.nextZIndex() 124 | } 125 | } else { 126 | this.createPopper() 127 | } 128 | }, 129 | 130 | doDestroy(forceDestroy) { 131 | /* istanbul ignore if */ 132 | if (!this.popperJS || (this.showPopper && !forceDestroy)) return 133 | this.popperJS.destroy() 134 | this.popperJS = null 135 | }, 136 | 137 | destroyPopper() { 138 | if (this.popperJS) { 139 | this.resetTransformOrigin() 140 | } 141 | }, 142 | 143 | resetTransformOrigin() { 144 | if (!this.transformOrigin) return 145 | const placementMap = { 146 | top: 'bottom', 147 | bottom: 'top', 148 | left: 'right', 149 | right: 'left', 150 | } 151 | const placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0] 152 | const origin = placementMap[placement] 153 | this.popperJS._popper.style.transformOrigin = typeof this.transformOrigin === 'string' ? this.transformOrigin : ['top', 'bottom'].indexOf(placement) > -1 ? `center ${origin}` : `${origin} center` 154 | }, 155 | 156 | appendArrow(element) { 157 | let hash 158 | if (this.appended) { 159 | return 160 | } 161 | 162 | this.appended = true 163 | 164 | for (const item in element.attributes) { 165 | if (/^_v-/.test(element.attributes[item].name)) { 166 | hash = element.attributes[item].name 167 | break 168 | } 169 | } 170 | 171 | const arrow = document.createElement('div') 172 | 173 | if (hash) { 174 | arrow.setAttribute(hash, '') 175 | } 176 | arrow.setAttribute('x-arrow', '') 177 | arrow.className = 'popper__arrow' 178 | element.appendChild(arrow) 179 | }, 180 | }, 181 | 182 | beforeDestroy() { 183 | this.doDestroy(true) 184 | if (this.popperElm && this.popperElm.parentNode === document.body) { 185 | this.popperElm.removeEventListener('click', stop) 186 | document.body.removeChild(this.popperElm) 187 | } 188 | }, 189 | 190 | // call destroy in keep-alive mode 191 | deactivated() { 192 | this.$options.beforeDestroy[0].call(this) 193 | }, 194 | } 195 | -------------------------------------------------------------------------------- /src/directives/attr.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind(el) { 3 | for (const attr of 'width,height,background,font-size,color'.split(',')) { 4 | if (attr in el.attributes) { 5 | const property = el.getAttribute(attr) 6 | el.style[attr] = property 7 | 8 | el.removeAttribute(attr) 9 | } 10 | } 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/layout/components/asider.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /src/layout/components/header.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 19 | 20 | 26 | -------------------------------------------------------------------------------- /src/layout/components/main.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 19 | 20 | 28 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 39 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from '@/router' 3 | import store from '@/store' 4 | import App from './App.vue' 5 | 6 | import '@/assets/styles/tailwind.css' 7 | 8 | import ElementUI from 'element-ui' 9 | import 'element-ui/lib/theme-chalk/index.css' 10 | Vue.use(ElementUI) 11 | 12 | import customElementUi from '@/components/element-ui' 13 | Vue.use(customElementUi) 14 | 15 | import '@/assets/styles/index.scss' 16 | import 'virtual:svg-icons-register' 17 | 18 | import SvgIcon from '@/components/SvgIcon' 19 | import RightToolbar from '@/components/RightToolbar' 20 | import ScrollPanel from '@/components/scrollPanel' 21 | import CustomTitle from '@/components/CustomTitle' 22 | import Layout from '@/layout' 23 | 24 | Vue.component('SvgIcon', SvgIcon) 25 | Vue.component('RightToolbar', RightToolbar) 26 | Vue.component('ScrollPanel', ScrollPanel) 27 | Vue.component('CustomTitle', CustomTitle) 28 | Vue.component('Layout', Layout) 29 | 30 | Vue.config.productionTip = false 31 | new Vue({ 32 | el: '#app', 33 | router, 34 | store, 35 | render: h => h(App), 36 | }) 37 | -------------------------------------------------------------------------------- /src/mixins/table-header-drag.js: -------------------------------------------------------------------------------- 1 | import { localstorageGet, localstorageSet } from '@/utils' 2 | import { Key } from '@/components/RightToolbar' 3 | 4 | export default { 5 | activated() { 6 | this.$refs.table?.doLayout() 7 | }, 8 | methods: { 9 | headerCellDragend(columns) { 10 | const optionName = this.$options.name 11 | const tableMap = localstorageGet(Key) 12 | if (!tableMap.dragger) tableMap.dragger = {} 13 | tableMap.dragger[optionName] = columns 14 | localstorageSet(Key, tableMap) 15 | }, 16 | headerDragend(newWidth, _oldWidth, column, _event, columns) { 17 | const optionName = this.$options.name 18 | const tableMap = localstorageGet(Key) 19 | if (!tableMap.dragger) tableMap.dragger = {} 20 | tableMap.dragger[optionName] = columns 21 | 22 | tableMap.dragger[optionName].forEach(item => { 23 | if (item.label === column.label) item.width = newWidth 24 | }) 25 | localstorageSet(Key, tableMap) 26 | }, 27 | tableReady(table) { 28 | const optionName = this.$options.name 29 | const tableMap = localstorageGet(Key) 30 | if (!tableMap.dragger) tableMap.dragger = {} 31 | table && (this.__cacheTable__ = table) 32 | ;(table || this.__cacheTable__).changeExistingSort(tableMap.dragger[optionName]) 33 | }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | export const constantRoutes = [ 7 | { 8 | path: '', 9 | component: () => import('@/views/dashboard'), 10 | }, 11 | { 12 | path: '/user', 13 | component: () => import('@/views/user'), 14 | }, 15 | { 16 | path: '/client', 17 | component: () => import('@/views/client'), 18 | }, 19 | { 20 | path: '/camera', 21 | component: () => import('@/views/camera'), 22 | }, 23 | { 24 | path: '/operate', 25 | component: () => import('@/views/operate'), 26 | }, 27 | 28 | { 29 | path: '/test', 30 | component: () => import('@/views/test'), 31 | }, 32 | { 33 | path: '/test2', 34 | component: () => import('@/views/test2'), 35 | }, 36 | { 37 | path: '/other', 38 | component: () => import('@/views/other'), 39 | }, 40 | { 41 | path: '/waterfall', 42 | component: () => import('@/views/waterfall'), 43 | }, 44 | { 45 | path: '/color', 46 | component: () => import('@/views/color'), 47 | }, 48 | { 49 | path: '/calendar', 50 | component: () => import('@/views/calendar'), 51 | }, 52 | { 53 | path: '/chart', 54 | component: () => import('@/views/chart'), 55 | }, 56 | ] 57 | 58 | const router = new Router({ 59 | mode: 'history', 60 | scrollBehavior: () => ({ y: 0 }), 61 | routes: constantRoutes, 62 | }) 63 | 64 | export default router 65 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | 5 | Vue.use(Vuex) 6 | 7 | const store = new Vuex.Store({ 8 | modules: { 9 | app, 10 | }, 11 | }) 12 | 13 | export default store 14 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | // import Cookies from 'js-cookie' 2 | 3 | const state = {} 4 | 5 | const mutations = {} 6 | 7 | const actions = {} 8 | 9 | export default { 10 | namespaced: true, 11 | state, 12 | mutations, 13 | actions, 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function localstorageSet(key, obj = {}) { 2 | if (!key) return 3 | localStorage.setItem(key, JSON.stringify(obj)) 4 | } 5 | 6 | export function localstorageGet(key) { 7 | if (!key) return {} 8 | 9 | try { 10 | return JSON.parse(localStorage.getItem(key) || '{}') 11 | } catch (error) { 12 | return {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/views/calendar/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 43 | 44 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/views/chart/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 35 | 36 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /src/views/client/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 102 | 103 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /src/views/color/ColorBlock.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 45 | 46 | 81 | 82 | 90 | -------------------------------------------------------------------------------- /src/views/color/ColorControl.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/views/color/ColorPanel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 74 | 75 | 129 | -------------------------------------------------------------------------------- /src/views/color/ColorRow.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 53 | 54 | 68 | -------------------------------------------------------------------------------- /src/views/color/ColorTooltip.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 73 | 74 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/views/color/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 27 | 28 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 54 | 55 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/views/operate/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/views/other/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "彭平", 4 | "stamp": 1725252504000, 5 | "opera": "订阅" 6 | }, 7 | { 8 | "name": "朱磊", 9 | "stamp": 1725538498000, 10 | "opera": "播放失败" 11 | }, 12 | { 13 | "name": "郝强", 14 | "stamp": 1725632980000, 15 | "opera": "控制设备" 16 | }, 17 | { 18 | "name": "邵刚", 19 | "stamp": 1725673416000, 20 | "opera": "退出" 21 | }, 22 | { 23 | "name": "秦洋", 24 | "stamp": 1725743792000, 25 | "opera": "回放视频" 26 | }, 27 | { 28 | "name": "姜秀英", 29 | "stamp": 1725824332000, 30 | "opera": "登录" 31 | }, 32 | { 33 | "name": "崔强", 34 | "stamp": 1725895856000, 35 | "opera": "播放失败" 36 | }, 37 | { 38 | "name": "龚明", 39 | "stamp": 1725981958000, 40 | "opera": "播放失败" 41 | }, 42 | { 43 | "name": "侯秀英", 44 | "stamp": 1726082260000, 45 | "opera": "登录" 46 | }, 47 | { 48 | "name": "龚洋", 49 | "stamp": 1726315494000, 50 | "opera": "播放失败" 51 | }, 52 | { 53 | "name": "彭平", 54 | "stamp": 1725164441000, 55 | "opera": "登录" 56 | }, 57 | { 58 | "name": "朱磊", 59 | "stamp": 1725491189000, 60 | "opera": "控制设备" 61 | }, 62 | { 63 | "name": "郝强", 64 | "stamp": 1725494633000, 65 | "opera": "订阅" 66 | }, 67 | { 68 | "name": "邵刚", 69 | "stamp": 1725679323000, 70 | "opera": "回放视频" 71 | }, 72 | { 73 | "name": "秦洋", 74 | "stamp": 1725774727000, 75 | "opera": "播放失败" 76 | }, 77 | { 78 | "name": "姜秀英", 79 | "stamp": 1725948987000, 80 | "opera": "播放失败" 81 | }, 82 | { 83 | "name": "崔强", 84 | "stamp": 1725976347000, 85 | "opera": "登录" 86 | }, 87 | { 88 | "name": "龚明", 89 | "stamp": 1726006759000, 90 | "opera": "登录" 91 | }, 92 | { 93 | "name": "侯秀英", 94 | "stamp": 1726066753000, 95 | "opera": "订阅" 96 | }, 97 | { 98 | "name": "龚洋", 99 | "stamp": 1726340809000, 100 | "opera": "订阅" 101 | }, 102 | { 103 | "name": "彭平", 104 | "stamp": 1725141696000, 105 | "opera": "退出" 106 | }, 107 | { 108 | "name": "朱磊", 109 | "stamp": 1725238316000, 110 | "opera": "回放视频" 111 | }, 112 | { 113 | "name": "郝强", 114 | "stamp": 1725389472000, 115 | "opera": "回放视频" 116 | }, 117 | { 118 | "name": "邵刚", 119 | "stamp": 1725820874000, 120 | "opera": "订阅" 121 | }, 122 | { 123 | "name": "秦洋", 124 | "stamp": 1726068572000, 125 | "opera": "登录" 126 | }, 127 | { 128 | "name": "姜秀英", 129 | "stamp": 1726218096000, 130 | "opera": "回放视频" 131 | }, 132 | { 133 | "name": "崔强", 134 | "stamp": 1726243278000, 135 | "opera": "退出" 136 | }, 137 | { 138 | "name": "龚明", 139 | "stamp": 1726268348000, 140 | "opera": "退出" 141 | }, 142 | { 143 | "name": "侯秀英", 144 | "stamp": 1726284830000, 145 | "opera": "登录" 146 | }, 147 | { 148 | "name": "龚洋", 149 | "stamp": 1726481098000, 150 | "opera": "播放失败" 151 | }, 152 | { 153 | "name": "彭平", 154 | "stamp": 1725273544000, 155 | "opera": "播放失败" 156 | }, 157 | { 158 | "name": "朱磊", 159 | "stamp": 1725571384000, 160 | "opera": "控制设备" 161 | }, 162 | { 163 | "name": "郝强", 164 | "stamp": 1725585770000, 165 | "opera": "订阅" 166 | }, 167 | { 168 | "name": "邵刚", 169 | "stamp": 1725735600000, 170 | "opera": "回放视频" 171 | }, 172 | { 173 | "name": "秦洋", 174 | "stamp": 1725768762000, 175 | "opera": "回放视频" 176 | }, 177 | { 178 | "name": "姜秀英", 179 | "stamp": 1725938274000, 180 | "opera": "播放失败" 181 | }, 182 | { 183 | "name": "崔强", 184 | "stamp": 1725979810000, 185 | "opera": "播放失败" 186 | }, 187 | { 188 | "name": "龚明", 189 | "stamp": 1726068860000, 190 | "opera": "登录" 191 | }, 192 | { 193 | "name": "侯秀英", 194 | "stamp": 1726103870000, 195 | "opera": "退出" 196 | }, 197 | { 198 | "name": "龚洋", 199 | "stamp": 1726181490000, 200 | "opera": "播放失败" 201 | }, 202 | { 203 | "name": "彭平", 204 | "stamp": 1725203201000, 205 | "opera": "回放视频" 206 | }, 207 | { 208 | "name": "朱磊", 209 | "stamp": 1725426631000, 210 | "opera": "登录" 211 | }, 212 | { 213 | "name": "郝强", 214 | "stamp": 1725457303000, 215 | "opera": "退出" 216 | }, 217 | { 218 | "name": "邵刚", 219 | "stamp": 1725677157000, 220 | "opera": "退出" 221 | }, 222 | { 223 | "name": "秦洋", 224 | "stamp": 1725743189000, 225 | "opera": "订阅" 226 | }, 227 | { 228 | "name": "姜秀英", 229 | "stamp": 1725858291000, 230 | "opera": "播放失败" 231 | }, 232 | { 233 | "name": "崔强", 234 | "stamp": 1725912511000, 235 | "opera": "播放失败" 236 | }, 237 | { 238 | "name": "龚明", 239 | "stamp": 1726095533000, 240 | "opera": "退出" 241 | }, 242 | { 243 | "name": "侯秀英", 244 | "stamp": 1726325481000, 245 | "opera": "播放失败" 246 | }, 247 | { 248 | "name": "龚洋", 249 | "stamp": 1726383325000, 250 | "opera": "播放失败" 251 | }, 252 | { 253 | "name": "彭平", 254 | "stamp": 1725207000000, 255 | "opera": "退出" 256 | }, 257 | { 258 | "name": "朱磊", 259 | "stamp": 1725242498000, 260 | "opera": "订阅" 261 | }, 262 | { 263 | "name": "郝强", 264 | "stamp": 1725405446000, 265 | "opera": "播放失败" 266 | }, 267 | { 268 | "name": "邵刚", 269 | "stamp": 1725410120000, 270 | "opera": "登录" 271 | }, 272 | { 273 | "name": "秦洋", 274 | "stamp": 1725439628000, 275 | "opera": "回放视频" 276 | }, 277 | { 278 | "name": "姜秀英", 279 | "stamp": 1725664544000, 280 | "opera": "播放失败" 281 | }, 282 | { 283 | "name": "崔强", 284 | "stamp": 1725947114000, 285 | "opera": "回放视频" 286 | }, 287 | { 288 | "name": "龚明", 289 | "stamp": 1726155732000, 290 | "opera": "播放失败" 291 | }, 292 | { 293 | "name": "侯秀英", 294 | "stamp": 1726278992000, 295 | "opera": "订阅" 296 | }, 297 | { 298 | "name": "龚洋", 299 | "stamp": 1726291070000, 300 | "opera": "订阅" 301 | }, 302 | { 303 | "name": "彭平", 304 | "stamp": 1725145022000, 305 | "opera": "控制设备" 306 | }, 307 | { 308 | "name": "朱磊", 309 | "stamp": 1725361690000, 310 | "opera": "订阅" 311 | }, 312 | { 313 | "name": "郝强", 314 | "stamp": 1725701396000, 315 | "opera": "订阅" 316 | }, 317 | { 318 | "name": "邵刚", 319 | "stamp": 1725718278000, 320 | "opera": "播放失败" 321 | }, 322 | { 323 | "name": "秦洋", 324 | "stamp": 1725787758000, 325 | "opera": "登录" 326 | }, 327 | { 328 | "name": "姜秀英", 329 | "stamp": 1725831562000, 330 | "opera": "回放视频" 331 | }, 332 | { 333 | "name": "崔强", 334 | "stamp": 1725852746000, 335 | "opera": "播放失败" 336 | }, 337 | { 338 | "name": "龚明", 339 | "stamp": 1726175590000, 340 | "opera": "控制设备" 341 | }, 342 | { 343 | "name": "侯秀英", 344 | "stamp": 1726476426000, 345 | "opera": "退出" 346 | }, 347 | { 348 | "name": "龚洋", 349 | "stamp": 1726603778000, 350 | "opera": "退出" 351 | }, 352 | { 353 | "name": "彭平", 354 | "stamp": 1725252888000, 355 | "opera": "回放视频" 356 | }, 357 | { 358 | "name": "朱磊", 359 | "stamp": 1725705216000, 360 | "opera": "订阅" 361 | }, 362 | { 363 | "name": "郝强", 364 | "stamp": 1725769194000, 365 | "opera": "回放视频" 366 | }, 367 | { 368 | "name": "邵刚", 369 | "stamp": 1725813086000, 370 | "opera": "订阅" 371 | }, 372 | { 373 | "name": "秦洋", 374 | "stamp": 1725854178000, 375 | "opera": "回放视频" 376 | }, 377 | { 378 | "name": "姜秀英", 379 | "stamp": 1725911414000, 380 | "opera": "订阅" 381 | }, 382 | { 383 | "name": "崔强", 384 | "stamp": 1725961334000, 385 | "opera": "退出" 386 | }, 387 | { 388 | "name": "龚明", 389 | "stamp": 1726265836000, 390 | "opera": "回放视频" 391 | }, 392 | { 393 | "name": "侯秀英", 394 | "stamp": 1726301986000, 395 | "opera": "回放视频" 396 | }, 397 | { 398 | "name": "龚洋", 399 | "stamp": 1726460456000, 400 | "opera": "退出" 401 | }, 402 | { 403 | "name": "彭平", 404 | "stamp": 1725177219000, 405 | "opera": "订阅" 406 | }, 407 | { 408 | "name": "朱磊", 409 | "stamp": 1725250961000, 410 | "opera": "控制设备" 411 | }, 412 | { 413 | "name": "郝强", 414 | "stamp": 1725281275000, 415 | "opera": "登录" 416 | }, 417 | { 418 | "name": "邵刚", 419 | "stamp": 1725365799000, 420 | "opera": "控制设备" 421 | }, 422 | { 423 | "name": "秦洋", 424 | "stamp": 1725367311000, 425 | "opera": "控制设备" 426 | }, 427 | { 428 | "name": "姜秀英", 429 | "stamp": 1725500271000, 430 | "opera": "订阅" 431 | }, 432 | { 433 | "name": "崔强", 434 | "stamp": 1725764301000, 435 | "opera": "播放失败" 436 | }, 437 | { 438 | "name": "龚明", 439 | "stamp": 1725827633000, 440 | "opera": "退出" 441 | }, 442 | { 443 | "name": "侯秀英", 444 | "stamp": 1725877635000, 445 | "opera": "订阅" 446 | }, 447 | { 448 | "name": "龚洋", 449 | "stamp": 1726578993000, 450 | "opera": "控制设备" 451 | }, 452 | { 453 | "name": "彭平", 454 | "stamp": 1725179026000, 455 | "opera": "退出" 456 | }, 457 | { 458 | "name": "朱磊", 459 | "stamp": 1725420826000, 460 | "opera": "退出" 461 | }, 462 | { 463 | "name": "郝强", 464 | "stamp": 1725488362000, 465 | "opera": "回放视频" 466 | }, 467 | { 468 | "name": "邵刚", 469 | "stamp": 1725701374000, 470 | "opera": "登录" 471 | }, 472 | { 473 | "name": "秦洋", 474 | "stamp": 1725771934000, 475 | "opera": "回放视频" 476 | }, 477 | { 478 | "name": "姜秀英", 479 | "stamp": 1725856636000, 480 | "opera": "订阅" 481 | }, 482 | { 483 | "name": "崔强", 484 | "stamp": 1725954242000, 485 | "opera": "播放失败" 486 | }, 487 | { 488 | "name": "龚明", 489 | "stamp": 1726028510000, 490 | "opera": "登录" 491 | }, 492 | { 493 | "name": "侯秀英", 494 | "stamp": 1726093994000, 495 | "opera": "订阅" 496 | }, 497 | { 498 | "name": "龚洋", 499 | "stamp": 1726107436000, 500 | "opera": "回放视频" 501 | } 502 | ] -------------------------------------------------------------------------------- /src/views/other/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /src/views/test/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/views/test2/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 149 | 150 | 159 | 160 | 203 | -------------------------------------------------------------------------------- /src/views/waterfall/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /vite-plugin-build-legacy.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import os from 'os' 3 | import fs from 'fs' 4 | const platform = os.platform() 5 | 6 | export default function VitePluginBuildLegacy() { 7 | return { 8 | name: 'vite-plugin-build-legacy', 9 | apply: 'build', 10 | generateBundle(_options, bundle) { 11 | let content = `| 文件名 | 大小 | \n| :---: | --- | \n` 12 | const [assets, chunks] = Object.values(bundle).reduce((r, i) => (r[i.type === 'asset' ? 0 : 1].push(i), r), [[], []]) 13 | assets.forEach(item => (content += `| ${item.fileName} | ${formatSize(item.source.length)} | \n`)) 14 | chunks.forEach(item => (content += `| ${item.fileName} | ${formatSize(item.code.length)} | \n`)) 15 | const totalSize = assets.reduce((total, item) => total + item.source.length, 0) + chunks.reduce((total, item) => total + item.code.length, 0) 16 | content += `| 总计 | ${formatSize(totalSize)}(仅构建资产) | \n` 17 | this.emitFile({ type: 'asset', fileName: 'assets.md', source: content }) 18 | this.emitFile({ 19 | type: 'asset', 20 | fileName: 'version.json', 21 | source: JSON.stringify(GeneratVersion(), null, 2), 22 | }) 23 | }, 24 | } 25 | } 26 | 27 | function formatSize(size) { 28 | if (size < 1024) { 29 | return size + 'B' 30 | } else if (size < 1024 * 1024) { 31 | return (size / 1024).toFixed(2) + 'KB' 32 | } else if (size < 1024 * 1024 * 1024) { 33 | return (size / (1024 * 1024)).toFixed(2) + 'MB' 34 | } else { 35 | return (size / (1024 * 1024 * 1024)).toFixed(2) + 'GB' 36 | } 37 | } 38 | 39 | export function GeneratVersion(assets_dir) { 40 | try { 41 | const commitId = execSync('git log -n1 --format=format:"%H"').toString().trim() 42 | const author = execSync('git log -n1 --format=format:"%an"').toString().trim() 43 | let branch 44 | 45 | try { 46 | branch = execSync('git symbolic-ref --short HEAD').toString().trim() 47 | } catch (error) { 48 | branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim() 49 | } 50 | const commitTime = execSync('git log -n1 --format=format:"%ad" --date=iso').toString().substring(0, 19) 51 | const content = execSync('git log -n1 --format=format:"%s"').toString().trim() 52 | 53 | const json = { platform, commitId, author, branch, commitTime, content } 54 | 55 | assets_dir && fs.writeFileSync(assets_dir, JSON.stringify(json, null, 2)) 56 | 57 | return json 58 | } catch (error) { 59 | return { msg: '获取版本信息失败', error: error.message } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('vite').UserConfig} */ 2 | import { defineConfig, loadEnv } from 'vite' 3 | import { resolve } from 'path' 4 | import { ViteEjsPlugin } from 'vite-plugin-ejs' 5 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 6 | import EslintPlugin from 'vite-plugin-eslint' 7 | import vue2Plugin from '@vitejs/plugin-vue2' 8 | import vue2JsxPlugin from '@vitejs/plugin-vue2-jsx' 9 | import { viteMockServe } from 'vite-plugin-mock' 10 | 11 | import VitePluginBuildLegacy, { GeneratVersion } from './vite-plugin-build-legacy' 12 | import { clearScreen, createColors } from './cli-helper' 13 | 14 | const colors = createColors() 15 | 16 | export default defineConfig(({ mode, command }) => { 17 | const env = loadEnv(mode, process.cwd()) 18 | 19 | const { VITE_APP_ENV } = env 20 | 21 | return { 22 | root: process.cwd(), 23 | // mode: 'development', 24 | base: VITE_APP_ENV === 'production' ? './' : '/', 25 | server: { 26 | port: 10001, 27 | host: true, 28 | cors: true, 29 | proxy: { 30 | [env.VITE_API_PREFIX]: { 31 | target: 'https://example:port', 32 | ws: false, 33 | changeOrigin: true, 34 | rewrite: path => path.replace(new RegExp(`^${env.VITE_API_PREFIX}`), ''), 35 | configure: proxy => { 36 | proxy.on('proxyReq', (_proxyReq, req, _res) => { 37 | clearScreen() 38 | console.log(colors.bgYellow(` ${req.method} `), colors.green(`${proxy.options.target}${req.url}`)) 39 | }) 40 | proxy.on('error', (_err, req, _res) => { 41 | console.log(colors.bgRed(`Error:${req.method} `), colors.green(`${proxy.options.target}${req.url}`)) 42 | }) 43 | }, 44 | }, 45 | }, 46 | }, 47 | build: { 48 | outDir: 'dist-examples', 49 | sourcemap: true, 50 | assetsDir: 'assets', 51 | rollupOptions: { 52 | output: { 53 | entryFileNames: 'assets/js/[name].[hash].js', 54 | assetFileNames: 'assets/[ext]/[name].[hash].[ext]', 55 | manualChunks: { 56 | lodash: ['lodash-es'], 57 | axios: ['axios'], 58 | 'vue-vendor': ['vue'], 59 | 'element-ui': ['element-ui'], 60 | }, 61 | chunkFileNames() { 62 | return `assets/js/[name].[hash].js` 63 | }, 64 | }, 65 | }, 66 | }, 67 | resolve: { 68 | alias: { 69 | '@': resolve(__dirname, 'src'), 70 | }, 71 | extensions: ['.vue', '.js', '.jsx', '.json'], 72 | }, 73 | define: { 74 | appVersion: JSON.stringify(GeneratVersion()), 75 | }, 76 | plugins: [ 77 | vue2Plugin(), 78 | vue2JsxPlugin(), 79 | ViteEjsPlugin({ title: 'vite-example' }), 80 | EslintPlugin({ 81 | cache: false, 82 | include: ['src/**/*.vue', 'src/**/*.js', 'src/**/*.jsx'], 83 | }), 84 | createSvgIconsPlugin({ 85 | iconDirs: [resolve(process.cwd(), 'src/assets/svg')], 86 | symbolId: 'icon-[dir]-[name]', 87 | svgoOptions: command === 'build', 88 | }), 89 | viteMockServe({ 90 | mockPath: resolve('__dirname', 'mock'), 91 | localEnabled: true, 92 | }), 93 | VitePluginBuildLegacy(), 94 | ], 95 | optimizeDeps: { 96 | include: ['vue', 'sass', 'axios', 'dayjs', 'lodash-es', 'element-ui', '@fullcalendar/*'], 97 | exclude: [], 98 | }, 99 | } 100 | }) 101 | -------------------------------------------------------------------------------- /vitejs + vue2测试目录: -------------------------------------------------------------------------------- 1 | 多条件的查询 2 | 3 | 需要排序 4 | 5 | 终端/摄像头 6 | 7 | 8 | 柱状图 9 | _____________________________________________ 10 | | | 11 | | | 12 | | ____ ____ | 13 | | | 92 | | 93 | | 14 | | | | | | | 15 | | | | ____ | | ____ | 16 | | | | | 47 | | | | 42 | | 17 | | | | | | | | | | | 18 | | | | | | | | | | | 19 | |__|____|_____|____|______|____|_____|____|__| 20 | user alice panda cat 21 | 22 | 日历图 23 | 24 | 25 | 26 | 雷达图 27 | --------------------------------------------------------------------------------