xuziqiang 6 months ago
commit
d0155dbe3c
  1. 19
      .editorconfig
  2. 8
      .env
  3. 22
      .env.development
  4. 35
      .env.production
  5. 15
      .eslintignore
  6. 74
      .eslintrc.js
  7. 37
      .gitignore
  8. 5
      .prettierignore
  9. 3
      .stylelintignore
  10. 21
      LICENSE
  11. 28
      README.md
  12. 2
      build/build-zip.ts
  13. 28
      build/less.ts
  14. 103
      build/plugin/mock/client.ts
  15. 255
      build/plugin/mock/createMockServer.ts
  16. 30
      build/plugin/mock/index.ts
  17. 9
      build/plugin/mock/mock.ts
  18. 38
      build/plugin/mock/types.ts
  19. 40
      build/plugin/mock/utils.ts
  20. 224
      build/plugin/theme/antdDarkThemePlugin.ts
  21. 46
      build/plugin/theme/constants.ts
  22. 39
      build/plugin/theme/esbuild/less.ts
  23. 199
      build/plugin/theme/index.ts
  24. 113
      build/plugin/theme/injectClientPlugin.ts
  25. 174
      build/plugin/theme/preprocessor/less/index.ts
  26. 128
      build/plugin/theme/utils.ts
  27. 35
      build/theme.ts
  28. 75
      build/themeConfig.ts
  29. 165
      build/vite-default.config.ts
  30. 164
      build/vite-low-version.config.ts
  31. 228
      build/vite-saas.config.ts
  32. 23
      gulpfile.js
  33. 13
      index.html
  34. 4
      lib/component/VNode/index.ts
  35. 13
      lib/component/VNode/v-node.ts
  36. 288
      lib/component/color/color-picker.vue
  37. 138
      lib/component/color/common/Alpha.vue
  38. 92
      lib/component/color/common/Checkboard.vue
  39. 114
      lib/component/color/common/EditableInput.vue
  40. 205
      lib/component/color/common/Hue.vue
  41. 132
      lib/component/color/common/Saturation.vue
  42. 4
      lib/component/color/index.ts
  43. 117
      lib/component/color/mixin/color.ts
  44. 0
      lib/component/echarts/components/chart/Base.vue
  45. 43
      lib/component/echarts/components/chart/bar/BaseBar.vue
  46. 255
      lib/component/echarts/components/chart/bar/MixedLineBar.vue
  47. 223
      lib/component/echarts/components/chart/bar/MultipleBar.vue
  48. 236
      lib/component/echarts/components/chart/bar/SingleBar.vue
  49. 270
      lib/component/echarts/components/chart/bar/StackedHorizontalBar.vue
  50. 26
      lib/component/echarts/components/chart/chartData.json
  51. 43
      lib/component/echarts/components/chart/gauge/BaseGauge.vue
  52. 309
      lib/component/echarts/components/chart/gauge/SimpleGauge.vue
  53. 398
      lib/component/echarts/components/chart/gauge/index.vue
  54. 43
      lib/component/echarts/components/chart/line/BaseLine.vue
  55. 222
      lib/component/echarts/components/chart/line/LineStacked.vue
  56. 248
      lib/component/echarts/components/chart/line/LineStackedArea.vue
  57. 43
      lib/component/echarts/components/chart/pie/BasePie.vue
  58. 219
      lib/component/echarts/components/chart/pie/PieLabelLineAlign.vue
  59. 43
      lib/component/echarts/components/chart/radar/BaseRadar.vue
  60. 141
      lib/component/echarts/components/chart/radar/CustomizedRadar.vue
  61. 89
      lib/component/echarts/components/chart/tool.js
  62. 293
      lib/component/echarts/components/other/BasicTable copy 2.vue
  63. 88
      lib/component/echarts/components/other/BasicTable copy.vue
  64. 368
      lib/component/echarts/components/other/BasicTable-old.vue
  65. 430
      lib/component/echarts/components/other/BasicTable.vue
  66. 78
      lib/component/echarts/echarts.vue
  67. 71
      lib/component/flowChart/flow-chart.vue
  68. 17
      lib/component/form/button/button.vue
  69. 4
      lib/component/form/button/index.ts
  70. 31
      lib/component/form/cascader/cascader.vue
  71. 5
      lib/component/form/cascader/index.ts
  72. 180
      lib/component/form/checkbox/checkbox-allgroup.vue
  73. 14
      lib/component/form/checkbox/checkbox-group.vue
  74. 12
      lib/component/form/checkbox/checkbox.vue
  75. 11
      lib/component/form/checkbox/index.ts
  76. 1188
      lib/component/form/date-picker/cron-antd.vue
  77. 13
      lib/component/form/date-picker/date-picker.vue
  78. 21
      lib/component/form/date-picker/index.ts
  79. 11
      lib/component/form/date-picker/month-picker.vue
  80. 11
      lib/component/form/date-picker/range-picker.vue
  81. 74
      lib/component/form/date-picker/v-cron.vue
  82. 11
      lib/component/form/date-picker/week-picker.vue
  83. 11
      lib/component/form/date-picker/year-picker.vue
  84. 176
      lib/component/form/disk-combination/disk-combination.vue
  85. 7
      lib/component/form/disk-combination/index.ts
  86. 264
      lib/component/form/editTable/custom-edit-table.vue
  87. 44
      lib/component/form/editTable/edit-table.vue
  88. 12
      lib/component/form/editTable/index.ts
  89. 679
      lib/component/form/etable/etable.vue
  90. 4
      lib/component/form/etable/index.ts
  91. 308
      lib/component/form/etable/modal-table.vue
  92. 323
      lib/component/form/etable/uplods.vue
  93. 27
      lib/component/form/field-registry.ts
  94. 51
      lib/component/form/filter-table/filter-table.vue
  95. 7
      lib/component/form/filter-table/index.ts
  96. 58
      lib/component/form/form-util.ts
  97. 122
      lib/component/form/form/child-form.vue
  98. 335
      lib/component/form/form/form-item.vue
  99. 37
      lib/component/form/form/form.d.ts
  100. 338
      lib/component/form/form/form.vue

19
.editorconfig

@ -0,0 +1,19 @@
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
max_line_length = 100
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

8
.env

@ -0,0 +1,8 @@
# port
VITE_PORT = 3400
# spa-title
VITE_GLOB_APP_TITLE = SaaS Lib
# spa shortname
VITE_GLOB_APP_SHORT_NAME = saas_lib

22
.env.development

@ -0,0 +1,22 @@
# Whether to open mock
VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = {"/api":{ "target":"http://100.73.70.51","changeOrigin": true}}
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console
VITE_DROP_CONSOLE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# File upload address, optional
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=

35
.env.production

@ -0,0 +1,35 @@
# Whether to open mock
VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Delete console
VITE_DROP_CONSOLE = true
# Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate
VITE_BUILD_COMPRESS = 'none'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# File upload address, optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false

15
.eslintignore

@ -0,0 +1,15 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

74
.eslintrc.js

@ -0,0 +1,74 @@
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2018,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
],
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
// 'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
},
});

37
.gitignore

@ -0,0 +1,37 @@
.DS_Store
node_modules
dist
release
# local env files
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#project
nervui-intelligent-pension-cloud/src
nervui-intelligent-pension-cloud/public
nervui-intelligent-pension-op/src
nervui-intelligent-pension-op/public
nervui-attendance-dingcloud/src
nervui-attendance-dingcloud/public
yarn.lock
package-lock.json

5
.prettierignore

@ -0,0 +1,5 @@
/dist/*
/node_modules/**
**/*.svg
**/*.sh
/public/*

3
.stylelintignore

@ -0,0 +1,3 @@
/dist/*
/public/*
public/*

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present, Nerv-SAAS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

28
README.md

@ -0,0 +1,28 @@
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur). Make sure to enable `vetur.experimental.templateInterpolationService` in settings!
### If Using `<script setup>`
[`<script setup>`](https://github.com/vuejs/rfcs/pull/227) is a feature that is currently in RFC stage. To get proper IDE support for the syntax, use [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) instead of Vetur (and disable Vetur).
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can use the following:
### If Using Volar
Run `Volar: Switch TS Plugin on/off` from VSCode command palette.
### If Using Vetur
1. Install and add `@vuedx/typescript-plugin-vue` to the [plugins section](https://www.typescriptlang.org/tsconfig#plugins) in `tsconfig.json`
2. Delete `src/shims-vue.d.ts` as it is no longer needed to provide module info to Typescript
3. Open `src/main.ts` in VSCode
4. Open the VSCode command palette
5. Search and run "Select TypeScript version" -> "Use workspace version"
test

2
build/build-zip.ts

@ -0,0 +1,2 @@
const build = async () => {};
build();

28
build/less.ts

@ -0,0 +1,28 @@
import { getThemeVariables } from 'ant-design-vue/dist/theme';
import lessToJs from 'less-vars-to-js';
import fs from 'fs';
function getVariablesByPath(paths: string[]) {
let modifyVars = {};
paths.forEach((path) => {
const paletteLess = fs.readFileSync(path, 'utf8');
modifyVars = Object.assign(
modifyVars,
lessToJs(paletteLess, { stripPrefix: true, resolveVariables: false })
);
});
return modifyVars;
}
export function generateModifyVars({
dark = false,
paths = [],
}: {
dark: boolean;
paths: string[];
}) {
const modifyVars = getThemeVariables({ dark });
return {
...modifyVars,
...getVariablesByPath(paths),
};
}

103
build/plugin/mock/client.ts

@ -0,0 +1,103 @@
/* eslint-disable */
import type { MockMethod } from './types';
export async function createProdMockServer(mockList: any[]) {
const Mock: any = await import('mockjs');
const { pathToRegexp } = await import('path-to-regexp');
Mock.XHR.prototype.__send = Mock.XHR.prototype.send;
Mock.XHR.prototype.send = function () {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false;
if (this.responseType) {
this.custom.xhr.responseType = this.responseType;
}
}
if (this.custom.requestHeaders) {
const headers: any = {};
for (let k in this.custom.requestHeaders) {
headers[k.toString().toLowerCase()] = this.custom.requestHeaders[k];
}
this.custom.options = Object.assign({}, this.custom.options, { headers });
}
this.__send.apply(this, arguments);
};
Mock.XHR.prototype.proxy_open = Mock.XHR.prototype.open;
Mock.XHR.prototype.open = function () {
let responseType = this.responseType;
this.proxy_open(...arguments);
if (this.custom.xhr) {
if (responseType) {
this.custom.xhr.responseType = responseType;
}
}
};
for (const { url, method, response, timeout } of mockList) {
__setupMock__(Mock, timeout);
Mock.mock(
pathToRegexp(url, undefined, { end: false }),
method || 'get',
__XHR2ExpressReqWrapper__(Mock, response),
);
}
}
function __param2Obj__(url: string) {
const search = url.split('?')[1];
if (!search) {
return {};
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"')
.replace(/\+/g, ' ') +
'"}',
);
}
function __XHR2ExpressReqWrapper__(_Mock: any, handle: (d: any) => any) {
return function (options: any) {
let result = null;
if (typeof handle === 'function') {
const { body, type, url, headers } = options;
let b = body;
try {
b = JSON.parse(body);
} catch {}
result = handle({
method: type,
body: b,
query: __param2Obj__(url),
headers,
});
} else {
result = handle;
}
return _Mock.mock(result);
};
}
function __setupMock__(mock: any, timeout = 0) {
timeout &&
mock.setup({
timeout,
});
}
export function defineMockModule(
fn: (config: {
env: Record<string, any>;
mode: string;
command: 'build' | 'serve';
}) => Promise<MockMethod[]> | MockMethod[],
) {
return fn;
}

255
build/plugin/mock/createMockServer.ts

@ -0,0 +1,255 @@
import type { ViteMockOptions, MockMethod, Recordable, RespThisType } from './types';
import path from 'node:path';
import fs from 'node:fs';
import chokidar from 'chokidar';
import colors from 'picocolors';
import url from 'url';
import fg from 'fast-glob';
import Mock from 'mockjs';
import { pathToRegexp, match } from 'path-to-regexp';
import { isArray, isFunction, sleep, isRegExp, isAbsPath } from './utils';
import { IncomingMessage, NextHandleFunction } from 'connect';
import { bundleRequire } from 'bundle-require';
import type { ResolvedConfig } from 'vite';
export let mockData: MockMethod[] = [];
export async function createMockServer(
opt: ViteMockOptions = { mockPath: 'mock', configPath: 'vite.mock.config' },
config: ResolvedConfig,
) {
opt = {
mockPath: 'mock',
watchFiles: true,
configPath: 'vite.mock.config.ts',
logger: true,
...opt,
};
if (mockData.length > 0) return;
mockData = await getMockConfig(opt, config);
await createWatch(opt, config);
}
// request match
export async function requestMiddleware(opt: ViteMockOptions) {
const { logger = true } = opt;
const middleware: NextHandleFunction = async (req, res, next) => {
let queryParams: {
query?: {
[key: string]: any;
};
pathname?: string | null;
} = {};
if (req.url) {
queryParams = url.parse(req.url, true);
}
const reqUrl = queryParams.pathname;
const matchRequest = mockData.find((item) => {
if (!reqUrl || !item || !item.url) {
return false;
}
if (item.method && item.method.toUpperCase() !== req.method) {
return false;
}
return pathToRegexp(item.url).test(reqUrl);
});
if (matchRequest) {
const isGet = req.method && req.method.toUpperCase() === 'GET';
const { response, rawResponse, timeout, statusCode, url } = matchRequest;
if (timeout) {
await sleep(timeout);
}
const urlMatch = match(url, { decode: decodeURIComponent });
let query = queryParams.query as any;
if (reqUrl) {
if ((isGet && JSON.stringify(query) === '{}') || !isGet) {
const params = (urlMatch(reqUrl) as any).params;
if (JSON.stringify(params) !== '{}') {
query = (urlMatch(reqUrl) as any).params || {};
} else {
query = queryParams.query || {};
}
}
}
const self: RespThisType = { req, res, parseJson: parseJson.bind(null, req) };
if (isFunction(rawResponse)) {
await rawResponse.bind(self)(req, res);
} else {
const body = await parseJson(req);
res.setHeader('Content-Type', 'application/json');
res.statusCode = statusCode || 200;
const mockResponse = isFunction(response)
? response.bind(self)({ url: req.url as any, body, query, headers: req.headers })
: response;
res.end(JSON.stringify(Mock.mock(mockResponse)));
}
logger && loggerOutput('request invoke', req.url!);
return;
}
next();
};
return middleware;
}
// create watch mock
function createWatch(opt: ViteMockOptions, config: ResolvedConfig) {
const { configPath, logger, watchFiles } = opt;
if (!watchFiles) {
return;
}
const { absConfigPath, absMockPath } = getPath(opt);
if (process.env.VITE_DISABLED_WATCH_MOCK === 'true') {
return;
}
const watchDir = [];
const exitsConfigPath = fs.existsSync(absConfigPath);
exitsConfigPath && configPath ? watchDir.push(absConfigPath) : watchDir.push(absMockPath);
const watcher = chokidar.watch(watchDir, {
ignored: opt.ignore || /.mjs$/,
ignoreInitial: true,
});
watcher.on('all', async (event, file) => {
logger && loggerOutput(`mock file ${event}`, file);
mockData = await getMockConfig(opt, config);
});
}
// clear cache
function cleanRequireCache(opt: ViteMockOptions) {
if (!require.cache) {
return;
}
const { absConfigPath, absMockPath } = getPath(opt);
Object.keys(require.cache).forEach((file) => {
if (file === absConfigPath || file.indexOf(absMockPath) > -1) {
delete require.cache[file];
}
});
}
function parseJson(req: IncomingMessage): Promise<Recordable> {
return new Promise((resolve) => {
let body = '';
let jsonStr = '';
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
try {
jsonStr = JSON.parse(body);
} catch (err) {
jsonStr = '';
}
resolve(jsonStr as any);
return;
});
});
}
// load mock .ts files and watch
async function getMockConfig(opt: ViteMockOptions, config: ResolvedConfig) {
cleanRequireCache(opt);
const { absConfigPath, absMockPath } = getPath(opt);
const { ignore, configPath, logger } = opt;
let ret: MockMethod[] = [];
if (configPath && fs.existsSync(absConfigPath)) {
logger && loggerOutput(`load mock data from`, absConfigPath);
ret = await resolveModule(absConfigPath, config);
return ret;
}
const mockFiles = fg
.sync(`**/*.{ts,mjs,js}`, {
cwd: absMockPath,
})
.filter((item) => {
if (!ignore) {
return true;
}
if (isFunction(ignore)) {
return ignore(item);
}
if (isRegExp(ignore)) {
return !ignore.test(path.basename(item));
}
return true;
});
try {
ret = [];
const resolveModulePromiseList = [];
for (let index = 0; index < mockFiles.length; index++) {
const mockFile = mockFiles[index];
resolveModulePromiseList.push(resolveModule(path.join(absMockPath, mockFile), config));
}
const loadAllResult = await Promise.all(resolveModulePromiseList);
for (const resultModule of loadAllResult) {
let mod = resultModule;
if (!isArray(mod)) {
mod = [mod];
}
ret = [...ret, ...mod];
}
} catch (error: any) {
loggerOutput(`mock reload error`, error);
ret = [];
}
return ret;
}
// Inspired by vite
// support mock .ts files
async function resolveModule(p: string, config: ResolvedConfig): Promise<any> {
const mockData = await bundleRequire({
filepath: p,
});
let mod = mockData.mod.default || mockData.mod;
if (isFunction(mod)) {
mod = await mod({ env: config.env, mode: config.mode, command: config.command });
}
return mod;
}
// get custom config file path and mock dir path
function getPath(opt: ViteMockOptions) {
const { mockPath, configPath } = opt;
const cwd = process.cwd();
const absMockPath = isAbsPath(mockPath) ? mockPath! : path.join(cwd, mockPath || '');
const absConfigPath = path.join(cwd, configPath || '');
return {
absMockPath,
absConfigPath,
};
}
function loggerOutput(title: string, msg: string, type: 'info' | 'error' = 'info') {
const tag = type === 'info' ? colors.cyan(`[vite:mock]`) : colors.red(`[vite:mock-server]`);
return console.log(
`${colors.dim(new Date().toLocaleTimeString())} ${tag} ${colors.green(title)} ${colors.dim(
msg,
)}`,
);
}

30
build/plugin/mock/index.ts

@ -0,0 +1,30 @@
import type { ViteMockOptions } from './types';
import type { Plugin } from 'vite';
import { ResolvedConfig } from 'vite';
import { createMockServer, requestMiddleware } from './createMockServer';
export function viteMockServe(opt: ViteMockOptions = {}): Plugin {
let isDev = false;
let config: ResolvedConfig;
return {
name: 'vite:mock',
enforce: 'pre' as const,
configResolved(resolvedConfig) {
config = resolvedConfig;
isDev = config.command === 'serve';
isDev && createMockServer(opt, config);
},
configureServer: async ({ middlewares }) => {
const { enable = isDev } = opt;
if (!enable) {
return;
}
const middleware = await requestMiddleware(opt);
middlewares.use(middleware);
},
};
}
export * from './types';

9
build/plugin/mock/mock.ts

@ -0,0 +1,9 @@
import { viteMockServe } from './index';
export function configMockPlugin({ enable, mockPath }: { enable: boolean; mockPath: string }) {
return viteMockServe({
// ignore: /.mjs$/,
mockPath: mockPath,
enable,
});
}

38
build/plugin/mock/types.ts

@ -0,0 +1,38 @@
import { IncomingMessage, ServerResponse } from 'http';
export interface ViteMockOptions {
mockPath?: string;
configPath?: string;
ignore?: RegExp | ((fileName: string) => boolean);
watchFiles?: boolean;
enable?: boolean;
logger?: boolean;
}
export interface RespThisType {
req: IncomingMessage;
res: ServerResponse;
parseJson: () => any;
}
export type MethodType = 'get' | 'post' | 'put' | 'delete' | 'patch';
export type Recordable<T = any> = Record<string, T>;
export declare interface MockMethod {
url: string;
method?: MethodType;
timeout?: number;
statusCode?: number;
response?: (
this: RespThisType,
opt: { url: Recordable; body: Recordable; query: Recordable; headers: Recordable },
) => any;
rawResponse?: (this: RespThisType, req: IncomingMessage, res: ServerResponse) => void;
}
export interface MockConfig {
env: Record<string, any>;
mode: string;
command: 'build' | 'serve';
}

40
build/plugin/mock/utils.ts

@ -0,0 +1,40 @@
import fs from 'fs';
const toString = Object.prototype.toString;
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`;
}
// eslint-disable-next-line
export function isFunction<T = Function>(val: unknown): val is T {
return is(val, 'Function') || is(val, 'AsyncFunction');
}
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp');
}
export function isAbsPath(path: string | undefined) {
if (!path) {
return false;
}
// Windows 路径格式:C:\ 或 \\ 开头,或已含盘符(D:\path\to\file)
if (/^([a-zA-Z]:\\|\\\\|(?:\/|\uFF0F){2,})/.test(path)) {
return true;
}
// Unix/Linux 路径格式:/ 开头
return /^\/[^/]/.test(path);
}
export function sleep(time: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('');
}, time);
});
}

224
build/plugin/theme/antdDarkThemePlugin.ts

@ -0,0 +1,224 @@
import type { Plugin, ResolvedConfig } from 'vite';
import path from 'path';
import fs from 'fs-extra';
import less from 'less';
import { createFileHash, minifyCSS, extractVariable } from './utils';
import chalk from 'chalk';
import { colorRE, linkID } from './constants';
import { injectClientPlugin } from './injectClientPlugin';
import { lessPlugin } from './preprocessor/less';
export interface AntdDarkThemeOption {
darkModifyVars?: any;
fileName?: string;
verbose?: boolean;
selector?: string;
filter?: (id: string) => boolean;
extractCss?: boolean;
preloadFiles?: string[];
loadMethod?: 'link' | 'ajax';
}
export function antdDarkThemePlugin(options: AntdDarkThemeOption): Plugin[] {
const {
darkModifyVars,
verbose = true,
fileName = 'app-antd-dark-theme-style',
selector,
filter,
extractCss = true,
preloadFiles = [],
loadMethod = 'link',
} = options;
let isServer = false;
let needSourcemap = false;
let config: ResolvedConfig;
let extCssString = '';
const styleMap = new Map<string, string>();
const codeCache = new Map<string, { code: string; css: string }>();
const cssOutputName = `${fileName}.${createFileHash()}.css`;
const hrefProtocals = [ 'http://' ];
const getCss = (css: string) => {
return `[${selector || 'data-theme="dark"'}] {${css}}`;
};
async function preloadLess() {
if (!preloadFiles || !preloadFiles.length) {
return;
}
for (const id of preloadFiles) {
const code = fs.readFileSync(id, 'utf-8');
less
.render(code, {
javascriptEnabled: true,
modifyVars: darkModifyVars,
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
})
.then(({ css }) => {
const colors = css.match(colorRE);
if (colors) {
css = extractVariable(css, colors.concat(['transparent']));
codeCache.set(id, { code, css });
}
});
}
}
function getProtocal(path): string | undefined {
let protocal:string | undefined;
hrefProtocals.forEach(hrefProtocal => {
if(path.startsWith(hrefProtocal)){
protocal = hrefProtocal;
}
})
return protocal;
}
return [
injectClientPlugin('antdDarkPlugin', {
antdDarkCssOutputName: cssOutputName,
antdDarkExtractCss: extractCss,
antdDarkLoadLink: loadMethod === 'link',
}),
{
name: 'vite:antd-dark-theme',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
needSourcemap = !!resolvedConfig.build.sourcemap;
isServer && preloadLess();
},
transformIndexHtml(html) {
let href;
const protocal = getProtocal(config.base);
if (isServer || loadMethod !== 'link') {
return html;
}
if(protocal) {
href = protocal + path.posix.join(config.base.slice(protocal.length), config.build.assetsDir, cssOutputName);
}
else {
href = path.posix.join(config.base, config.build.assetsDir, cssOutputName)
}
return {
html,
tags: [
{
tag: 'link',
attrs: {
disabled: true,
id: linkID,
rel: 'alternate stylesheet',
href: href,
},
injectTo: 'head',
},
],
};
},
async transform(code, id) {
if (!id.endsWith('.less') || !code.includes('@')) {
return null;
}
if (typeof filter === 'function' && !filter(id)) {
return null;
}
const getResult = (content: string) => {
return {
map: needSourcemap ? this.getCombinedSourcemap() : null,
code: content,
};
};
let processCss = '';
const cache = codeCache.get(id);
const isUpdate = !cache || cache.code !== code;
if (isUpdate) {
const { css } = await less.render(code, {
javascriptEnabled: true,
modifyVars: darkModifyVars,
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
});
const colors = css.match(colorRE);
if (colors) {
// The theme only extracts css related to color
// Can effectively reduce the size
processCss = extractVariable(css, colors.concat(['transparent']));
}
} else {
processCss = cache!.css;
}
if (isServer || !extractCss) {
isUpdate && codeCache.set(id, { code, css: processCss });
return getResult(`${getCss(processCss)}\n` + code);
} else {
if (!styleMap.has(id)) {
const { css } = await less.render(getCss(processCss), {
filename: path.resolve(id),
plugins: [lessPlugin(id, config)],
});
extCssString += `${css}\n`;
}
styleMap.set(id, processCss);
}
return null;
},
async writeBundle() {
if (!extractCss) {
return;
}
const {
root,
build: { outDir, assetsDir, minify },
} = config;
if (minify) {
extCssString = await minifyCSS(extCssString, config);
}
const cssOutputPath = path.resolve(root, outDir, assetsDir, cssOutputName);
fs.writeFileSync(cssOutputPath, extCssString);
},
closeBundle() {
if (verbose && !isServer && extractCss) {
const {
build: { outDir, assetsDir },
} = config;
console.log(
chalk.cyan('\n✨ [vite-plugin-theme:antd-dark]') +
` - extract antd dark css code file is successfully:`
);
try {
const { size } = fs.statSync(path.join(outDir, assetsDir, cssOutputName));
console.log(
chalk.dim(outDir + '/') +
chalk.magentaBright(`${assetsDir}/${cssOutputName}`) +
`\t\t${chalk.dim((size / 1024).toFixed(2) + 'kb')}` +
'\n'
);
} catch (error) {}
}
},
},
];
}

46
build/plugin/theme/constants.ts

@ -0,0 +1,46 @@
import path, { resolve } from 'path';
import { normalizePath } from 'vite';
import { existsSync } from 'node:fs';
export const VITE_CLIENT_ENTRY = '/@vite/client';
let clientPath = process.cwd();
if (
!existsSync(
normalizePath(path.resolve(clientPath, 'node_modules/vite-plugin-theme/es//client.js')),
)
) {
clientPath = resolve(process.cwd(), '../');
console.log('子目录运行');
}
export const VITE_PLUGIN_THEME_CLIENT_ENTRY = normalizePath(
path.resolve(clientPath, 'node_modules/vite-plugin-theme/es/'),
);
export const CLIENT_PUBLIC_ABSOLUTE_PATH = normalizePath(
VITE_PLUGIN_THEME_CLIENT_ENTRY + '/client.js',
);
export const CLIENT_PUBLIC_PATH = `/${VITE_PLUGIN_THEME_CLIENT_ENTRY}/client.js`;
export const commentRE = /\\\\?n|\n|\\\\?r|\/\*[\s\S]+?\*\//g;
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|postcss)($|\\?)`;
export const colorRE =
/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|rgba?\((.*),\s*(.*),\s*(.*)(?:,\s*(.*(?:.*)?))?\)/gi;
export const cssVariableString = `const css = "`;
export const cssBlockRE = /[^}]*\{[^{]*\}/g;
export const cssLangRE = new RegExp(cssLangs);
export const ruleRE = /(\w+-)*\w+:/;
export const cssValueRE = /(\s?[a-z0-9]+\s)*/;
export const safeEmptyRE = /\s?/;
export const importSafeRE = /(\s*!important)?/;
export const linkID = '__VITE_PLUGIN_THEME-ANTD_DARK_THEME_LINK__';

39
build/plugin/theme/esbuild/less.ts

@ -0,0 +1,39 @@
import path from 'path';
import { Plugin } from 'esbuild';
import less from 'less';
/** Less-loader for esbuild */
export const lessLoader = (content, options: Less.Options = {}): Plugin => {
return {
name: 'less-loader',
setup: (build) => {
build.onResolve({ filter: /\.less$/, namespace: 'file' }, (args) => {
const filePath = path.resolve(
process.cwd(),
path.relative(process.cwd(), args.resolveDir),
args.path
);
return {
path: filePath,
};
});
// Build .less files
build.onLoad({ filter: /\.less$/, namespace: 'file' }, async (args) => {
const dir = path.dirname(args.path);
try {
const result = await less.render(content, {
...options,
paths: [...(options.paths || []), dir],
});
return {
contents: result.css,
loader: 'css',
resolveDir: dir,
};
} catch (e) {}
});
},
};
};

199
build/plugin/theme/index.ts

@ -0,0 +1,199 @@
import { Plugin, ResolvedConfig } from 'vite';
import path from 'path';
import fs from 'fs-extra';
import { debug as Debug } from 'debug';
import { extractVariable, minifyCSS } from './utils';
// export * from '../client/colorUtils';
export { antdDarkThemePlugin } from './antdDarkThemePlugin';
import { VITE_CLIENT_ENTRY, cssLangRE, cssVariableString, CLIENT_PUBLIC_PATH } from './constants';
export type ResolveSelector = (selector: string) => string;
export type InjectTo = 'head' | 'body' | 'body-prepend';
export interface ViteThemeOptions {
colorVariables: string[];
wrapperCssSelector?: string;
resolveSelector?: ResolveSelector;
customerExtractVariable?: (code: string) => string;
fileName?: string;
injectTo?: InjectTo;
verbose?: boolean;
isProd: boolean; // 必须传递环境标识
}
import { createFileHash, formatCss } from './utils';
import chalk from 'chalk';
import { injectClientPlugin } from './injectClientPlugin';
const debug = Debug('vite-plugin-theme');
export function viteThemePlugin(opt: ViteThemeOptions): Plugin[] {
let isServer = false;
let config: ResolvedConfig;
let clientPath = '';
const styleMap = new Map<string, string>();
let extCssSet = new Set<string>();
const emptyPlugin: Plugin = {
name: 'vite:theme',
};
const options: ViteThemeOptions = Object.assign(
{
colorVariables: [],
wrapperCssSelector: '',
fileName: 'app-theme-style',
injectTo: 'body',
verbose: true,
isProd: true, // 默认为 true,切换主题只在生产环境生效。
},
opt,
);
debug('plugin options:', options);
const {
colorVariables,
wrapperCssSelector,
resolveSelector,
customerExtractVariable,
fileName,
verbose,
} = options;
if (!colorVariables || colorVariables.length === 0) {
console.error('colorVariables is not empty!');
return [emptyPlugin];
}
const resolveSelectorFn = resolveSelector || ((s: string) => `${wrapperCssSelector} ${s}`);
const cssOutputName = `${fileName}.${createFileHash()}.css`;
let needSourcemap = false;
console.log('options.isProd', options.isProd);
return [
injectClientPlugin('colorPlugin', {
colorPluginCssOutputName: cssOutputName,
colorPluginOptions: options,
}),
{
...emptyPlugin,
enforce: options.isProd ? undefined : 'post', // 生产环境不设置 enforce;开发环境设置为 post,切换主题才会都生效。
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
clientPath = JSON.stringify(path.posix.join(config.base, CLIENT_PUBLIC_PATH));
needSourcemap = !!resolvedConfig.build.sourcemap;
debug('plugin config:', resolvedConfig);
},
async transform(code, id) {
if (!cssLangRE.test(id)) {
return null;
}
const getResult = (content: string) => {
return {
map: needSourcemap ? this.getCombinedSourcemap() : null,
code: content,
};
};
const clientCode = isServer
? await getClientStyleString(code)
: code.replace('export default', '').replace('"', '');
// Used to extract the relevant color configuration in css, you can pass in the function to override
const extractCssCodeTemplate =
typeof customerExtractVariable === 'function'
? customerExtractVariable(clientCode)
: extractVariable(clientCode, colorVariables, resolveSelectorFn);
debug('extractCssCodeTemplate:', id, extractCssCodeTemplate);
if (!extractCssCodeTemplate) {
return null;
}
// dev-server
if (isServer) {
const retCode = [
`import { addCssToQueue } from ${clientPath}`,
`const themeCssId = ${JSON.stringify(id)}`,
`const themeCssStr = ${JSON.stringify(formatCss(extractCssCodeTemplate))}`,
`addCssToQueue(themeCssId, themeCssStr)`,
code,
];
return getResult(retCode.join('\n'));
} else {
if (!styleMap.has(id)) {
extCssSet.add(extractCssCodeTemplate);
}
styleMap.set(id, extractCssCodeTemplate);
}
return null;
},
async writeBundle() {
const {
root,
build: { outDir, assetsDir, minify },
} = config;
let extCssString = '';
for (const css of extCssSet) {
extCssString += css;
}
if (minify) {
extCssString = await minifyCSS(extCssString, config);
}
const cssOutputPath = path.resolve(root, outDir, assetsDir, cssOutputName);
fs.writeFileSync(cssOutputPath, extCssString);
},
closeBundle() {
if (verbose && !isServer) {
const {
build: { outDir, assetsDir },
} = config;
console.log(
chalk.cyan('\n✨ [vite-plugin-theme]') + ` - extract css code file is successfully:`,
);
try {
const { size } = fs.statSync(path.join(outDir, assetsDir, cssOutputName));
console.log(
chalk.dim(outDir + '/') +
chalk.magentaBright(`${assetsDir}/${cssOutputName}`) +
`\t\t${chalk.dim((size / 1024).toFixed(2) + 'kb')}` +
'\n',
);
} catch (error) {}
}
},
},
];
}
// Intercept the css code embedded in js
async function getClientStyleString(code: string) {
if (!code.includes(VITE_CLIENT_ENTRY)) {
return code;
}
code = code.replace(/\\n/g, '');
const cssPrefix = cssVariableString;
const cssPrefixLen = cssPrefix.length;
const cssPrefixIndex = code.indexOf(cssPrefix);
const len = cssPrefixIndex + cssPrefixLen;
const cssLastIndex = code.indexOf('\n', len + 1);
if (cssPrefixIndex !== -1) {
code = code.slice(len, cssLastIndex);
}
return code;
}

113
build/plugin/theme/injectClientPlugin.ts

@ -0,0 +1,113 @@
import path from 'path';
import { ResolvedConfig, normalizePath, Plugin } from 'vite';
import { ViteThemeOptions } from '.';
import { CLIENT_PUBLIC_PATH, CLIENT_PUBLIC_ABSOLUTE_PATH } from './constants';
import { debug as Debug } from 'debug';
const debug = Debug('vite:inject-vite-plugin-theme-client');
type PluginType = 'colorPlugin' | 'antdDarkPlugin';
export function injectClientPlugin(
type: PluginType,
{
colorPluginOptions,
colorPluginCssOutputName,
antdDarkCssOutputName,
antdDarkExtractCss,
antdDarkLoadLink,
}: {
colorPluginOptions?: ViteThemeOptions;
antdDarkCssOutputName?: string;
colorPluginCssOutputName?: string;
antdDarkExtractCss?: boolean;
antdDarkLoadLink?: boolean;
}
): Plugin {
let config: ResolvedConfig;
let isServer: boolean;
let needSourcemap = false;
return {
name: 'vite:inject-vite-plugin-theme-client',
enforce: 'pre',
configResolved(resolvedConfig) {
config = resolvedConfig;
isServer = resolvedConfig.command === 'serve';
needSourcemap = !!resolvedConfig.build.sourcemap;
},
transformIndexHtml: {
enforce: 'pre',
async transform(html) {
if (html.includes(CLIENT_PUBLIC_PATH)) {
return html;
}
return {
html,
tags: [
{
tag: 'script',
attrs: {
type: 'module',
src: path.posix.join(CLIENT_PUBLIC_PATH),
},
injectTo: 'head-prepend',
},
],
};
},
},
async transform(code, id) {
const nid = normalizePath(id);
const path = normalizePath('vite-plugin-theme/es/client.js');
const getMap = () => (needSourcemap ? this.getCombinedSourcemap() : null);
if (
nid === CLIENT_PUBLIC_ABSOLUTE_PATH ||
nid.includes(path) ||
// support .vite cache
nid.includes(path.replace(/\//gi, '_'))
) {
debug('transform client file:', id, code);
const {
build: { assetsDir },
} = config;
const getOutputFile = (name?: string) => {
return JSON.stringify(`${config.base}${assetsDir}/${name}`);
};
if (type === 'colorPlugin') {
code = code
.replace('__COLOR_PLUGIN_OUTPUT_FILE_NAME__', getOutputFile(colorPluginCssOutputName))
.replace('__COLOR_PLUGIN_OPTIONS__', JSON.stringify(colorPluginOptions));
}
if (type === 'antdDarkPlugin') {
code = code.replace(
'__ANTD_DARK_PLUGIN_OUTPUT_FILE_NAME__',
getOutputFile(antdDarkCssOutputName)
);
if (typeof antdDarkExtractCss === 'boolean') {
code = code.replace(
'__ANTD_DARK_PLUGIN_EXTRACT_CSS__',
JSON.stringify(antdDarkExtractCss)
);
}
if (typeof antdDarkLoadLink === 'boolean') {
code = code.replace(
'__ANTD_DARK_PLUGIN_LOAD_LINK__',
JSON.stringify(antdDarkExtractCss)
);
}
}
return {
code: code.replace('__PROD__', JSON.stringify(!isServer)),
map: getMap(),
};
}
},
};
}

174
build/plugin/theme/preprocessor/less/index.ts

@ -0,0 +1,174 @@
import path from 'path';
import fs from 'fs';
import { Alias, normalizePath, ResolvedConfig } from 'vite';
import less from 'less';
export type ResolveFn = (
id: string,
importer?: string,
aliasOnly?: boolean
) => Promise<string | undefined>;
type CssUrlReplacer = (url: string, importer?: string) => string | Promise<string>;
export const externalRE = /^(https?:)?\/\//;
export const isExternalUrl = (url: string) => externalRE.test(url);
export const dataUrlRE = /^\s*data:/i;
export const isDataUrl = (url: string) => dataUrlRE.test(url);
const cssUrlRE = /url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/;
let ViteLessManager: any;
function createViteLessPlugin(
rootFile: string,
alias: Alias[],
resolvers: { less: ResolveFn }
): Less.Plugin {
if (!ViteLessManager) {
ViteLessManager = class ViteManager extends less.FileManager {
resolvers;
rootFile;
alias;
constructor(rootFile: string, resolvers: ResolveFn, alias: Alias[]) {
super();
this.rootFile = rootFile;
this.resolvers = resolvers;
this.alias = alias;
}
supports() {
return true;
}
supportsSync() {
return false;
}
async loadFile(
filename: string,
dir: string,
opts: any,
env: any
): Promise<Less.FileLoadResult> {
const resolved = await this.resolvers.less(filename, path.join(dir, '*'));
if (resolved) {
const result = await rebaseUrls(resolved, this.rootFile, this.alias);
let contents;
if (result && 'contents' in result) {
contents = result.contents;
} else {
contents = fs.readFileSync(resolved, 'utf-8');
}
return {
filename: path.resolve(resolved),
contents,
};
} else {
return super.loadFile(filename, dir, opts, env);
}
}
};
}
return {
install(_, pluginManager) {
pluginManager.addFileManager(new ViteLessManager(rootFile, resolvers, alias));
},
minVersion: [3, 0, 0],
};
}
export function lessPlugin(id, config: ResolvedConfig) {
const resolvers = createCSSResolvers(config);
return createViteLessPlugin(id, config.resolve.alias, resolvers);
}
function createCSSResolvers(config: ResolvedConfig): { less: ResolveFn } {
let lessResolve: ResolveFn | undefined;
return {
get less() {
return (
lessResolve ||
(lessResolve = config.createResolver({
extensions: ['.less', '.css'],
mainFields: ['less', 'style'],
tryIndex: false,
preferRelative: true,
}))
);
},
};
}
/**
* relative url() inside \@imported sass and less files must be rebased to use
* root file as base.
*/
async function rebaseUrls(file: string, rootFile: string, alias: Alias[]): Promise<any> {
file = path.resolve(file); // ensure os-specific flashes
// in the same dir, no need to rebase
const fileDir = path.dirname(file);
const rootDir = path.dirname(rootFile);
if (fileDir === rootDir) {
return { file };
}
// no url()
const content = fs.readFileSync(file, 'utf-8');
if (!cssUrlRE.test(content)) {
return { file };
}
const rebased = await rewriteCssUrls(content, (url) => {
if (url.startsWith('/')) return url;
// match alias, no need to rewrite
for (const { find } of alias) {
const matches = typeof find === 'string' ? url.startsWith(find) : find.test(url);
if (matches) {
return url;
}
}
const absolute = path.resolve(fileDir, url);
const relative = path.relative(rootDir, absolute);
return normalizePath(relative);
});
return {
file,
contents: rebased,
};
}
function rewriteCssUrls(css: string, replacer: CssUrlReplacer): Promise<string> {
return asyncReplace(css, cssUrlRE, async (match) => {
const [matched, rawUrl] = match;
return await doUrlReplace(rawUrl, matched, replacer);
});
}
export async function asyncReplace(
input: string,
re: RegExp,
replacer: (match: RegExpExecArray) => string | Promise<string>
) {
let match: RegExpExecArray | null;
let remaining = input;
let rewritten = '';
while ((match = re.exec(remaining))) {
rewritten += remaining.slice(0, match.index);
rewritten += await replacer(match);
remaining = remaining.slice(match.index + match[0].length);
}
rewritten += remaining;
return rewritten;
}
async function doUrlReplace(rawUrl: string, matched: string, replacer: CssUrlReplacer) {
let wrap = '';
const first = rawUrl[0];
if (first === `"` || first === `'`) {
wrap = first;
rawUrl = rawUrl.slice(1, -1);
}
if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
return matched;
}
return `url(${wrap}${await replacer(rawUrl)}${wrap})`;
}

128
build/plugin/theme/utils.ts

@ -0,0 +1,128 @@
import { ResolvedConfig } from 'vite';
import { createHash } from 'crypto';
import { ResolveSelector } from '.';
import { commentRE, cssBlockRE, ruleRE, cssValueRE, safeEmptyRE, importSafeRE } from './constants';
import CleanCSS from 'clean-css';
export function getVariablesReg(colors: string[]) {
return new RegExp(
colors
.map(
(i) =>
`(${i
.replace(/\s/g, ' ?')
.replace(/\(/g, `\\(`)
.replace(/\)/g, `\\)`)
.replace(/0?\./g, `0?\\.`)})`
)
.join('|')
);
}
export function combineRegs(decorator = '', joinString = '', ...args: any[]) {
const regString = args
.map((item) => {
const str = item.toString();
return `(${str.slice(1, str.length - 1)})`;
})
.join(joinString);
return new RegExp(regString, decorator);
}
export function formatCss(s: string) {
s = s.replace(/\s*([{}:;,])\s*/g, '$1');
s = s.replace(/;\s*;/g, ';');
s = s.replace(/,[\s.#\d]*{/g, '{');
s = s.replace(/([^\s])\{([^\s])/g, '$1 {\n\t$2');
s = s.replace(/([^\s])\}([^\n]*)/g, '$1\n}\n$2');
s = s.replace(/([^\s]);([^\s}])/g, '$1;\n\t$2');
return s;
}
export function createFileHash() {
return createHash('sha256').digest('hex').substr(0, 8);
}
/**
* Compress the generated code
*/
export async function minifyCSS(css: string, config: ResolvedConfig) {
const res = new CleanCSS({
rebase: false,
...config.build.cleanCssOptions,
}).minify(css);
if (res.errors && res.errors.length) {
console.error(`error when minifying css:\n${res.errors}`);
throw res.errors[0];
}
if (res.warnings && res.warnings.length) {
config.logger.warn(`warnings when minifying css:\n${res.warnings}`);
}
return res.styles;
}
// Used to extract relevant color configuration in css
export function extractVariable(
code: string,
colorVariables: string[],
resolveSelector?: ResolveSelector,
colorRE?: RegExp
) {
colorVariables = Array.from(new Set(colorVariables));
code = code.replace(commentRE, '');
const cssBlocks = code.match(cssBlockRE);
if (!cssBlocks || cssBlocks.length === 0) {
return '';
}
let allExtractedVariable = '';
const variableReg = getVariablesReg(colorVariables);
for (let index = 0; index < cssBlocks.length; index++) {
const cssBlock = cssBlocks[index];
if (!variableReg.test(cssBlock) || !cssBlock) {
continue;
}
const cssSelector = cssBlock.match(/[^{]*/)?.[0] ?? '';
if (!cssSelector) {
continue;
}
if (/^@.*keyframes/.test(cssSelector)) {
allExtractedVariable += `${cssSelector}{${extractVariable(
cssBlock.replace(/[^{]*\{/, '').replace(/}$/, ''),
colorVariables,
resolveSelector,
colorRE
)}}`;
continue;
}
const colorReg = combineRegs(
'g',
'',
ruleRE,
cssValueRE,
safeEmptyRE,
variableReg,
importSafeRE
);
const colorReplaceTemplates = cssBlock.match(colorRE || colorReg);
if (!colorReplaceTemplates) {
continue;
}
allExtractedVariable += `${
resolveSelector ? resolveSelector(cssSelector) : cssSelector
} {${colorReplaceTemplates.join(';')}}`;
}
return allExtractedVariable;
}

35
build/theme.ts

@ -0,0 +1,35 @@
import { viteThemePlugin } from './plugin/theme';
import { generateColors, getThemeColors } from './themeConfig';
import { mixLighten, mixDarken, tinycolor } from 'vite-plugin-theme';
export function theme({ isProd }) {
const colors = generateColors({
mixDarken,
mixLighten,
tinycolor,
});
return viteThemePlugin({
resolveSelector: (s) => {
s = s.trim();
switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s;
case '.ant-steps-item-icon > .ant-steps-icon':
return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
default:
if (s.indexOf('.ant-btn') >= -1) {
// 按钮被重新定制过,需要过滤掉class防止覆盖
return s;
}
}
return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
},
colorVariables: [...getThemeColors(), ...colors],
isProd,
});
}

75
build/themeConfig.ts

@ -0,0 +1,75 @@
/** @format */
import { generate } from '@ant-design/colors';
export const primaryColor = '#37abc4';
//export const primaryColor = '#ff4919';
export const darkMode = 'light';
type Fn = (...arg: any) => any;
type GenerateTheme = 'default' | 'dark';
export interface GenerateColorsParams {
mixLighten: Fn;
mixDarken: Fn;
tinycolor: any;
color?: string;
}
export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
return generate(color, {
theme,
});
}
export function getThemeColors(color?: string) {
const tc = color || primaryColor;
const lightColors = generateAntColors(tc);
const primary = lightColors[5];
const modeColors = generateAntColors(primary, 'dark');
return [...lightColors, ...modeColors];
}
export function generateColors({
color = primaryColor,
mixLighten,
mixDarken,
tinycolor,
}: GenerateColorsParams) {
const arr = new Array(19).fill(0);
const lightens = arr.map((_t, i) => {
return mixLighten(color, i / 5);
});
const darkens = arr.map((_t, i) => {
return mixDarken(color, i / 5);
});
const alphaColors = arr.map((_t, i) => {
return tinycolor(color)
.setAlpha(i / 20)
.toRgbString();
});
const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
const tinycolorLightens = arr
.map((_t, i) => {
return tinycolor(color)
.lighten(i * 5)
.toHexString();
})
.filter((item) => item !== '#ffffff');
const tinycolorDarkens = arr
.map((_t, i) => {
return tinycolor(color)
.darken(i * 5)
.toHexString();
})
.filter((item) => item !== '#000000');
return [...lightens, ...darkens].filter((item) => !item.includes('-'));
}

165
build/vite-default.config.ts

@ -0,0 +1,165 @@
/** @format */
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { basename, resolve } from 'path';
import dayjs from 'dayjs';
import pkg from '../package.json';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import { generateModifyVars } from './less';
import { theme } from './theme';
const { dependencies, devDependencies, name, version } = pkg;
//设置分包和合并包的策略
const output = {
manualChunks(id: any): string {
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)`;
const cssLangRE = new RegExp(cssLangs);
const isCSSRequest = (request: string): boolean => cssLangRE.test(request);
// && !isCSSRequest(id)
if (id.includes('style.css')) {
// if (isCSSRequest(id)) {
// 需要单独分割那些资源 就写判断逻辑就行
return 'src/style.css';
}
// // 最小化拆分包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
// 分manifest包,解决chunk碎片问题
if (id.includes('src')) {
return 'manifest';
}
},
};
export default function nsDefineConfig(params: {
serviceMode: 'saas' | 'paas';
dirname: string;
baseDir: string;
proxy?: object;
customOutput?: boolean;
}) {
const { serviceMode, dirname, baseDir, customOutput = false } = params;
function pathResolve(dir: string): string {
return resolve(dirname, '.', dir);
}
// @ts-ignore
return defineConfig(({ mode, command }) => {
//运行信息
const __APP_INFO__ = {
platform: process.env.PLATFORM,
pkg: { dependencies, devDependencies, name, version },
buildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
serviceMode,
};
console.log('mode', mode);
const env = loadEnv(mode, dirname);
const { VITE_PORT, VITE_PROXY = '{}', VITE_LEGACY, VITE_PUBLIC_PATH } = env;
const port = Number(VITE_PORT);
const proxy = params.proxy ? params.proxy : JSON.parse(VITE_PROXY);
const isBuild = command === 'build';
const server = {
host: true,
port,
proxy,
fs: {
allow: ['..'],
},
};
const config: any = {
base: VITE_PUBLIC_PATH,
cacheDir: `${baseDir}node_modules/.vite-${basename(dirname)}`,
build: {
target: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14'],
assetsDir: 'asset',
brotliSize: false,
// target: 'esnext',
rollupOptions: {
external: [],
},
},
resolve: {
alias: [
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
{
find: /\/nerv-lib\//,
replacement: pathResolve('../lib') + '/',
},
{
find: /\/nerv-base\//,
replacement: pathResolve(`../lib/${serviceMode}`) + '/',
},
{
find: 'antd/lib',
replacement: 'antd/es',
},
{
find: '@antv/x6',
replacement: '@antv/x6/dist/x6.js',
},
{
find: 'flv.js',
replacement: 'flv.js/dist/flv.min.js',
},
],
},
server,
define: {
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
...generateModifyVars({
dark: false,
paths: [
pathResolve(`${baseDir}lib/${serviceMode}/theme/variable.less`),
pathResolve('./src/theme/variable.less'),
],
}),
},
javascriptEnabled: true,
},
},
},
plugins: [
vue(),
vueJsx(),
theme({ isProd: mode !== 'development' }),
VITE_LEGACY &&
isBuild &&
legacy({
targets: ['defaults', 'not IE 11'],
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [
pathResolve('src/icon'),
pathResolve(`${baseDir}lib/${serviceMode}/asset/icon`),
],
svgoOptions: isBuild,
symbolId: 'icon-[dir]-[name]',
}),
],
};
if (customOutput) {
config.build.rollupOptions['output'] = output;
}
return config;
});
}

164
build/vite-low-version.config.ts

@ -0,0 +1,164 @@
/** @format */
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { basename, resolve } from 'path';
import dayjs from 'dayjs';
import pkg from '../package.json';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import { generateModifyVars } from './less';
import { theme } from './theme';
const { dependencies, devDependencies, name, version } = pkg;
//设置分包和合并包的策略
const output = {
manualChunks(id: any): string {
const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\\?)`;
const cssLangRE = new RegExp(cssLangs);
const isCSSRequest = (request: string): boolean => cssLangRE.test(request);
// && !isCSSRequest(id)
if (id.includes('style.css')) {
// if (isCSSRequest(id)) {
// 需要单独分割那些资源 就写判断逻辑就行
return 'src/style.css';
}
// // 最小化拆分包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
// 分manifest包,解决chunk碎片问题
if (id.includes('src')) {
return 'manifest';
}
},
};
export default function nsDefineConfig(params: {
serviceMode: 'saas' | 'paas';
dirname: string;
baseDir: string;
proxy?: object;
customOutput?: boolean;
}) {
const { serviceMode, dirname, baseDir, customOutput = false } = params;
function pathResolve(dir: string): string {
return resolve(dirname, '.', dir);
}
// @ts-ignore
return defineConfig(({ mode, command }) => {
//运行信息
const __APP_INFO__ = {
platform: process.env.PLATFORM,
pkg: { dependencies, devDependencies, name, version },
buildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
serviceMode,
};
const env = loadEnv(mode, dirname);
const { VITE_PORT, VITE_PROXY = '{}', VITE_LEGACY, VITE_PUBLIC_PATH } = env;
const port = Number(VITE_PORT);
const proxy = params.proxy ? params.proxy : JSON.parse(VITE_PROXY);
const isBuild = command === 'build';
const server = {
host: true,
port,
proxy,
fs: {
allow: ['..'],
},
};
const config: any = {
base: VITE_PUBLIC_PATH,
cacheDir: `${baseDir}node_modules/.vite-${basename(dirname)}`,
build: {
assetsDir: 'asset',
brotliSize: false,
target: 'es2015',
rollupOptions: {
external: [],
},
},
resolve: {
alias: [
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
{
find: /\/nerv-lib\//,
replacement: pathResolve('../lib') + '/',
},
{
find: /\/nerv-base\//,
replacement: pathResolve(`../lib/${serviceMode}`) + '/',
},
{
find: 'antd/lib',
replacement: 'antd/es',
},
{
find: '@antv/x6',
replacement: '@antv/x6/dist/x6.js',
},
{
find: 'flv.js',
replacement: 'flv.js/dist/flv.min.js',
},
],
},
server,
define: {
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
...generateModifyVars({
dark: false,
paths: [
pathResolve(`${baseDir}lib/${serviceMode}/theme/variable.less`),
pathResolve('./src/theme/variable.less'),
],
}),
},
javascriptEnabled: true,
},
},
},
plugins: [
vue(),
vueJsx(),
theme(),
VITE_LEGACY &&
isBuild &&
legacy({
targets: ['defaults', 'not IE 11', 'Chrome 64'],
modernPolyfills: true,
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [
pathResolve('src/icon'),
pathResolve(`${baseDir}lib/${serviceMode}/asset/icon`),
],
svgoOptions: isBuild,
symbolId: 'icon-[dir]-[name]',
}),
],
};
if (customOutput) {
config.build.rollupOptions['output'] = output;
}
return config;
});
}

228
build/vite-saas.config.ts

@ -0,0 +1,228 @@
/** @format */
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import { basename, resolve } from 'path';
import dayjs from 'dayjs';
import pkg from '../package.json';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import { generateModifyVars } from './less';
import { theme } from './theme';
import { visualizer } from 'rollup-plugin-visualizer';
import duration from 'dayjs/plugin/duration';
import path from 'path';
import { splitVendorChunkPlugin } from 'vite';
import { configMockPlugin } from './plugin/mock/mock';
dayjs.extend(duration);
interface Statistics {
buildStartTime: number;
outputOptionsTime: number[];
closeBundleTime: number;
}
function buildTimePlugin() {
const statistics: Statistics = {
buildStartTime: 0,
outputOptionsTime: [],
closeBundleTime: 0,
};
return {
name: 'build-time',
buildStart() {
statistics.buildStartTime = Date.now();
console.log('开始打包');
},
outputOptions() {
statistics.outputOptionsTime.push(Date.now());
if (statistics.outputOptionsTime.length === 1) {
console.log(
`分析耗时: ${dayjs
.duration(
statistics.outputOptionsTime[statistics.outputOptionsTime.length - 1] -
statistics.buildStartTime,
)
.format('m:s')}`,
);
} else {
console.log(
`上一步耗时: ${dayjs
.duration(
statistics.outputOptionsTime[statistics.outputOptionsTime.length - 1] -
statistics.outputOptionsTime[statistics.outputOptionsTime.length - 2],
)
.format('m:s')}`,
);
}
},
closeBundle() {
statistics.closeBundleTime = Date.now();
if (statistics.outputOptionsTime.length === 1) {
console.log(
`分包耗时 ${dayjs
.duration(statistics.closeBundleTime - statistics.outputOptionsTime[0])
.format('m:s')}`,
);
}
console.log(
`总耗时 ${dayjs
.duration(statistics.closeBundleTime - statistics.buildStartTime)
.format('m:s')}`,
);
},
};
}
const { dependencies, devDependencies, name, version } = pkg;
export default function nsDefineConfig(params: {
serviceMode?: 'saas' | 'paas';
dirname: string;
baseDir?: string;
proxy?: object;
manualChunks?: boolean;
mock?: boolean;
mockPath?: string;
}) {
const {
serviceMode = 'saas',
dirname,
baseDir = '../',
manualChunks = false,
mock = false,
mockPath = `${basename(dirname)}/mock`,
} = params;
function pathResolve(dir: string): string {
return resolve(dirname, '.', dir);
}
console.log('mockPath', mockPath);
// @ts-ignore
return defineConfig(({ mode, command }) => {
//运行信息
const __APP_INFO__ = {
platform: process.env.PLATFORM,
pkg: { dependencies, devDependencies, name, version },
buildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
serviceMode,
};
const env = loadEnv(mode, dirname);
const { VITE_PORT, VITE_PROXY = '{}', VITE_LEGACY, VITE_PUBLIC_PATH } = env;
const port = Number(VITE_PORT);
const proxy = params.proxy ? params.proxy : JSON.parse(VITE_PROXY);
const isBuild = command === 'build';
const server = {
host: true,
port,
proxy,
fs: {
allow: ['..'],
},
};
const config: any = {
base: VITE_PUBLIC_PATH,
cacheDir: `${baseDir}node_modules/.vite-${basename(dirname)}`,
build: {
assetsDir: 'asset',
brotliSize: false,
target: 'es2015',
rollupOptions: {
output: {
manualChunks: (id: string) => {
const root = basename(dirname);
const fileName = path
.normalize(path.relative(root, id))
.split(path.sep)
.join(path.posix.sep);
if (fileName.includes('lib/')) {
return 'nerv-lib';
} else if (fileName.includes('src/view/')) {
return id.toString().split('src/view/')[1].split('/')[0].toString();
}
},
},
},
},
resolve: {
alias: [
{
find: 'vue-i18n',
replacement: 'vue-i18n/dist/vue-i18n.cjs.js',
},
{
find: /\/@\//,
replacement: pathResolve('src') + '/',
},
{
find: /\/nerv-lib\//,
replacement: pathResolve('../lib') + '/',
},
{
find: /\/nerv-base\//,
replacement: pathResolve(`../lib/${serviceMode}`) + '/',
},
{
find: '@antv/x6',
replacement: '@antv/x6/dist/x6.js',
},
{
find: 'flv.js',
replacement: 'flv.js/dist/flv.min.js',
},
],
},
server,
define: {
__APP_INFO__: JSON.stringify(__APP_INFO__),
},
css: {
preprocessorOptions: {
less: {
modifyVars: {
...generateModifyVars({
dark: false,
paths: [
pathResolve(`${baseDir}lib/${serviceMode}/theme/variable.less`),
pathResolve('./src/theme/variable.less'),
],
}),
},
javascriptEnabled: true,
},
},
},
plugins: [
vue(),
vueJsx(),
theme({ isProd: mode !== 'development' }),
VITE_LEGACY &&
isBuild &&
legacy({
targets: ['defaults', 'not IE 11'],
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [
pathResolve('src/icon'),
pathResolve(`${baseDir}lib/${serviceMode}/asset/icon`),
],
svgoOptions: isBuild,
symbolId: 'icon-[dir]-[name]',
}),
configMockPlugin({ enable: mock, mockPath }),
visualizer(),
isBuild && buildTimePlugin(),
splitVendorChunkPlugin(),
],
};
return config;
});
}

23
gulpfile.js

@ -0,0 +1,23 @@
const project = 'nervui-smart-parking'; //修改成对象项目 并手动生成routes.json
const gulp = require('gulp');
const replace = require('gulp-replace');
const json = require(`./${project}/routes.json`);
const router = {};
for (let i = 0, j = json.length; i < j; i++) {
router[json[i].path] = json[i].name;
}
function defaultTask() {
return gulp
.src([`${project}/src/view/**/*.vue`])
.pipe(
replace(/route: ('[a-zA-Z\-\/]+')/g, function () {
let key = arguments[1].replace(/'/g, '');
console.log(key, router[key]);
if (router[key]) return `route: { name: '${router[key]}' }`;
return `route: '${key}'`;
}),
)
.pipe(gulp.dest(`${project}/src/view/`));
}
exports.default = defaultTask;

13
index.html

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>nerv-saas</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

4
lib/component/VNode/index.ts

@ -0,0 +1,4 @@
import VNode from './v-node';
import { withInstall } from '/nerv-lib/util';
export const NsVNode = withInstall(VNode);

13
lib/component/VNode/v-node.ts

@ -0,0 +1,13 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsVNode',
props: {
content: {
type: [Object, String],
},
},
render() {
return this.content;
},
});

288
lib/component/color/color-picker.vue

@ -0,0 +1,288 @@
<template>
<div :class="['vc-sketch', disableAlpha ? 'vc-sketch__disable-alpha' : '']">
<div class="vc-sketch-saturation-wrap">
<saturation v-model:value="colors" @change="childChange" />
</div>
<div class="vc-sketch-controls">
<div class="vc-sketch-sliders">
<div class="vc-sketch-hue-wrap">
<hue v-model:value="colors" @change="childChange" />
</div>
</div>
<div class="vc-sketch-color-wrap">
<div
:aria-label="`Current color is ${activeColor}`"
class="vc-sketch-active-color"
:style="{ background: activeColor }"
></div>
<check-board />
</div>
</div>
<div class="vc-sketch-field" v-if="!disableFields">
<!-- rgba -->
<div class="vc-sketch-field--double">
<edit-input label="hex" :value="hex" @change="inputChange" />
</div>
<div class="vc-sketch-field--single">
<edit-input label="r" :value="colors.rgba.r" @change="inputChange" />
</div>
<div class="vc-sketch-field--single">
<edit-input label="g" :value="colors.rgba.g" @change="inputChange" />
</div>
<div class="vc-sketch-field--single">
<edit-input label="b" :value="colors.rgba.b" @change="inputChange" />
</div>
</div>
<div
class="vc-sketch-presets"
role="group"
aria-label="A color preset, pick one to set as current color"
>
<template v-for="c in presetColors" :key="c">
<div
v-if="!isTransparent(c)"
class="vc-sketch-presets-color"
:aria-label="'Color:' + c"
:style="{ background: c }"
@click="handlePreset(c)"
>
</div>
<div
v-else
:aria-label="'Color:' + c"
class="vc-sketch-presets-color"
@click="handlePreset(c)"
>
<check-board />
</div>
</template>
</div>
</div>
</template>
<script>
import colorMixin from './mixin/color';
import EditInput from './common/EditableInput.vue';
import saturation from './common/Saturation.vue';
import hue from './common/Hue.vue';
import CheckBoard from './common/Checkboard.vue';
const presetColors = [
'#D0021B',
'#F5A623',
'#F8E71C',
'#8B572A',
'#7ED321',
'#417505',
'#BD10E0',
'#4A90E2',
'#50E3C2',
'#B8E986',
'#000000',
'#4A4A4A',
];
export default {
name: 'NsColorPicker',
components: {
saturation,
hue,
EditInput,
CheckBoard,
},
mixins: [colorMixin],
props: {
presetColors: {
type: Array,
default() {
return presetColors;
},
},
disableAlpha: {
type: Boolean,
default: false,
},
disableFields: {
type: Boolean,
default: false,
},
},
computed: {
hex() {
let hex;
if (this.colors.a < 1) {
hex = this.colors.hex8;
} else {
hex = this.colors.hex;
}
return hex.replace('#', '');
},
activeColor() {
var rgba = this.colors.rgba;
return 'rgba(' + [rgba.r, rgba.g, rgba.b, rgba.a].join(',') + ')';
},
},
methods: {
handlePreset(c) {
this.colorChange({
hex: c,
source: 'hex',
});
},
childChange(data) {
this.colorChange(data);
},
inputChange(data) {
if (!data) {
return;
}
if (data.hex) {
this.isValidHex(data.hex) &&
this.colorChange({
hex: data.hex,
source: 'hex',
});
} else if (data.r || data.g || data.b || data.a) {
this.colorChange({
r: data.r || this.colors.rgba.r,
g: data.g || this.colors.rgba.g,
b: data.b || this.colors.rgba.b,
a: data.a || this.colors.rgba.a,
source: 'rgba',
});
}
},
},
};
</script>
<style>
.vc-sketch {
position: relative;
width: 200px;
padding: 10px 10px 0;
box-sizing: initial;
background: #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
}
.vc-sketch-saturation-wrap {
width: 100%;
padding-bottom: 75%;
position: relative;
overflow: hidden;
}
.vc-sketch-controls {
display: flex;
}
.vc-sketch-sliders {
padding: 4px 0;
flex: 1;
}
.vc-sketch-sliders .vc-hue,
.vc-sketch-sliders .vc-alpha-gradient {
border-radius: 2px;
}
.vc-sketch-hue-wrap {
position: relative;
height: 24px;
}
.vc-sketch-alpha-wrap {
position: relative;
height: 10px;
margin-top: 4px;
overflow: hidden;
}
.vc-sketch-color-wrap {
width: 24px;
height: 24px;
position: relative;
margin-top: 4px;
margin-left: 4px;
border-radius: 3px;
}
.vc-sketch-active-color {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 2px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15), inset 0 0 4px rgba(0, 0, 0, 0.25);
z-index: 2;
}
.vc-sketch-color-wrap .vc-checkerboard {
background-size: auto;
}
.vc-sketch-field {
display: flex;
padding-top: 4px;
}
.vc-sketch-field .vc-input__input {
width: 90%;
padding: 2px 0;
text-align: center;
border: none;
box-shadow: inset 0 0 0 1px #ccc;
font-size: 10px;
}
.vc-sketch-field .vc-input__label {
display: block;
text-align: center;
font-size: 11px;
color: #222;
padding-top: 3px;
padding-bottom: 4px;
text-transform: capitalize;
}
.vc-sketch-field--single {
flex: 1;
padding-left: 6px;
}
.vc-sketch-field--double {
flex: 2;
}
.vc-sketch-presets {
margin-right: -10px;
margin-left: -10px;
padding-left: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.vc-sketch-presets-color {
border-radius: 3px;
overflow: hidden;
position: relative;
display: inline-block;
margin: 0 7px 10px 0;
vertical-align: top;
cursor: pointer;
width: 24px;
height: 24px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}
.vc-sketch-presets-color .vc-checkerboard {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
border-radius: 3px;
}
.vc-sketch__disable-alpha .vc-sketch-color-wrap {
height: 10px;
}
</style>

138
lib/component/color/common/Alpha.vue

@ -0,0 +1,138 @@
<template>
<div class="vc-alpha">
<div class="vc-alpha-checkboard-wrap">
<checkboard></checkboard>
</div>
<div class="vc-alpha-gradient" :style="{ background: gradientColor }"></div>
<div
class="vc-alpha-container"
ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div class="vc-alpha-pointer" :style="{ left: colors.a * 100 + '%' }">
<div class="vc-alpha-picker"></div>
</div>
</div>
</div>
</template>
<script>
import checkboard from './Checkboard.vue';
export default {
name: 'Alpha',
props: {
value: Object,
onChange: Function,
},
components: {
checkboard,
},
computed: {
colors() {
return this.value;
},
gradientColor() {
var rgba = this.colors.rgba;
var rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
return (
'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)'
);
},
},
methods: {
handleChange(e, skip) {
!skip && e.preventDefault();
var container = this.$refs.container;
if (!container) {
// for some edge cases, container may not exist. see #220
return;
}
var containerWidth = container.clientWidth;
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
var left = pageX - xOffset;
var a;
if (left < 0) {
a = 0;
} else if (left > containerWidth) {
a = 1;
} else {
a = Math.round((left * 100) / containerWidth) / 100;
}
if (this.colors.a !== a) {
this.$emit('change', {
h: this.colors.hsl.h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: a,
source: 'rgba',
});
}
},
handleMouseDown(e) {
this.handleChange(e, true);
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp() {
this.unbindEventListeners();
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
},
},
};
</script>
<style>
.vc-alpha {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.vc-alpha-checkboard-wrap {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
overflow: hidden;
}
.vc-alpha-gradient {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.vc-alpha-container {
cursor: pointer;
position: relative;
z-index: 2;
height: 100%;
margin: 0 3px;
}
.vc-alpha-pointer {
z-index: 2;
position: absolute;
}
.vc-alpha-picker {
cursor: pointer;
width: 4px;
border-radius: 1px;
height: 8px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
background: #fff;
margin-top: 1px;
transform: translateX(-2px);
}
</style>

92
lib/component/color/common/Checkboard.vue

@ -0,0 +1,92 @@
<template>
<div class="vc-checkerboard" :style="bgStyle"></div>
</template>
<script>
let _checkboardCache = {};
export default {
name: 'Checkboard',
props: {
size: {
type: [Number, String],
default: 8,
},
white: {
type: String,
default: '#fff',
},
grey: {
type: String,
default: '#e6e6e6',
},
},
computed: {
bgStyle() {
return {
'background-image': 'url(' + getCheckboard(this.white, this.grey, this.size) + ')',
};
},
},
};
/**
* get base 64 data by canvas
*
* @param {String} c1 hex color
* @param {String} c2 hex color
* @param {Number} size
*/
function renderCheckboard(c1, c2, size) {
// Dont Render On Server
if (typeof document === 'undefined') {
return null;
}
var canvas = document.createElement('canvas');
canvas.width = canvas.height = size * 2;
var ctx = canvas.getContext('2d');
// If no context can be found, return early.
if (!ctx) {
return null;
}
ctx.fillStyle = c1;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = c2;
ctx.fillRect(0, 0, size, size);
ctx.translate(size, size);
ctx.fillRect(0, 0, size, size);
return canvas.toDataURL();
}
/**
* get checkboard base data and cache
*
* @param {String} c1 hex color
* @param {String} c2 hex color
* @param {Number} size
*/
function getCheckboard(c1, c2, size) {
var key = c1 + ',' + c2 + ',' + size;
if (_checkboardCache[key]) {
return _checkboardCache[key];
} else {
var checkboard = renderCheckboard(c1, c2, size);
_checkboardCache[key] = checkboard;
return checkboard;
}
}
</script>
<style>
.vc-checkerboard {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background-size: contain;
}
</style>

114
lib/component/color/common/EditableInput.vue

@ -0,0 +1,114 @@
<template>
<div class="vc-editable-input">
<input
:aria-labelledby="labelId"
class="vc-input__input"
v-model="val"
@keydown="handleKeyDown"
@input="update"
ref="input"
/>
<span :for="label" class="vc-input__label" :id="labelId">{{ labelSpanText }}</span>
<span class="vc-input__desc">{{ desc }}</span>
</div>
</template>
<script>
export default {
name: 'editableInput',
props: {
label: String,
labelText: String,
desc: String,
value: [String, Number],
max: Number,
min: Number,
arrowOffset: {
type: Number,
default: 1,
},
},
computed: {
val: {
get() {
return this.value;
},
set(v) {
// TODO: min
if (!(this.max === undefined) && +v > this.max) {
this.$refs.input.value = this.max;
} else {
return v;
}
},
},
labelId() {
return `input__label__${this.label}__${Math.random().toString().slice(2, 5)}`;
},
labelSpanText() {
return this.labelText || this.label;
},
},
methods: {
update(e) {
this.handleChange(e.target.value);
},
handleChange(newVal) {
let data = {};
data[this.label] = newVal;
if (data.hex === undefined && data['#'] === undefined) {
this.$emit('change', data);
} else if (newVal.length > 5) {
this.$emit('change', data);
}
},
// **** unused
// handleBlur (e) {
// console.log(e)
// },
handleKeyDown(e) {
let val = this.val;
let number = Number(val);
if (number) {
let amount = this.arrowOffset || 1;
// Up
if (e.keyCode === 38) {
val = number + amount;
this.handleChange(val);
e.preventDefault();
}
// Down
if (e.keyCode === 40) {
val = number - amount;
this.handleChange(val);
e.preventDefault();
}
}
},
// **** unused
// handleDrag (e) {
// console.log(e)
// },
// handleMouseDown (e) {
// console.log(e)
// }
},
};
</script>
<style>
.vc-editable-input {
position: relative;
}
.vc-input__input {
padding: 0;
border: 0;
outline: none;
}
.vc-input__label {
text-transform: capitalize;
}
</style>

205
lib/component/color/common/Hue.vue

@ -0,0 +1,205 @@
<template>
<div :class="['vc-hue', directionClass]">
<div
class="vc-hue-container"
role="slider"
:aria-valuenow="colors.hsl.h"
aria-valuemin="0"
aria-valuemax="360"
ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div
class="vc-hue-pointer"
:style="{ top: pointerTop, left: pointerLeft }"
role="presentation"
>
<div class="vc-hue-picker"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Hue',
props: {
value: Object,
direction: {
type: String,
// [horizontal | vertical]
default: 'horizontal',
},
},
data() {
return {
oldHue: 0,
pullDirection: '',
};
},
computed: {
colors() {
const h = this.value.hsl.h;
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
this.oldHue = h;
return this.value;
},
directionClass() {
return {
'vc-hue--horizontal': this.direction === 'horizontal',
'vc-hue--vertical': this.direction === 'vertical',
};
},
pointerTop() {
if (this.direction === 'vertical') {
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return 0;
return -((this.colors.hsl.h * 100) / 360) + 100 + '%';
} else {
return 0;
}
},
pointerLeft() {
if (this.direction === 'vertical') {
return 0;
} else {
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
return (this.colors.hsl.h * 100) / 360 + '%';
}
},
},
methods: {
handleChange(e, skip) {
!skip && e.preventDefault();
var container = this.$refs.container;
if (!container) {
// for some edge cases, container may not exist. see #220
return;
}
var containerWidth = container.clientWidth;
var containerHeight = container.clientHeight;
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
var yOffset = container.getBoundingClientRect().top + window.pageYOffset;
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
var pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
var left = pageX - xOffset;
var top = pageY - yOffset;
var h;
var percent;
if (this.direction === 'vertical') {
if (top < 0) {
h = 360;
} else if (top > containerHeight) {
h = 0;
} else {
percent = -((top * 100) / containerHeight) + 100;
h = (360 * percent) / 100;
}
if (this.colors.hsl.h !== h) {
this.$emit('change', {
h: h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: this.colors.hsl.a,
source: 'hsl',
});
}
} else {
if (left < 0) {
h = 0;
} else if (left > containerWidth) {
h = 360;
} else {
percent = (left * 100) / containerWidth;
h = (360 * percent) / 100;
}
if (this.colors.hsl.h !== h) {
this.$emit('change', {
h: h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: this.colors.hsl.a,
source: 'hsl',
});
}
}
},
handleMouseDown(e) {
this.handleChange(e, true);
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp(e) {
this.unbindEventListeners();
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
},
},
};
</script>
<style>
.vc-hue {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
border-radius: 2px;
}
.vc-hue--horizontal {
background: linear-gradient(
to right,
#f00 0%,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
#f00 100%
);
}
.vc-hue--vertical {
background: linear-gradient(
to top,
#f00 0%,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
#f00 100%
);
}
.vc-hue-container {
cursor: pointer;
margin: 0 2px;
position: relative;
height: 100%;
}
.vc-hue-pointer {
z-index: 2;
position: absolute;
}
.vc-hue-picker {
cursor: pointer;
margin-top: 1px;
width: 4px;
border-radius: 1px;
height: 20px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
background: #fff;
transform: translateX(-2px);
}
</style>

132
lib/component/color/common/Saturation.vue

@ -0,0 +1,132 @@
<template>
<div
class="vc-saturation"
:style="{ background: bgColor }"
ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div class="vc-saturation--white"></div>
<div class="vc-saturation--black"></div>
<div class="vc-saturation-pointer" :style="{ top: pointerTop, left: pointerLeft }">
<div class="vc-saturation-circle"></div>
</div>
</div>
</template>
<script>
import clamp from 'clamp';
import { throttle } from 'lodash-es';
export default {
name: 'Saturation',
props: {
value: Object,
},
computed: {
colors() {
return this.value;
},
bgColor() {
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
},
pointerTop() {
return -(this.colors.hsv.v * 100) + 1 + 100 + '%';
},
pointerLeft() {
return this.colors.hsv.s * 100 + '%';
},
},
methods: {
throttle: throttle(
(fn, data) => {
fn(data);
},
20,
{
leading: true,
trailing: false,
}
),
handleChange(e, skip) {
!skip && e.preventDefault();
var container = this.$refs.container;
if (!container) {
// for some edge cases, container may not exist. see #220
return;
}
var containerWidth = container.clientWidth;
var containerHeight = container.clientHeight;
var xOffset = container.getBoundingClientRect().left + window.pageXOffset;
var yOffset = container.getBoundingClientRect().top + window.pageYOffset;
var pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
var pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
var left = clamp(pageX - xOffset, 0, containerWidth);
var top = clamp(pageY - yOffset, 0, containerHeight);
var saturation = left / containerWidth;
var bright = clamp(-(top / containerHeight) + 1, 0, 1);
this.throttle(this.onChange, {
h: this.colors.hsv.h,
s: saturation,
v: bright,
a: this.colors.hsv.a,
source: 'hsva',
});
},
onChange(param) {
this.$emit('change', param);
},
handleMouseDown(e) {
// this.handleChange(e, true)
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp(e) {
this.unbindEventListeners();
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
},
},
created() {},
};
</script>
<style>
.vc-saturation,
.vc-saturation--white,
.vc-saturation--black {
cursor: pointer;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.vc-saturation--white {
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.vc-saturation--black {
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
}
.vc-saturation-pointer {
cursor: pointer;
position: absolute;
}
.vc-saturation-circle {
cursor: head;
width: 4px;
height: 4px;
box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0, 0, 0, 0.3),
0 0 1px 2px rgba(0, 0, 0, 0.4);
border-radius: 50%;
transform: translate(-2px, -2px);
}
</style>

4
lib/component/color/index.ts

@ -0,0 +1,4 @@
import ColorPicker from './color-picker.vue';
import { withInstall } from '/nerv-lib/util';
export const NsColorPicker = withInstall(ColorPicker);

117
lib/component/color/mixin/color.ts

@ -0,0 +1,117 @@
import tinycolor from 'tinycolor2';
function _colorChange(data, oldHue) {
const alpha = data && data.a;
let color;
// hsl is better than hex between conversions
if (data && data.hsl) {
color = tinycolor(data.hsl);
} else if (data && data.hex && data.hex.length > 0) {
color = tinycolor(data.hex);
} else if (data && data.hsv) {
color = tinycolor(data.hsv);
} else if (data && data.rgba) {
color = tinycolor(data.rgba);
} else if (data && data.rgb) {
color = tinycolor(data.rgb);
} else {
color = tinycolor(data);
}
if (color && (color._a === undefined || color._a === null)) {
color.setAlpha(alpha || 1);
}
const hsl = color.toHsl();
const hsv = color.toHsv();
if (hsl.s === 0) {
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
}
/* --- comment this block to fix #109, may cause #25 again --- */
// when the hsv.v is less than 0.0164 (base on test)
// because of possible loss of precision
// the result of hue and saturation would be miscalculated
// if (hsv.v < 0.0164) {
// hsv.h = data.h || (data.hsv && data.hsv.h) || 0
// hsv.s = data.s || (data.hsv && data.hsv.s) || 0
// }
// if (hsl.l < 0.01) {
// hsl.h = data.h || (data.hsl && data.hsl.h) || 0
// hsl.s = data.s || (data.hsl && data.hsl.s) || 0
// }
/* ------ */
return {
hsl: hsl,
hex: color.toHexString().toUpperCase(),
hex8: color.toHex8String().toUpperCase(),
rgba: color.toRgb(),
hsv: hsv,
oldHue: data.h || oldHue || hsl.h,
source: data.source,
a: data.a || color.getAlpha(),
};
}
export default {
props: ['value'],
data() {
return {
val: _colorChange(this.value),
};
},
computed: {
colors: {
get() {
return this.val;
},
set(newVal) {
this.val = newVal;
this.$emit('update:value', newVal);
},
},
},
watch: {
value(newVal) {
this.val = _colorChange(newVal);
},
},
methods: {
colorChange(data, oldHue) {
this.oldHue = this.colors.hsl.h;
this.colors = _colorChange(data, oldHue || this.oldHue);
},
isValidHex(hex) {
return tinycolor(hex).isValid();
},
simpleCheckForValidColor(data) {
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
let checked = 0;
let passed = 0;
for (let i = 0; i < keysToCheck.length; i++) {
const letter = keysToCheck[i];
if (data[letter]) {
checked++;
if (!isNaN(data[letter])) {
passed++;
}
}
}
if (checked === passed) {
return data;
}
},
paletteUpperCase(palette) {
return palette.map((c) => c.toUpperCase());
},
isTransparent(color) {
return tinycolor(color).getAlpha() === 0;
},
},
};

0
lib/component/echarts/components/chart/Base.vue

43
lib/component/echarts/components/chart/bar/BaseBar.vue

@ -0,0 +1,43 @@
<!-- @format -->
<template>
<div class="base-bar" id="basebar" ref="BaseBarRef"></div>
</template>
<script>
import { defineComponent } from 'vue';
import { init } from 'echarts';
export default defineComponent({
name: 'BaseBar',
props: {},
data() {
return {
options: null,
};
},
created() {},
mounted() {},
methods: {
setOption(obj) {
this.options = obj;
},
getOption() {
return this.options;
},
renderChart() {
if (!this.chartbox) {
this.chartbox = init(this.$refs.BaseBarRef, null, { renderer: 'svg' });
}
this.chartbox.clear();
let option = this.getOption();
this.chartbox.setOption(option, { notMerge: false });
},
},
});
</script>
<style lang="less">
.base-bar {
height: 100%;
width: 100%;
}
</style>

255
lib/component/echarts/components/chart/bar/MixedLineBar.vue

@ -0,0 +1,255 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BaseBar.vue';
import { tranNumber, lineToolTips, colorTransferToRgb } from '../tool.js';
export default defineComponent({
name: 'MultipleBar',
extends: BaseBar,
props: {
xAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
yAxisName: {
type: Array,
required: true,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: () => {
return ['#33B8CB', '#00F0FF', '#00F3FF', '#32C691'];
},
},
borderColor: {
type: String,
default: '#24B0D4',
},
shadowColor: {
type: String,
default: '#2DBFDD',
},
},
data() {
return {
colorRgb: [],
borderColorRGB: {},
shadowColorRGB: {},
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < this.colorDatas.length; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
this.borderColorRGB = colorTransferToRgb(this.borderColor);
this.shadowColorRGB = colorTransferToRgb(this.shadowColor);
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
var barObject = {
name: this.series[0].name,
barMaxWidth: '36%',
data: this.series[0].data,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[1].r},${this.colorRgb[1].g},${this.colorRgb[1].b}, 1)`, // 100%
},
{
offset: 0.13,
color: `rgba(${this.colorRgb[0].r},${this.colorRgb[0].g},${this.colorRgb[0].b},1)`, // 0%
},
{
offset: 1,
color: `rgba(${this.colorRgb[0].r},${this.colorRgb[0].g},${this.colorRgb[0].b},0.1)`, // 0%
},
],
},
},
type: this.series[0].type,
};
var lineObject = {
type: this.series[1].type,
name: this.series[1].name,
data: this.series[1].data,
yAxisIndex: 1,
lineStyle: {
width: 2,
},
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#0F3A46',
borderColor: '#00F0FF',
borderWidth: 2,
},
lineStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[2].r},${this.colorRgb[2].g},${this.colorRgb[2].b}, 1)`, // 100%
},
{
offset: 0.7,
color: `rgba(${this.colorRgb[2].r},${this.colorRgb[2].g},${this.colorRgb[2].b}, 1)`, // 100%
},
{
offset: 1,
color: `rgba(${this.colorRgb[3].r},${this.colorRgb[3].g},${this.colorRgb[3].b},1)`, // 0%
},
],
},
},
};
seriesDatas = [barObject, lineObject];
for (let i = 0; i < this.series.length; i++) {
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
itemStyle: {
color: this.colorDatas[i],
},
});
}
return { series: seriesDatas, legendData: legendDatas };
},
getyAxisDatas() {
let yAxisArr = [];
for (let i = 0; i < this.yAxisName.length; i++) {
let name = this.yAxisName[i].length ? this.yAxisName[i] : '';
var offVal = name.length * 10 + 20;
var offArr = i == 0 ? [0, 0, 0, -offVal] : [0, -offVal, 0, 0];
yAxisArr.push({
type: 'value',
alignTicks: true,
name: this.yAxisName[i],
nameTextStyle: {
padding: offArr,
opacity: 0.34,
fontSize: 11,
},
nameLocation: 'end',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
splitLine: {
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
formatter: function (v) {
return tranNumber(v);
},
},
});
}
return yAxisArr;
},
optionHandle() {
let colorDatas = this.colorDatas;
let changeData = this.getChangeData();
let yAxisDatas = this.getyAxisDatas();
var option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
},
backgroundColor: 'rgba(0,0,0,0.65)',
borderColor: 'rgba(0,0,0,0.65)',
padding: [4, 8],
formatter: function (params) {
let res = '';
for (let i = 0; i < params.length; i++) {
let { name, data, seriesName } = params[i];
let color = colorDatas[i];
var resDiv = lineToolTips(name, color, data, seriesName, i);
res += resDiv;
}
return res;
},
},
grid: {
// top:'24',
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: {
type: 'category',
data: this.xAxis,
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
//
rotate: this.axisLabelRotate,
},
axisTick: {
//x线
show: false,
},
},
yAxis: yAxisDatas,
series: changeData.series,
};
return option;
},
},
});
</script>

223
lib/component/echarts/components/chart/bar/MultipleBar.vue

@ -0,0 +1,223 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BaseBar.vue';
import { tranNumber, lineToolTips, colorTransferToRgb } from '../tool.js';
export default defineComponent({
name: 'MultipleBar',
extends: BaseBar,
props: {
xAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
yAxisName: {
type: String,
required: true,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: () => {
return ['#10DDDD', '#E0CFA3', '#50F3A8', '#88B8FF'];
},
},
},
data() {
return {
colorRgb: [],
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < this.colorDatas.length; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
for (let i = 0; i < this.series.length; i++) {
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
itemStyle: {
color: this.colorDatas[i],
},
});
seriesDatas.push({
name: this.series[i].name,
barMaxWidth: 15,
data: this.series[i].data,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[i].r},${this.colorRgb[i].g},${this.colorRgb[i].b},0.8)`, // 0%
},
{
offset: 1,
color: `rgba(${this.colorRgb[i].r},${this.colorRgb[i].g},${this.colorRgb[i].b}, 0)`, // 100%
},
],
},
},
// emphasis:{
// color:"#ff00ff"
// },
type: 'bar',
});
}
return { series: seriesDatas, legendData: legendDatas };
},
optionHandle() {
let colorDatas = this.colorDatas;
let changeData = this.getChangeData();
let offVal = this.yAxisName.length * 10 + 20;
let offArr = [0, 0, 0, -offVal];
let option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.65)',
borderColor: 'rgba(0,0,0,0.65)',
padding: [4, 8],
axisPointer: {
type: 'shadow',
shadowStyle: {
color: '#ffffff',
opacity: 0.08,
width: 'auto',
},
},
formatter: function (params) {
let res = '';
for (let i = 0; i < params.length; i++) {
let { name, data, seriesName } = params[i];
let color = colorDatas[i];
var resDiv = lineToolTips(name, color, data, seriesName, i);
res += resDiv;
}
return res;
},
},
legend: {
x: 'center', //
y: 'top', //
// padding:[25,10,0,0], //[]
show: true,
textStyle: {
color: '#FFFFFF',
opacity: 0.85,
},
itemHeight: 8,
itemWidth: 8,
itemGap: 28,
data: changeData.legendData,
// right: '24',
// bottom: '24',
top: '24',
},
grid: {
// top:'24',
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: {
type: 'category',
data: this.xAxis,
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
//
rotate: this.axisLabelRotate,
},
splitLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisTick: {
//x线
show: false,
},
},
yAxis: {
show: true,
name: this.yAxisName,
nameTextStyle: {
padding: offArr,
opacity: 0.34,
fontSize: 11,
},
nameLocation: 'end',
type: 'value',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
splitLine: {
show: false,
onZero: false,
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
formatter: function (v) {
return tranNumber(v);
},
},
axisTick: {
//y线
show: false,
},
},
series: changeData.series,
};
return option;
},
},
});
</script>

236
lib/component/echarts/components/chart/bar/SingleBar.vue

@ -0,0 +1,236 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BaseBar.vue';
import { tranNumber, colorTransferToRgb } from '../tool.js';
// import * as from 'echarts';
export default defineComponent({
name: 'MultipleBar',
extends: BaseBar,
props: {
xAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
yAxisName: {
type: String,
required: true,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: () => {
return ['#40E0F0', '#047CB6'];
},
},
borderColor: {
type: String,
default: '#24B0D4',
},
shadowColor: {
type: String,
default: '#2DBFDD',
},
},
data() {
return {
colorRgb: [],
borderColorRGB: {},
shadowColorRGB: {},
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < 2; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
this.borderColorRGB = colorTransferToRgb(this.borderColor);
this.shadowColorRGB = colorTransferToRgb(this.shadowColor);
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
for (let i = 0; i < this.series.length; i++) {
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
itemStyle: {
color: this.colorDatas[i],
},
});
seriesDatas.push({
name: this.series[i].name,
barMaxWidth: '24',
data: this.series[i].data,
showBackground: true,
backgroundStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{
offset: 0,
color: `rgba(${this.borderColorRGB.r},${this.borderColorRGB.g},${this.borderColorRGB.b}, 0.2)`, //color: 'rgba(64, 224, 240, 0.7)' // 0%
},
{
offset: 0.3,
color: `rgba(180, 180, 180, 0.1)`, // 100%
},
{
offset: 0.7,
color: `rgba(180, 180, 180, 0.1)`, // 100%
},
{
offset: 1,
color: `rgba(${this.borderColorRGB.r},${this.borderColorRGB.g},${this.borderColorRGB.b}, 0.2)`, // 100%
},
],
},
borderColor: `rgba(${this.borderColorRGB.r},${this.borderColorRGB.g},${this.borderColorRGB.b},1)`, //'rgba(36, 176, 212, 1)',
borderWidth: 0.5,
borderType: 'solid',
shadowColor: `rgba(${this.shadowColorRGB.r},${this.shadowColorRGB.g},${this.shadowColorRGB.b},1)`, //'rgba(45, 191, 221, 0.7)',
shadowBlur: 3,
},
label: {
show: true,
position: 'top',
color: '#fff',
fontWeight: 400,
},
// textStyle: {
// },
itemStyle: {
shadowColor: '#30C6E1',
shadowBlur: 4,
borderWidth: 0.5,
borderColor: '#00F0FF',
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[0].r},${this.colorRgb[0].g},${this.colorRgb[0].b},0.5)`, //color: 'rgba(64, 224, 240, 0.7)' // 0%
},
{
offset: 1,
color: `rgba(${this.colorRgb[1].r},${this.colorRgb[1].g},${this.colorRgb[1].b},0.7)`, // 100%
},
],
},
},
type: 'bar',
});
}
return { series: seriesDatas, legendData: legendDatas };
},
optionHandle() {
let colorDatas = this.colorDatas;
let changeData = this.getChangeData();
let yAxisName = this.yAxisName;
var offVal = yAxisName.length * 10 + 20;
var offArr = [0, 0, 0, -offVal];
var option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
grid: {
// top:'24',
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: {
type: 'category',
data: this.xAxis,
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
rotate: this.axisLabelRotate,
},
splitLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisTick: {
//x线
show: false,
},
},
yAxis: {
show: true,
name: this.yAxisName,
nameTextStyle: {
padding: offArr,
opacity: 0.34,
fontSize: 11,
},
nameLocation: 'end',
type: 'value',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
splitLine: {
show: false,
onZero: false,
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
formatter: function (v) {
return tranNumber(v);
},
},
axisTick: {
show: false,
},
},
series: changeData.series,
};
return option;
},
},
});
</script>

270
lib/component/echarts/components/chart/bar/StackedHorizontalBar.vue

@ -0,0 +1,270 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BaseBar.vue';
import { tranNumber, lineToolTips, colorTransferToRgb } from '../tool.js';
export default defineComponent({
name: 'MultipleBar',
extends: BaseBar,
props: {
yAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: () => {
return [
'#085779',
'#16C5EC',
'#087D76',
'#14FFF1',
'#135572',
'#1593C9',
'#04613F',
'#11D683',
];
},
},
},
data() {
return {
colorRgb: [],
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < this.colorDatas.length; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
for (let i = 0; i < this.series.length; i++) {
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
});
seriesDatas.push({
name: this.series[i].name,
type: 'bar',
barMaxWidth: '40%',
showBackground: false,
stack: 'total',
barMinWidth: 5,
label: {
show: true,
color: '#FFFFFF',
distance: 0,
formatter: function (params) {
if (params.value > 0) {
return params.value;
} else {
return '';
}
},
// position:'insideRight'
},
emphasis: {
focus: 'series',
showBackground: false,
},
data: this.series[i].data,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[i * 2].r},${this.colorRgb[i * 2].g},${
this.colorRgb[i * 2].b
},1)`, // 0% 处的颜色
},
{
offset: 1,
color: `rgba(${this.colorRgb[i * 2 + 1].r},${this.colorRgb[i * 2 + 1].g},${
this.colorRgb[i * 2 + 1].b
}, 1)`, // 100% 处的颜色
},
],
},
},
type: 'bar',
});
}
return { series: seriesDatas, legendData: legendDatas };
},
optionHandle() {
let colorDatas = this.colorDatas;
let changeData = this.getChangeData();
var option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.65)',
borderColor: 'rgba(0,0,0,0.65)',
padding: [4, 8],
axisPointer: {
type: 'shadow',
shadowStyle: {
color: '#ffffff',
opacity: 0.08,
width: 'auto',
},
},
formatter: function (params) {
let res = '';
for (let i = 0; i < params.length; i++) {
let { name, data, seriesName } = params[i];
let color = colorDatas[i * 2];
var resDiv = lineToolTips(name, color, data, seriesName, i);
res += resDiv;
}
return res;
},
},
legend: {
x: 'right', //
y: 'top', //
padding: [0, 24, 0, 0], //[]
show: true,
textStyle: {
color: '#FFFFFF',
opacity: 0.85,
},
itemHeight: 8,
itemWidth: 8,
itemGap: 28,
data: changeData.legendData,
top: '24',
},
grid: {
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: [
{
type: 'value',
position: 'bottom',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
},
splitLine: {
show: true,
interval: 2,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisTick: {
//x线
show: false,
},
},
{
type: 'value',
position: 'top',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
splitLine: {
show: true,
interval: 2,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisTick: {
//x线
show: false,
},
},
],
yAxis: {
type: 'category',
data: this.yAxis,
show: true,
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#ffffff',
width: 1,
opacity: 0.2,
},
},
splitLine: {
show: false,
onZero: false,
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
rotate: this.axisLabelRotate,
},
axisTick: {
//y线
show: false,
},
},
series: changeData.series,
};
return option;
},
},
});
</script>

26
lib/component/echarts/components/chart/chartData.json

@ -0,0 +1,26 @@
{
"line":{
"name":"折线图表",
"LineStackedArea":{
"id":"lsa",
"name":"面积折线图",
"component":"LineStackedConmmon.vue",
"boxWidth":700,
"boxHeight":500,
"smooth":1
},
"LineStack":{
"id":"ls",
"name":"折线图",
"component":"LineStackedConmmon.vue",
"boxWidth":700,
"boxHeight":500,
"smooth":0
}
}
}

43
lib/component/echarts/components/chart/gauge/BaseGauge.vue

@ -0,0 +1,43 @@
<!-- @format -->
<template>
<div class="base-gauge" id="basegauge" ref="BaseGaugeRef"></div>
</template>
<script>
import { defineComponent } from 'vue';
import { init } from 'echarts';
export default defineComponent({
name: 'BaseGauge',
props: {},
data() {
return {
options: null,
};
},
created() {},
mounted() {},
methods: {
setOption(obj) {
this.options = obj;
},
getOption() {
return this.options;
},
renderChart() {
if (!this.chartbox) {
this.chartbox = init(this.$refs.BaseGaugeRef, null, { renderer: 'svg' });
}
this.chartbox.clear();
let option = this.getOption();
this.chartbox.setOption(option, { notMerge: false });
},
},
});
</script>
<style lang="less">
.base-gauge {
height: 100%;
width: 100%;
}
</style>

309
lib/component/echarts/components/chart/gauge/SimpleGauge.vue

@ -0,0 +1,309 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BaseGauge.vue';
export default defineComponent({
name: 'SimpleGauge',
extends: BaseBar,
props: {
datas: {
type: Array,
required: true,
},
min: {
type: Number,
required: true,
},
max: {
type: Number,
required: true,
},
progress: {
type: Number,
required: true,
},
label: {
type: String,
default: '',
},
color: {
type: String,
default: '#73FBFD',
},
textColor: {
type: String,
default: '#C0FAFF',
},
},
data() {
return {};
},
computed: {},
created() {},
mounted() {
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
optionHandle() {
let _this = this;
let option = {
series: [
{
radius: '80%',
type: 'gauge',
detail: { show: false },
axisLine: {
lineStyle: {
color: [[1, _this.color]],
width: 2,
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
type: 'gauge',
min: _this.min,
max: _this.max,
radius: '75%',
splitNumber: 12,
itemStyle: {
color: _this.color,
},
center: ['50%', '50%'],
progress: {
show: true,
width: _this.progress,
itemStyle: {
color: _this.color,
opacity: 0.7,
},
},
axisLine: {
show: false,
},
pointer: {
icon: 'path://M1.6861, 33.5189L1.33874, 1.07101L2.2249, 1.06152L2.57226, 33.5094L1.6861, 33.5189Z,M0.649407, 20.0229L0.760354, 0.860351L3.41895, 0.875744L3.308, 20.0382L0.649407, 20.0229Z',
// keepAspect:true,
// icon:'path://M7.15528, 44.1242C8.9665, 44.1242, 10.4348, 42.656, 10.4348, 40.8447C10.4348, 39.0335, 8.9665, 37.5652, 7.15528, 37.5652C5.34406, 37.5652, 3.87578, 39.0335, 3.87578, 40.8447C3.87578, 42.656, 5.34406, 44.1242, 7.15528, 44.1242ZM7.15528, 48C11.107, 48, 14.3106, 44.7965, 14.3106, 40.8447C14.3106, 36.893, 11.107, 33.6895, 7.15528, 33.6895C3.20353, 33.6895, 0, 36.893, 0, 40.8447C0, 44.7965, 3.20353, 48, 7.15528, 48Z,M6.70801, 0H7.60242V33.9876H6.70801V0Z,M5.81348 0H8.49671V19.2298H5.81348V0Z',
// icon:'path://M7.15528 44.1242C8.9665 44.1242 10.4348 42.656 10.4348 40.8447C10.4348 39.0335 8.9665 37.5652 7.15528 37.5652C5.34406 37.5652 3.87578 39.0335 3.87578 40.8447C3.87578 42.656 5.34406 44.1242 7.15528 44.1242ZM7.15528 48C11.107 48 14.3106 44.7965 14.3106 40.8447C14.3106 36.893 11.107 33.6895 7.15528 33.6895C3.20353 33.6895 0 36.893 0 40.8447C0 44.7965 3.20353 48 7.15528 48Z,M6.70801 0H7.60242V33.9876H6.70801V0Z,M5.81348 0H8.49671V19.2298H5.81348V0Z',
length: '75%',
},
// anchor:{
// show: true,
// size:6,
// icon:'circle',
// itemStyle:{
// color:'#fff'
// }
// },
axisTick: {
show: false,
},
splitLine: {
show: false,
},
axisLabel: {
show: false,
},
detail: {
color: _this.textColor,
fontSize: 26,
fontWeight: 400,
offsetCenter: [0, '70%'],
formatter: function (value) {
// '{80|' + value.toFixed(0) + '}{unit|%}';
return `${value}{unit|${_this.label}}`;
},
rich: {
unit: {
fontSize: 9,
color: _this.textColor,
padding: [0, 0, -15, 3],
},
},
},
data: _this.datas,
},
{
radius: '58%',
type: 'gauge',
detail: { show: false },
axisLine: {
// 线
lineStyle: {
// lineStyle线
color: [[1, _this.color]],
width: 2,
opacity: 0.2,
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
radius: '58%',
type: 'gauge',
detail: { show: false },
axisLine: {
lineStyle: {
color: [[1, _this.color]],
width: 16,
opacity: 0.2,
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
//1
type: 'pie',
radius: '25%',
center: ['50%', '50%'],
emphasis: {
disabled: true,
},
animation: false,
itemStyle: {
color: '#6DB9C6',
opacity: 0.2,
label: {
show: false,
},
labelLine: {
show: false,
},
},
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
{
//1
type: 'pie',
radius: '20%',
emphasis: {
disabled: true,
},
animation: false,
center: ['50%', '50%'],
itemStyle: {
color: '#6DB9C6',
opacity: 0.2,
label: {
show: false,
},
labelLine: {
show: false,
},
},
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
{
//1
type: 'pie',
radius: ['8%', '4%'],
emphasis: {
disabled: true,
},
animation: false,
center: ['50%', '50%'],
itemStyle: {
color: '#6DB9C6',
opacity: 1,
label: {
show: false,
},
labelLine: {
show: false,
},
},
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
{
//1
type: 'pie',
radius: '4%',
emphasis: {
disabled: true,
},
animation: false,
center: ['50%', '50%'],
itemStyle: {
color: '#274348',
opacity: 1,
label: {
show: false,
},
labelLine: {
show: false,
},
},
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
],
};
return option;
},
},
});
</script>

398
lib/component/echarts/components/chart/gauge/index.vue

@ -0,0 +1,398 @@
<!-- @format -->
<template>
<div class="content-chart">
<div class="chartContainer1" id="chartContainer1" ref="ChartEl1"></div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import * as echarts from 'echarts';
// import { get_all_data } from './main';
export default defineComponent({
name: 'Dashboard',
props: {},
data() {
return {
// map: {},
// growthRatioRate: null,
// complaintSumCount: 0, //
// todayComplaintCount: 0, //
// sumCount: null, //
// todaySumCount: null, //
// replyCount: null, //
// growthRatioType: null,
// valueStyle: `background: linear-gradient(180deg, #E1F5FE 0%, #E1F5FE 28.7%, #4DC6F5 31.13%, #11B6EE 56.06%);
// -webkit-background-clip:
// text;color: transparent;
// font-size:32px;
// height:40px;
// line-height: 40px;
// font-weight: bold;
// letter-spacing:0px;`,
// chart: null,
// chart1: null,
// dataSource: [],
// selectItem: '',
// timer: {},
};
},
watch: {},
created() {},
mounted() {
// debugger;
// var jsonData = get_all_data();
this.setChart();
},
methods: {
setChart() {
// debugger;
if (!this.chart1) {
this.chart1 = echarts.init(this.$refs.ChartEl1, null, { renderer: 'svg' });
}
this.chart1.clear();
let options1 = {
series: [
{
radius: '80%',
type: 'gauge',
detail: { show: false },
axisLine: {
// 线
lineStyle: {
// lineStyle线
color: [[1, '#0EE5E5']],
width: 2,
// ,
// shadowColor: '#fff', //
// shadowBlur: 10
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
type: 'gauge',
// startAngle: -45,
// endAngle: 180,
min: 0,
max: 180,
radius: '75%',
splitNumber: 12,
itemStyle: {
color: 'rgb(82,180,182)',
},
center: ['50%', '50%'],
progress: {
show: true,
// roundCap: true,
width: 18,
itemStyle: {
// borderWidth: 2,
// borderColor: "rgba(227, 18, 18, 1)",
// borderType: "solid",
// borderCap: "square",
// borderMiterLimit:20,
// borderDashOffset:15,
// shadowColor: 'rgba(250, 250, 0, 0.5)',
// shadowBlur: 10,
// shadowOffsetY:30,
// borderType: [15, 15],
// borderSolidOffset: 5,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#73FBFD', // 0%
},
{
offset: 1,
color: '#73FBFD', // 100%
},
],
global: false,
},
},
},
axisLine: {
show: false,
// roundCap: true,
// lineStyle: {
// color: [
// [0, '#1B444F'],
// [1, '#1B4488'],
// ],
// width: 18,
// },
},
// axisLine: { // 线(线)
// show: true, // 线(线), true
// lineStyle: { // 线
// color: [
// [1,new echarts.graphic.LinearGradient(0, 0, 1, 0, [
// {
// offset: 0.1,
// color: "#AAF02E"
// },
// {
// offset: 0.3,
// color: "#CEE326"
// },
// {
// offset: 0.6,
// color: "#EBD212"
// },
// {
// offset: 1,
// color: "#FF6A00"
// }
// ])
// ]
// ],
// // color: colorTemplate1, //线 ([0,1]) [[0.2, '#91c7ae'], [0.8, '#63869e'], [1, '#c23531']]
// opacity: 0.8, // 0 1 0
// width: 15, //线, 30
// shadowBlur: 20, //() shadowColor,shadowOffsetX, shadowOffsetY
// shadowColor: "#fff", //color
// }
// },
pointer: {
//circle', 'rect', 'roundRect' 'triangle', 'diamond', 'pin', 'arrow', 'none'
// icon:'path://M1.28582 2.53393L34.9873 16.8505,M1.28582 2.53393L34.9873 16.8505,M1.28564 1.98535L21.0455 10.7669',
length: '75%',
},
anchor: {
show: true,
size: 6,
icon: 'circle',
itemStyle: {
color: '#fff',
},
},
axisTick: {
show: false,
},
splitLine: {
show: false,
// distance: 2,
// length: 6,
// show: true,
// lineStyle: {
// width: 1,
// },
},
axisLabel: {
show: false,
},
detail: {
color: '#0FB9DC',
fontSize: 28,
fontWeight: 'bold',
offsetCenter: [0, '50%'],
formatter: function (value) {
// return 80 + '%';
return '{80|' + value.toFixed(0) + '}{unit|%}';
},
rich: {
// value: {
// fontSize: 50,
// fontWeight: 'bolder',
// color: '#777000'
// },
unit: {
fontSize: 15,
color: '#0FB9DC',
padding: [0, 0, -15, 2],
},
},
},
data: [
{
value: 80,
name: '',
},
],
},
{
radius: '58%',
type: 'gauge',
detail: { show: false },
axisLine: {
// 线
lineStyle: {
// lineStyle线
color: [[1, '#0EE5E5']],
width: 2,
opacity: 0.4,
// ,
// shadowColor: '#fff', //
// shadowBlur: 10
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
radius: '58%',
type: 'gauge',
detail: { show: false },
axisLine: {
// 线
lineStyle: {
// lineStyle线
color: [[1, '#0EE5E5']],
width: 16,
opacity: 0.2,
// ,
// shadowColor: '#fff', //
// shadowBlur: 10
},
},
axisLabel: {
show: false,
},
splitLine: {
show: false,
},
axisTick: {
show: false,
},
pointer: {
show: false,
},
},
{
//1
type: 'pie',
radius: '20%',
center: ['50%', '50%'],
z: 1,
itemStyle: {
normal: {
color: '#0EE5E5',
opacity: 0.2,
label: {
show: false,
},
labelLine: {
show: false,
},
},
},
hoverAnimation: false,
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
{
//1
type: 'pie',
radius: '15%',
center: ['50%', '50%'],
z: 1,
itemStyle: {
normal: {
color: '#0EE5E5',
opacity: 0.2,
label: {
show: false,
},
labelLine: {
show: false,
},
},
},
hoverAnimation: false,
label: {
show: false,
},
tooltip: {
show: false,
},
data: [100],
},
// ,
// {
// radius: '20%',
// type: 'gauge',
// startAngle:0,
// endAngle:360,
// detail: {show:false},
// axisLine: { // 线
// lineStyle: { // lineStyle线
// color: [ [1, '#0EE5E5']],
// width: 16,
// opacity:0.2
// // ,
// // shadowColor: '#fff', //
// // shadowBlur: 10
// }
// },
// axisLabel:{
// show:false
// },
// splitLine:{
// show:false
// },
// axisTick:{
// show:false
// },
// pointer:{
// show:false
// }
// }
],
};
this.chart1.setOption(options1, { notMerge: false });
},
},
});
</script>
<style lang="less" scoped>
.content-chart {
margin: 30px auto;
padding: 0 16px;
width: 300px;
height: 300px;
// position: relative;
.chartContainer1 {
height: 100%;
width: 100%;
transform: translateX(-20px);
}
}
</style>

43
lib/component/echarts/components/chart/line/BaseLine.vue

@ -0,0 +1,43 @@
<!-- @format -->
<template>
<div class="base-line" id="baseLine" ref="BaseLineRef"></div>
</template>
<script>
import { defineComponent } from 'vue';
import { init } from 'echarts';
export default defineComponent({
name: 'BaseLine',
props: {},
data() {
return {
options: null,
};
},
created() {},
mounted() {},
methods: {
setOption(obj) {
this.options = obj;
},
getOption() {
return this.options;
},
renderChart() {
if (!this.chartbox) {
this.chartbox = init(this.$refs.BaseLineRef, null, { renderer: 'svg' });
}
this.chartbox.clear();
let option = this.getOption();
this.chartbox.setOption(option, { notMerge: false });
},
},
});
</script>
<style lang="less">
.base-line {
height: 100%;
width: 100%;
}
</style>

222
lib/component/echarts/components/chart/line/LineStacked.vue

@ -0,0 +1,222 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseLine from './BaseLine.vue';
import { tranNumber, lineToolTips, colorTransferToRgb } from '../tool.js';
export default defineComponent({
name: 'LineStackedArea',
extends: BaseLine,
props: {
xAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
yAxisName: {
type: String,
required: true,
},
smooth: {
type: Boolean,
default: false,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: ['#32C5FF', '#0CCDC3', '#2FC25B'],
},
},
data() {
return {
colorRgb: [],
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < this.colorDatas.length; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
var xAxisInterval = 0;
var maxLen = 0;
for (let i = 0; i < this.series.length; i++) {
if (i == 0) {
maxLen = this.series[i].data.length;
} else if (this.series[i].data.length > maxLen) {
maxLen = this.series[i].data.length;
}
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
});
seriesDatas.push({
name: this.series[i].name,
type: 'line',
smooth: this.smooth,
showSymbol: false,
lineStyle: {
width: 2,
color: this.colorDatas[i],
},
itemStyle: {
color: this.colorDatas[i],
},
emphasis: {
itemStyle: {
focus: 'series',
color: '#2A2A2A',
borderColor: this.colorDatas[i],
borderWidth: 2,
},
},
data: this.series[i].data,
});
}
if (maxLen > 15) {
xAxisInterval = Math.ceil(maxLen / 15) - 1;
}
return { series: seriesDatas, legendData: legendDatas, xAxisInterval: xAxisInterval };
},
optionHandle() {
let mouseCurValue = 0;
let changeData = this.getChangeData();
let yAxisName = this.yAxisName;
var offVal = yAxisName.length * 10 + 20;
var offArr = [0, 0, 0, -offVal];
let option = {
animationDuration: 500,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.65)',
borderColor: 'rgba(0,0,0,0.65)',
padding: [4, 8],
axisPointer: {
type: 'line',
axis: 'auto',
},
formatter: function (params) {
let res = '',
sum = 0;
let minOffset = -1;
let minIndex = -1;
for (let i = 0; i < params.length; i++) {
let { name, color, data, seriesName } = params[i];
var resDiv = lineToolTips(name, color, data, seriesName, i);
res += resDiv;
}
return res;
},
},
legend: {
textStyle: {
color: '#FFFFFF',
opacity: 0.85,
},
itemHeight: 8,
itemWidth: 8,
itemGap: 28,
data: changeData.legendData,
right: '24',
bottom: '24',
top: '24',
},
grid: {
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: {
type: 'category',
data: this.xAxis,
axisTick: {
show: true,
alignWithLabel: true,
inside: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
interval: changeData.xAxisInterval,
rotate: this.axisLabelRotate,
},
axisLine: {
onZero: false,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
},
yAxis: {
show: true,
name: yAxisName,
nameTextStyle: {
padding: offArr,
opacity: 0.34,
fontSize: 11,
},
nameLocation: 'end',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
splitLine: {
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
formatter: function (v) {
return tranNumber(v);
},
},
},
series: changeData.series,
};
return option;
},
},
});
</script>

248
lib/component/echarts/components/chart/line/LineStackedArea.vue

@ -0,0 +1,248 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseLine from './BaseLine.vue';
import { tranNumber, lineToolTips, colorTransferToRgb } from '../tool.js';
export default defineComponent({
name: 'LineStackedArea',
extends: BaseLine,
props: {
xAxis: {
type: Array,
required: true,
},
series: {
type: Array,
required: true,
},
yAxisName: {
type: String,
required: true,
},
smooth: {
type: Boolean,
default: true,
},
axisLabelRotate: {
type: Number,
default: 0,
},
colorDatas: {
type: Array,
default: ['#32C5FF', '#0CCDC3', '#2FC25B'],
},
},
data() {
return {
colorRgb: [],
};
},
computed: {},
created() {},
mounted() {
for (let i = 0; i < this.colorDatas.length; i++) {
this.colorRgb.push(colorTransferToRgb(this.colorDatas[i]));
}
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
getChangeData() {
var seriesDatas = [];
var legendDatas = [];
var xAxisInterval = 0;
var maxLen = 0;
for (let i = 0; i < this.series.length; i++) {
if (i == 0) {
maxLen = this.series[i].data.length;
} else if (this.series[i].data.length > maxLen) {
maxLen = this.series[i].data.length;
}
legendDatas.push({
name: this.series[i].name,
icon: 'rect',
});
seriesDatas.push({
name: this.series[i].name,
type: 'line',
smooth: this.smooth,
showSymbol: false,
lineStyle: {
width: 2,
},
tooltip: {
show: true,
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: `rgba(${this.colorRgb[i].r},${this.colorRgb[i].g},${this.colorRgb[i].b},0.2)`, // 0%
},
{
offset: 1,
color: `rgba(${this.colorRgb[i].r},${this.colorRgb[i].g},${this.colorRgb[i].b}, 0)`, // 100%
},
],
global: false, // false
},
},
lineStyle: {
width: 2,
color: this.colorDatas[i],
},
itemStyle: {
color: this.colorDatas[i],
},
emphasis: {
itemStyle: {
focus: 'series',
color: '#2A2A2A',
borderColor: this.colorDatas[i],
borderWidth: 2,
},
},
data: this.series[i].data,
});
}
if (maxLen > 15) {
xAxisInterval = Math.ceil(maxLen / 15) - 1;
}
return { series: seriesDatas, legendData: legendDatas, xAxisInterval: xAxisInterval };
},
optionHandle() {
let mouseCurValue = 0;
let changeData = this.getChangeData();
let yAxisName = this.yAxisName;
var offVal = yAxisName.length * 10 + 20;
var offArr = [0, 0, 0, -offVal];
let option = {
animationDuration: 500,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.65)',
borderColor: 'rgba(0,0,0,0.65)',
padding: [4, 8],
axisPointer: {
type: 'line',
axis: 'auto',
},
formatter: function (params) {
let res = '',
sum = 0;
let minOffset = -1;
let minIndex = -1;
for (let i = 0; i < params.length; i++) {
let { name, color, data, seriesName } = params[i];
var resDiv = lineToolTips(name, color, data, seriesName, i);
res += resDiv;
}
return res;
},
},
legend: {
textStyle: {
color: '#FFFFFF',
opacity: 0.85,
},
itemHeight: 8,
itemWidth: 8,
itemGap: 28,
data: changeData.legendData,
right: '24',
bottom: '24',
top: '24',
},
grid: {
// top:'24',
left: '24',
right: '24',
bottom: '24',
containLabel: true,
},
xAxis: {
type: 'category',
data: this.xAxis,
axisTick: {
show: true,
alignWithLabel: true,
inside: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
interval: changeData.xAxisInterval,
rotate: this.axisLabelRotate,
},
axisLine: {
onZero: false,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
},
yAxis: {
show: true,
name: yAxisName,
nameTextStyle: {
padding: offArr,
opacity: 0.34,
fontSize: 11,
},
nameLocation: 'end',
axisLine: {
show: true,
onZero: false,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
splitLine: {
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.2,
},
},
axisLabel: {
show: true,
opacity: 0.65,
fontSize: 11,
formatter: function (v) {
return tranNumber(v);
},
},
},
series: changeData.series,
};
return option;
},
},
});
</script>

43
lib/component/echarts/components/chart/pie/BasePie.vue

@ -0,0 +1,43 @@
<!-- @format -->
<template>
<div class="base-pie" id="basepie" ref="BasePieRef"></div>
</template>
<script>
import { defineComponent } from 'vue';
import { init } from 'echarts';
export default defineComponent({
name: 'BasePie',
props: {},
data() {
return {
options: null,
};
},
created() {},
mounted() {},
methods: {
setOption(obj) {
this.options = obj;
},
getOption() {
return this.options;
},
renderChart() {
if (!this.chartbox) {
this.chartbox = init(this.$refs.BasePieRef, null, { renderer: 'svg' });
}
this.chartbox.clear();
let option = this.getOption();
this.chartbox.setOption(option, { notMerge: false });
},
},
});
</script>
<style lang="less">
.base-pie {
height: 100%;
width: 100%;
}
</style>

219
lib/component/echarts/components/chart/pie/PieLabelLineAlign.vue

@ -0,0 +1,219 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseBar from './BasePie.vue';
export default defineComponent({
name: 'PieLabelLineAlign',
extends: BaseBar,
props: {
series: {
type: Array,
required: true,
},
borderColor: {
type: String,
required: true,
},
colorDatas: {
type: Array,
default: () => {
return [
'#15B0A7',
'#2193D3',
'#2D71D8',
'#10AAB4',
'#1AA5E1',
'#15B0A7',
'#1AA5E1',
'#30BB78',
'#04C19F',
'#37B2E7',
'#397ADC',
];
},
},
},
data() {
return {};
},
computed: {},
created() {},
mounted() {
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
optionHandle() {
let clientWidth = parseInt(this.$refs.BasePieRef.clientWidth);
let colorDatas = this.colorDatas;
let colorLen = colorDatas.length;
let borderColor = this.borderColor;
let { name: titleName, data: seriesDatas, total } = this.series[0];
let option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '44%',
z: 5,
style: {
text: titleName,
textAlign: 'center',
font: '400 0.563em "Microsoft YaHei"',
fill: '#EBF9FF',
width: 30,
height: 30,
},
},
{
type: 'text',
left: 'center',
top: '49%',
z: 5,
style: {
text: total,
textAlign: 'center',
font: '500 1.8125em "Microsoft YaHei"',
fill: '#E1F5FE',
width: 30,
height: 30,
},
},
],
},
series: [
{
name: titleName,
type: 'pie',
radius: ['35%', '50%'],
itemStyle: {
borderColor: borderColor,
borderWidth: 2,
color: function (params) {
var idx = params.dataIndex;
return colorDatas[idx % colorLen];
// debugger;
},
},
textStyle: { fontSize: '12', fontFamily: 'Microsoft YaHei' },
data: seriesDatas.map((item, index) => {
item.label = {
color: colorDatas[index % colorLen],
};
return item;
}),
label: {
show: true,
alignTo: 'labelLine',
minMargin: 8,
edgeDistance: 20,
lineHeight: 15,
// 'value'
formatter: '{value|{b}} {c} {d}%',
rich: {
// 'value'
value: {
fontSize: 11,
color: '#EFFFFF',
opacity: 0.65,
},
},
// color:function (params){
// var idx = params.dataIndex;
// return colorDatas[idx%colorLen];
// // debugger;
// },
// formatter: function(val){
// debugger;
// let data = val.data;
// let rStr = "";
// let index = -1;
// for(let key in data){
// index += 1;
// let m = 'mark'+index;
// rStr += data[key]+"-";//`{${m}|${data[key]}}`
// }
// return rStr;
// },
// rich: {
// mark0:{
// fontSize: 11,
// color:'#EFFFFF',
// opacity:0.65
// }
// ,
// mark1:{
// padding: [0,0,0,8],
// color:'#397ADC',
// opacity:0.65
// },
// mark2:{
// padding: [0,0,0,8],
// color:'#397ADC',
// opacity:0.65
// }
// }
},
labelLine: {
show: true,
lineStyle: { color: '#FFFFFF', opacity: 0.35 },
length: 10,
length2: 2,
maxSurfaceAngle: 60,
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < clientWidth / 2;
const points = params.labelLinePoints;
params.labelRect.y = params.labelRect.y - 7;
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
dy: -8,
labelLinePoints: points,
};
},
},
{
name: 'bg',
type: 'pie',
radius: '30%',
showEmptyCircle: true,
emptyCircleStyle: {
color: '#00667C',
opacity: 0.3,
},
},
{
name: 'bg2',
type: 'pie',
radius: '25%',
showEmptyCircle: true,
emptyCircleStyle: {
color: '#027D98',
borderWidth: 1,
shadowColor: 'rgba(2, 125, 152, 1)',
shadowBlur: 2,
opacity: 0.4,
},
},
],
};
return option;
},
},
});
</script>

43
lib/component/echarts/components/chart/radar/BaseRadar.vue

@ -0,0 +1,43 @@
<!-- @format -->
<template>
<div class="base-radar" id="baseradar" ref="BaseRadarRef"></div>
</template>
<script>
import { defineComponent } from 'vue';
import { init } from 'echarts';
export default defineComponent({
name: 'BaseRadar',
props: {},
data() {
return {
options: null,
};
},
created() {},
mounted() {},
methods: {
setOption(obj) {
this.options = obj;
},
getOption() {
return this.options;
},
renderChart() {
if (!this.chartbox) {
this.chartbox = init(this.$refs.BaseRadarRef, null, { renderer: 'svg' });
}
this.chartbox.clear();
let option = this.getOption();
this.chartbox.setOption(option, { notMerge: false });
},
},
});
</script>
<style lang="less">
.base-radar {
height: 100%;
width: 100%;
}
</style>

141
lib/component/echarts/components/chart/radar/CustomizedRadar.vue

@ -0,0 +1,141 @@
<!-- @format -->
<script>
import { defineComponent } from 'vue';
import BaseRadar from './BaseRadar.vue';
export default defineComponent({
name: 'BasicRadar',
extends: BaseRadar,
props: {
series: {
type: Array,
required: true,
},
indicator: {
type: Array,
required: true,
},
radius: {
type: String,
default: '75%',
},
},
data() {
return {};
},
computed: {},
created() {},
mounted() {
let optionData = this.optionHandle();
this.setOption(optionData);
this.renderChart();
},
methods: {
optionHandle() {
let _this = this;
let index = 0;
let color = [
'#0075FF',
'#7a7b82',
'#B2BCF3',
'#655ADC',
'#D04CFF',
'#F14A4A',
'#EB9260',
'#E8EB60',
'#3FE280',
'#3FC5E2',
];
let richObj = {
top: {
color: '#10C8D8',
fontFamily: 'Microsoft YaHei',
fontSize: 20,
fontWeight: 500,
align: 'left',
padding: [0, 0, 5, 10],
},
//
end: {
color: 'rgba(255, 255, 250, 0.65)',
fontFamily: 'Microsoft YaHei',
fonSize: 12,
fontWeight: 400,
align: 'center',
},
};
for (let i = 0; i < color.length; i++) {
var obj = { height: 6, width: 6, align: 'center', backgroundColor: color[i] };
richObj['rect' + (i + 1)] = obj;
}
let option = {
animationDuration: 300,
animationEasing: 'linear',
textStyle: {
color: '#fff',
fontFamily: 'Microsoft YaHei',
},
radar: {
radius: '70%',
splitNumber: 2, //
axisName: {
formatter: (params) => {
// 使formatter使`{|}`
// debugger;
// console.log(params);
let a = params.split(',')[0];
let b = params.split(',')[1];
// `${0}px`
index++;
var rect = 'rect' + index;
return `{top|${a}}\n{${rect}|} {end|${b}}`;
// return '{top|'+a+'}' + '\n' + '{rect|}{end|'+b+'}'
},
//
rich: richObj,
},
// 线
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 205, 0.05)',
},
},
splitArea: {
show: false,
areaStyle: {
color: 'rgba(255,0,0,0)', //
},
},
splitLine: {
show: true,
lineStyle: {
width: 2,
color: ['rgba(255, 255, 255, 0.1)', 'rgba(6, 235, 238, 0.1)'],
// color: 'rgba(255, 255, 255, 0.1)', //
},
},
indicator: _this.indicator,
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
symbol: 'none',
lineStyle: {
color: '#06EBEE',
width: 2,
},
areaStyle: {
color: 'rgba(6, 235, 238, 0.1)',
},
data: _this.series,
},
],
};
return option;
},
},
});
</script>

89
lib/component/echarts/components/chart/tool.js

@ -0,0 +1,89 @@
import chartdatas from './chartData.json';
const chartDatas = chartdatas;
export function getAllChartdatas(){
return chartDatas;
}
export function getChartData(name,childName){
return chartDatas[name][childName];
}
export function tranNumber(num){
let numStr = num.toString();
let numV = 0;
// 十万以内直接返回
if (numStr.length < 5 ) {
return numStr;
}else if(numStr.length>=5 && numStr.length<=7){
// 单位用K(千)
numV =parseInt(num/1000);
return numV+'K';
}else if(numStr.length>=8 && numStr.length<=10){
numV =parseInt(num/10000);
return numV+'W';
}else if(numStr.length>=11){
numV =parseInt(num/100000000);
return numV+'M';
}
};
// 折线 图表 统一弹窗
/**
*
*
* @export
* @param {*} name 标题
* backgrouncolor 背景色
* @param {*} data 数据
* @param {*} seriesName 系列名
* @param {*} label 数据后面的单位
*/
export function lineToolTips(name,backgrouncolor,data,seriesName,index){
// var res = `<div>
// </div>`
// var top = index==0?0:15;
var res = `<div style="font-size:12px; color:#ffffff;text-align:left;">
<span style="display:inline-block;margin-right:0px;width:8px;height:8px;border-radius:0px;background-color:${backgrouncolor};"></span>
<span style="margin-right:3px;opacity: 0.65;">${seriesName}</span>
<span style="opacity: 0.65;">${name}</span>
<span style="font-weight:400;color:#fff;margin-left:3px;">
${data}</span>
</div>`
return res;
}
/**
*
*
* @export 16进制颜色值转RGB
* @param {*} color
* @return {*}
*/
export function colorTransferToRgb(color) {
var colorObj = {r:0,g:0,b:0};
if (typeof color !== 'string' && !(color instanceof String)) return console.error("请输入16进制字符串形式的颜色值");
color = color.charAt(0) === '#' ? color.substring(1) : color;
if (color.length !== 6 && color.length !== 3) return console.error("请输入正确的颜色值")
if (color.length === 3) {
color = color.replace(/(\w)(\w)(\w)/, '$1$1$2$2$3$3')
}
var reg = /\w{2}/g;
var colors = color.match(reg);
for (var i = 0; i < colors.length; i++) {
colors[i] = parseInt(colors[i], 16).toString();
if(i==0){
colorObj.r = parseInt(colors[i]);
}else if(i==1){
colorObj.g = parseInt(colors[i]);
}else if(i==2){
colorObj.b = parseInt(colors[i]);
}
}
return colorObj;
}

293
lib/component/echarts/components/other/BasicTable copy 2.vue

@ -0,0 +1,293 @@
<template>
<div class="table-view" id="tableView">
<div class="thead">
<div v-for="(value,key,index) in theadDatas" :key="index" :class="'flex'+index">{{value}}</div>
</div>
<div class="tbodymain">
<div class="tbody b1" ref="tbody1">
<div v-for="item in tableDatas1" :key="item.id" class="row">
<div v-for="(value,key,index) in item" :key="index" :class="'flex'+index"><span :class=[timeHandle(key),colorHandle(key,value)]>{{value}}</span></div>
</div>
</div>
<div class="tbody b2" ref="tbody2">
<div v-for="item in tableDatas2" :key="item.id" class="row">
<div v-for="(value,key,index) in item" :key="index" :class="'flex'+index"><span :class=[timeHandle(key),colorHandle(key,value)]>{{value}}</span></div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
// import { init } from './js/index.js'
export default defineComponent({
name: 'baisicTable',
props: {},
data() {
return {
timeOut:null,
srolltime:null,
height:26,
tableDatas:[
{num:"沪866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"黄色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"黄色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"绿色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"绿色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"黄色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"黄色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"绿色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"绿色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"赣866B61",color:"蓝色",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"}
],
theadDatas:{num:"车牌号",color:"车牌颜色",positon:"违停地点",time:"违停时间"},
tableDatas1:[],
tableDatas2:[],
pageIndex:0,
}
},
created() {
if(this.tableDatas.length>=14){
for(let i=0;i<7;i++){
this.tableDatas1.push(this.tableDatas[i]);
this.tableDatas2.push(this.tableDatas[i+7]);
}
}else{
}
},
mounted() {
// init();
if(this.tableDatas.length>7){
this.timeIntervalHandle();
}else{
this.tableDatas1 = this.tableDatas;
}
},
methods: {
timeIntervalHandle(){
// debugger;
console.log(this.$refs.tbody1);
// this.$refs.tbody1.scrollTop = -20;
this.$refs.tbody1.style.top = `${0}px`;
this.$refs.tbody2.style.top = `${182}px`;
// console.log(this.$refs.tbody1.scrollTop);
this.srollHanle();
},
changDatas(){
},
srollHanle(){
let scrollH = 0;
let _this = this;
this.srolltime = setInterval(function(){
scrollH += 2;
// debugger;
_this.$refs.tbody1.style.top = (parseInt(_this.$refs.tbody1.style.top) - 2)+"px";
_this.$refs.tbody2.style.top = (parseInt(_this.$refs.tbody2.style.top) - 2)+"px";
if(scrollH>=26){
clearInterval(_this.srolltime);
if(parseInt(_this.$refs.tbody1.style.top)<=-182){
_this.$refs.tbody1.style.top = 182+'px';
// _this.$refs.tbody2.style.top = 0+'px';
}else if(parseInt(_this.$refs.tbody2.style.top)<=-182){
_this.$refs.tbody2.style.top = 182+'px';
// _this.$refs.tbody1.style.top = 0+'px';
}
_this.timeOut = setTimeout(_this.srollHanle,1500);
}
},20)
},
timeHandle(key){
// debugger;
if(key=="time"){
return 'time';
}else{
return '';
}
},
colorHandle(key,value){
// debugger;
if(key=="color"){
if(value.indexOf('蓝') != -1){
return 'blue';
}else if(value.indexOf('黄') != -1){
return 'yellow';
}else if(value.indexOf('绿') != -1){
return 'green';
}
}
}
}
})
</script>
<style lang="less" scoped>
.table-view{
width: 100%;
height: 100%;
border: 1px solid #0E748A;
// border: 1.5px solid #0E748A;
// box-shadow: 0 0 2px #00667C inset,0 0 0px rgb(0,153,184);
color: #FFFFFF;
background-color: #04465B;
font-size: 10px;
// text-transform: scale(0.75);
// transform:scale(0.5,1);
.thead{
display: flex;
background-color: #005062;
border-bottom: 1px solid #0E748A;
// padding: 10px 20px;
// position: relative;
div{
flex: 1;//
text-align: left;
flex-wrap:nowrap;
opacity: 0.85;
margin: 5px 10px;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
.tbodymain{
position: relative;
// width: 100%;
height: 183px;
overflow: hidden;
// display: flex;
// flex
.tbody{
position: absolute;
width: 100%;
top:0px;
border-top: none;
height: 182px;
// display: flex;
.row:nth-child(odd){
background-color: rgba(0,31,49,0.4);
}
.row:nth-child(even){
background-color: rgba(0,70,105,0.4);
}
.row{
height: 26px;
display: flex;
// justify-content:space-between;
// padding: 10px 20px;
text-align: left;
flex-wrap:nowrap;
// opacity: 0.65;
// background-color: #001F31;
div{
flex: 1;//
// padding: 10px 20px;
margin: 5px 10px;
text-align: left;
flex-wrap:nowrap;
// opacity: 0.65;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
span{
opacity: 0.65;
}
.blue{
background-color:rgba(87, 208, 235, 0.2);
color: #57D0EB;
padding: 0px 5px;
border:1px solid rgba(87, 208, 235, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.yellow{
background-color:rgba(235, 211, 87, 0.2);
color: #EBD357;
padding: 0px 5px;
border:1px solid rgba(235, 211, 87, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.green{
background-color:rgba(87, 235, 155, 0.2);
color: #57EB9B;
padding: 0px 5px;
border:1px solid rgba(87, 235, 155, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.time{
background-color: #012539;
border-radius: 2px;
padding: 5px 10px;
overflow:hidden;
opacity: 0.7;
font-size: 8px;
line-height: 16px;
}
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
box-shadow: 0 0 2px #00667C inset,0 0 0px rgb(0,153,184);
}
.b2{
.row:nth-child(odd){
background-color: rgba(0,70,105,0.4);
}
.row:nth-child(even){
background-color: rgba(0,31,49,0.4);
}
}
// .b1{
// top:0px;
// }
// .b2{
// top: -40px;
// }
}
}
</style>

88
lib/component/echarts/components/other/BasicTable copy.vue

@ -0,0 +1,88 @@
<template>
<div class="table-view" id="tableView">
<table class="table-main" border="0" cellpadding = "10" align="left" >
<thead>
<tr height="25" valign="middle">
<td v-for="(value,key,index) in theadDatas" :key="index">{{value}}</td>
</tr>
</thead>
<tbody>
<tr v-for="item in tableDatas" :key="item.id">
<td v-for="(value,key,index) in item" :key="index">{{value}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { defineComponent } from 'vue';
// import { init } from './js/index.js'
export default defineComponent({
name: 'baisicTable',
props: {},
data() {
return {
theadDatas:{num:"车牌号",color:"车牌颜色",positon:"违停地点",time:"违停时间"},
tableDatas:[
{num:"沪866B61",color:"#57D0EB",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"},
{num:"沪866B61",color:"#57D0EB",positon:"安徽省芜湖市镜湖区东路33号",time:"2020-09-12 12:00:03"}
],
theadKeys:null,
}
},
created() {
this.theadKeys = Object.keys(this.theadDatas);
},
mounted() {
// init();
},
methods: {
}
})
</script>
<style lang="less" scoped>
.table-view{
// position: absolute;
width: 100%;
height: 100%;
border: 1.5px solid #0E748A;
box-shadow: 0 0 2px #00667C inset,0 0 0px rgb(0,153,184);
.table-main{
// border = "1" width = "90%" cellspacing="0" cellpadding = "10"
color: #FFFFFF;
font-size: 10;
// border: 1.5px solid #0E748A;
thead{
text-align: left;
width: 90%;
background-color: #005062;
}
td{
// width:100%;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
}
thead{
opacity: 0.65;
}
}
}
</style>

368
lib/component/echarts/components/other/BasicTable-old.vue

@ -0,0 +1,368 @@
<template>
<div class="table-view" id="tableView">
<div class="thead">
<div v-for="(value,key,index) in thead" :key="index" :class="'flex'+index">{{value}}</div>
</div>
<div class="tbodymain" @mouseenter="bodyMouseEnter" @mouseleave="bodyMouseLeave">
<div class="tbody b1" ref="tbody1">
<div v-for="item in tableDatas1" :key="item.id" class="row">
<div v-for="(value,key,index) in item" :key="index" :class="'flex'+index">
<!-- <marquee v-if="key=='positon' && value.length>fontScroll" behavior="scroll" scrolldelay="200" @mouseenter="marqueeStop($event)" @mouseleave="marqueeStart($event)">{{value}}</marquee> -->
<span :class=[timeHandle(key),colorHandle(key,value),scrollHandle(key,value)]>{{value}}</span></div>
</div>
</div>
<div class="tbody b2" ref="tbody2">
<div v-for="item in tableDatas2" :key="item.id" class="row">
<div v-for="(value,key,index) in item" :key="index" :class="'flex'+index">
<!-- <marquee v-if="key=='positon' && value.length>fontScroll" behavior="scroll" scrolldelay="200" @mouseenter="marqueeStop($event)" @mouseleave="marqueeStart($event)">{{value}}</marquee> -->
<span :class=[timeHandle(key),colorHandle(key,value),scrollHandle(key,value)]>{{value}}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'baisicTable',
props: {
datas:{
type:Array,
required: true
},
thead:{
type:Array,
required: true
},
fontScroll:{
type:Number,
default:14
}
},
data() {
return {
timeOut:null,
srolltime:null,
tableDatas1:[],
tableDatas2:[],
pageIndex:0,
isColScroll:true,
total:0,
curPage:0,
height:26,
tableDatas:[]
}
},
watch:{
datas:{
handler(newval){
this.tableDatas = newval;
this.changDatas();
},
deep:true
}
},
created() {
this.tableDatas = this.datas;
let len = this.tableDatas.length;
if(len>=14){
this.tableDatas1 = this.tableDatas.slice(0,7);
this.tableDatas2 = this.tableDatas.slice(7,14);
}else if(len>7 && len<14){
this.tableDatas1 = this.tableDatas.slice(0,7);
this.tableDatas2 = this.tableDatas.slice(7,len);
let addData = this.tableDatas.slice(0,7-this.tableDatas2.length);
this.tableDatas2 = this.tableDatas2.concat(addData);
}else if(len <= 7){
this.tableDatas1 = this.tableDatas;
}
},
mounted() {
if(this.tableDatas.length>7){
this.timeIntervalHandle();
}else{
this.tableDatas1 = this.tableDatas;
}
if(this.tableDatas.length>14){
this.changDatas();
}
},
methods: {
changDatas(){
let len = this.tableDatas.length;
this.page = Math.ceil(len/7);
let endNum = len%7;
if(endNum>0){
let addNum = 7-endNum;
let addArr = this.tableDatas.slice(0,addNum);
this.tableDatas = this.tableDatas.concat(addArr);
}
this.curPage = 1;
},
bodyMouseEnter(){
this.isColScroll = false;
},
bodyMouseLeave(){
this.isColScroll = true;
this.srollHandle();
},
marqueeStart(e){
e.currentTarget.start();
},
marqueeStop(e){
e.currentTarget.stop();
},
timeIntervalHandle(){
this.$refs.tbody1.style.top = `${0}px`;
this.$refs.tbody2.style.top = `${182}px`;
setTimeout(this.srollHandle,3000);
},
srollHandle(){
if(!this.isColScroll) return;
let scrollH = 0;
let _this = this;
clearInterval(this.srolltime);
clearTimeout(this.timeOut);
this.srolltime = setInterval(function(){
scrollH += 2;
_this.$refs.tbody1.style.top = (parseInt(_this.$refs.tbody1.style.top) - 2)+"px";
_this.$refs.tbody2.style.top = (parseInt(_this.$refs.tbody2.style.top) - 2)+"px";
// console.log("");
if(scrollH>=26){
clearInterval(_this.srolltime);
if(parseInt(_this.$refs.tbody1.style.top)<=-182){
_this.$refs.tbody1.style.top = 182+'px';
if(_this.page > 2){
_this.updateData(1);
}
}else if(parseInt(_this.$refs.tbody2.style.top)<=-182){
_this.$refs.tbody2.style.top = 182+'px';
if(_this.page > 2){
_this.updateData(2);
}
}
_this.timeOut = setTimeout(_this.srollHandle,1500);
}
},24);
},
updateData(id){
this.curPage++;
if(this.curPage>=this.page){
this.curPage = 0;
}
let start = parseInt(this.curPage*7);
let end = start+7;
let getData = this.tableDatas.slice(start,end);
if(id==1){
this.tableDatas1 = getData;
}else{
this.tableDatas2 = getData;
}
},
timeHandle(key){
if(key=="time"){
return 'time';
}else{
return '';
}
},
scrollHandle(key,value){
if(key=='positon' && value.length>this.fontScroll){
return 'animate';
}
},
colorHandle(key,value){
if(key=="color"){
if(value.indexOf('蓝') != -1){
return 'blue';
}else if(value.indexOf('黄') != -1){
return 'yellow';
}else if(value.indexOf('绿') != -1){
return 'green';
}
}
}
}
})
</script>
<style lang="less" scoped>
.table-view{
width: 100%;
height: 100%;
border: 1px solid #0E748A;
color: #FFFFFF;
background-color: #04465B;
font-size: 10px;
.thead{
display: flex;
background-color: #005062;
border-bottom: 1px solid #0E748A;
div{
flex: 1;
text-align: left;
flex-wrap:nowrap;
opacity: 0.85;
margin: 5px 10px;
word-break:keep-all;
white-space:nowrap;
overflow:hidden;
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
.tbodymain{
position: relative;
height: 183px;
overflow: hidden;
.tbody{
position: absolute;
width: 100%;
top:0px;
border-top: none;
height: 182px;
.row:nth-child(odd){
background-color: rgba(0,31,49,0.4);
}
.row:nth-child(even){
background-color: rgba(0,70,105,0.4);
}
.row{
height: 26px;
display: flex;
text-align: left;
flex-wrap:nowrap;
div{
flex: 1;//
margin: 5px 12px;
text-align: left;
flex-wrap:nowrap;
word-break:keep-all;
white-space:nowrap;
overflow:hidden;
span{
opacity: 0.65;
}
marquee{
opacity: 0.65;
}
.blue{
background-color:rgba(87, 208, 235, 0.2);
color: #57D0EB;
padding: 0px 5px;
border:1px solid rgba(87, 208, 235, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.yellow{
background-color:rgba(235, 211, 87, 0.2);
color: #EBD357;
padding: 0px 5px;
border:1px solid rgba(235, 211, 87, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.green{
background-color:rgba(87, 235, 155, 0.2);
color: #57EB9B;
padding: 0px 5px;
border:1px solid rgba(87, 235, 155, 0.4);
border-radius: 1px;
opacity: 1;
font-size: 8px;
line-height: 16px;
}
.time{
background-color: #012539;
border-radius: 2px;
padding: 5px 10px;
overflow:hidden;
opacity: 0.7;
font-size: 8px;
line-height: 16px;
}
.animate {
display: inline-block;
animation: 13s wordsLoop linear infinite normal;
}
@keyframes wordsLoop {
0% {
transform: translateX(100%);
-webkit-transform: translateX(100%);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
@-webkit-keyframes wordsLoop {
0% {
transform: translateX(0px);
-webkit-transform: translateX(0px);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
box-shadow: 0 0 2px #00667C inset,0 0 0px rgb(0,153,184);
}
.b2{
.row:nth-child(odd){
background-color: rgba(0,70,105,0.4);
}
.row:nth-child(even){
background-color: rgba(0,31,49,0.4);
}
}
// .b1{
// top:0px;
// }
// .b2{
// top: -40px;
// }
}
}
</style>

430
lib/component/echarts/components/other/BasicTable.vue

@ -0,0 +1,430 @@
<template>
<div class="table-view" id="tableView" ref="tableView" :style="{fontSize:`${fontSize}px`}">
<div class="thead" :style="{height: `${headHeight}px`}">
<div v-for="(value,key,index) in thead" :key="index" :style="{height: `${headHeight-10}px`,lineHeight:`${headHeight-10}px`,flex:flexs[index]}">{{value}}</div>
</div>
<div class="tbodymain" :style="{height: `${bodyHeight}px`}" @mouseenter="bodyMouseEnter" @mouseleave="bodyMouseLeave">
<div class="tbody b1" ref="tbody1" :style="{height: `${bodyHeight}px`}">
<div v-for="item in tableDatas1" :key="item.id" class="row" :style="{height: `${cellHeight}px`}">
<div v-for="(value,key,index) in item" :key="index" :style="{flex:flexs[index]}">
<!-- <marquee v-if="key=='positon' && value.length>fontScroll" behavior="scroll" scrolldelay="200" @mouseenter="marqueeStop($event)" @mouseleave="marqueeStart($event)">{{value}}</marquee> -->
<span :class=[timeHandle(key),colorHandle(key,value),scrollHandle(key,value)] :style="{height: `${cellHeight-10}px`,lineHeight:`${cellHeight-10}px`}">{{value}}</span></div>
</div>
</div>
<div class="tbody b2" ref="tbody2" :style="{height: `${bodyHeight}px`}">
<div v-for="item in tableDatas2" :key="item.id" class="row" :style="{height: `${cellHeight}px`}">
<div v-for="(value,key,index) in item" :key="index" :style="{flex:flexs[index]}">
<!-- <marquee v-if="key=='positon' && value.length>fontScroll" behavior="scroll" scrolldelay="200" @mouseenter="marqueeStop($event)" @mouseleave="marqueeStart($event)">{{value}}</marquee> -->
<span :class=[timeHandle(key),colorHandle(key,value),scrollHandle(key,value)] :style="{height: `${cellHeight-10}px`,lineHeight:`${cellHeight-10}px`}">{{value}}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'baisicTable',
props: {
datas:{
type:Array,
required: true
},
thead:{
type:Array,
required: true
},
flexs:{
type:Array,
default:[1,1,3,2.3]
},
fontScroll:{
type:Number,
default:14
},
headHeight:{
type:Number,
default:37
},
rowNum:{
type:Number,
default:7
},
colNum:{
type:Number,
default:4
},
speed:{
type:Number,
default:2
},
fontSize:{
type:Number,
default:10
}
},
data() {
return {
timeOut:null,
srolltime:null,
tableDatas1:[],
tableDatas2:[],
pageIndex:0,
isColScroll:true,
total:0,
curPage:0,
cellHeight:26,
tableDatas:[],
bodyHeight:182
}
},
watch:{
datas:{
handler(newval){
this.tableDatas = newval;
this.changDatas();
},
deep:true
}
},
created() {
this.tableDatas = this.datas;
let len = this.tableDatas.length;
let row = this.rowNum;
let doubleRow = parseInt(row*2);
if(len>=doubleRow){
this.tableDatas1 = this.tableDatas.slice(0,row);
this.tableDatas2 = this.tableDatas.slice(row,doubleRow);
}else if(len>row && len<doubleRow){
this.tableDatas1 = this.tableDatas.slice(0,row);
this.tableDatas2 = this.tableDatas.slice(row,len);
let addData = this.tableDatas.slice(0,row-this.tableDatas2.length);
this.tableDatas2 = this.tableDatas2.concat(addData);
}else if(len <= row){
this.tableDatas1 = this.tableDatas;
}
},
mounted() {
this.getBodyMainHeight();
let row = this.rowNum;
let doubleRow = parseInt(row*2);
if(this.tableDatas.length>row){
this.timeIntervalHandle();
}else{
this.tableDatas1 = this.tableDatas;
}
if(this.tableDatas.length>doubleRow){
this.changDatas();
}
},
methods: {
getBodyMainHeight(){
// debugger;
let tableDom = document.getElementById("tableView");
// console.log(tableDom.clientHeight);
this.bodyHeight = parseInt(tableDom.clientHeight-this.headHeight);
// console.log(this.bodyHeight);
this.cellHeight = (this.bodyHeight/this.rowNum).toFixed(6);
console.log(this.cellHeight);
},
changDatas(){
let len = this.tableDatas.length;
let row = this.rowNum;
this.page = Math.ceil(len/row);
let endNum = len%row;
if(endNum>0){
let addNum = row-endNum;
let addArr = this.tableDatas.slice(0,addNum);
this.tableDatas = this.tableDatas.concat(addArr);
}
this.curPage = 1;
},
bodyMouseEnter(){
this.isColScroll = false;
},
bodyMouseLeave(){
this.isColScroll = true;
this.srollHandle();
},
marqueeStart(e){
e.currentTarget.start();
},
marqueeStop(e){
e.currentTarget.stop();
},
timeIntervalHandle(){
this.$refs.tbody1.style.top = `${0}px`;
this.$refs.tbody2.style.top = `${this.bodyHeight}px`;
setTimeout(this.srollHandle,3000);
},
srollHandle(){
if(!this.isColScroll) return;
let scrollSpeed = 0;
let _this = this;
clearInterval(this.srolltime);
clearTimeout(this.timeOut);
this.srolltime = setInterval(function(){
scrollSpeed += _this.speed;
if(scrollSpeed>_this.cellHeight){
//
let v = (_this.cellHeight - (scrollSpeed-_this.speed)).toFixed(6);
// console.log("V=====" + v);
// console.log(parseFloat(_this.$refs.tbody1.style.top));
_this.$refs.tbody1.style.top = (parseFloat(_this.$refs.tbody1.style.top) - v).toFixed(6)+"px";
_this.$refs.tbody2.style.top = (parseFloat(_this.$refs.tbody2.style.top) - v).toFixed(6)+"px";
// console.log(parseFloat(_this.$refs.tbody1.style.top));
}else{
_this.$refs.tbody1.style.top = (parseFloat(_this.$refs.tbody1.style.top) - _this.speed).toFixed(6)+"px";
_this.$refs.tbody2.style.top = (parseFloat(_this.$refs.tbody2.style.top) - _this.speed).toFixed(6)+"px";
// console.log("");
}
if(parseFloat(_this.$refs.tbody1.style.top)<=-_this.bodyHeight){
// debugger;
_this.$refs.tbody1.style.top = _this.bodyHeight+'px';
if(_this.page > 2){
_this.updateData(1);
}
}else if(parseFloat(_this.$refs.tbody2.style.top)<=-_this.bodyHeight){
// debugger;
_this.$refs.tbody2.style.top = _this.bodyHeight+'px';
if(_this.page > 2){
_this.updateData(2);
}
}
if(scrollSpeed>=_this.cellHeight){
clearInterval(_this.srolltime);
_this.timeOut = setTimeout(_this.srollHandle,1500);
}
},24);
},
updateData(id){
let row = this.rowNum;
this.curPage++;
if(this.curPage>=this.page){
this.curPage = 0;
}
let start = parseInt(this.curPage*row);
let end = start+row;
let getData = this.tableDatas.slice(start,end);
if(id==1){
this.tableDatas1 = getData;
}else{
this.tableDatas2 = getData;
}
},
timeHandle(key){
if(key=="time"){
return 'time';
}else{
return '';
}
},
scrollHandle(index,key,value){
if(key=='positon' && value.length>this.fontScroll){
return 'animate';
}
},
colorHandle(key,value){
if(key=="color"){
if(value.indexOf('蓝') != -1){
return 'blue';
}else if(value.indexOf('黄') != -1){
return 'yellow';
}else if(value.indexOf('绿') != -1){
return 'green';
}
}
}
}
})
</script>
<style lang="less" scoped>
.table-view{
width: 100%;
height: 100%;
border: 1px solid #0E748A;
color: #FFFFFF;
background-color: #04465B;
// font-size: 10px;
.thead{
display: flex;
background-color: #005062;
border-bottom: 1px solid #0E748A;
div{
flex: 1;
text-align: left;
flex-wrap:nowrap;
opacity: 0.85;
margin: 5px 10px;
word-break:keep-all;
white-space:nowrap;
overflow:hidden;
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
.tbodymain{
position: relative;
height: 183px;
overflow: hidden;
.tbody{
position: absolute;
width: 100%;
top:0px;
border-top: none;
height: 182px;
.row:nth-child(odd){
background-color: rgba(0,31,49,0.4);
}
.row:nth-child(even){
background-color: rgba(0,70,105,0.4);
}
.row{
height: 26px;
display: flex;
text-align: left;
flex-wrap:nowrap;
div{
flex: 1;//
margin: 5px 12px;
text-align: left;
flex-wrap:nowrap;
word-break:keep-all;
white-space:nowrap;
overflow:hidden;
span{
opacity: 0.65;
// height: 37px;
// line-height: 37px;
// display: flex;
// // justify-content: center; //
// align-items: center; //
}
marquee{
opacity: 0.65;
}
.blue{
background-color:rgba(87, 208, 235, 0.2);
color: #57D0EB;
padding: 0px 5px;
border:1px solid rgba(87, 208, 235, 0.4);
border-radius: 1px;
opacity: 1;
// font-size: 8px;
// line-height: 16px;
}
.yellow{
background-color:rgba(235, 211, 87, 0.2);
color: #EBD357;
padding: 0px 5px;
border:1px solid rgba(235, 211, 87, 0.4);
border-radius: 1px;
opacity: 1;
// font-size: 8px;
// line-height: 16px;
}
.green{
background-color:rgba(87, 235, 155, 0.2);
color: #57EB9B;
padding: 0px 5px;
border:1px solid rgba(87, 235, 155, 0.4);
border-radius: 1px;
opacity: 1;
// font-size: 8px;
// line-height: 16px;
}
.time{
background-color: #012539;
border-radius: 2px;
padding: 5px 10px;
overflow:hidden;
opacity: 0.7;
// font-size: 8px;
// line-height: 16px;
}
.animate {
display: inline-block;
animation: 13s wordsLoop linear infinite normal;
}
@keyframes wordsLoop {
0% {
transform: translateX(100%);
-webkit-transform: translateX(100%);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
@-webkit-keyframes wordsLoop {
0% {
transform: translateX(0px);
-webkit-transform: translateX(0px);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
}
.flex0{
flex:1;
}
.flex1{
flex:1;
}
.flex2{
flex:3;
}
.flex3{
flex:2.3;
}
}
box-shadow: 0 0 2px #00667C inset,0 0 0px rgb(0,153,184);
}
.b2{
.row:nth-child(odd){
background-color: rgba(0,70,105,0.4);
}
.row:nth-child(even){
background-color: rgba(0,31,49,0.4);
}
}
// .b1{
// top:0px;
// }
// .b2{
// top: -40px;
// }
}
}
</style>

78
lib/component/echarts/echarts.vue

@ -0,0 +1,78 @@
<template>
<div class="ns-chart">
<v-chart :id="id" ref="chart" />
</div>
</template>
<script lang="ts">
import * as echarts from 'echarts/core';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import VChart, { THEME_KEY } from 'vue-echarts';
import { defineComponent, onMounted, reactive, ref } from 'vue';
import { PieChart, BarChart, ScatterChart, RadarChart } from 'echarts/charts';
import 'echarts/lib/component/grid';
import { GridComponent } from 'echarts/components';
use([
CanvasRenderer,
LineChart,
PieChart,
BarChart,
ScatterChart,
RadarChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
]);
export default defineComponent({
components: {
VChart,
},
provide: {
[THEME_KEY]: 'light',
},
props: {
id: String,
option: {
type: Object,
default: () => ({}),
},
},
setup(props) {
// var id=ref<String>('');
onMounted(() => {
const getOptions = () => {
console.log('sdada');
console.log(props.id);
var chart = echarts.init(document.getElementById(props.id));
chart.showLoading();
setTimeout(() => {
console.log('loading');
chart.hideLoading();
chart.setOption(props.option);
}, 2000);
};
getOptions();
});
return {
props,
};
},
});
</script>
<style scoped>
.ns-chart {
height: 500px;
width: 450px;
border: 1px solid #dcdfe2;
margin: 5px;
}
</style>

71
lib/component/flowChart/flow-chart.vue

@ -0,0 +1,71 @@
<template><div id="container"></div> </template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Graph } from '@antv/x6';
export default defineComponent({
name: 'NsFlowChart',
setup() {},
mounted() {
const data = {
//
nodes: [
{
id: 'node1', // String
x: 40, // Number x
y: 40, // Number y
width: 80, // Number width
height: 40, // Number height
label: 'hello', // String
line: {
stroke: 'orange',
},
},
{
id: 'node2', // String
x: 160, // Number x
y: 180, // Number y
width: 80, // Number width
height: 40, // Number height
label: 'world', // String
},
],
//
edges: [
{
source: 'node1', // String id
target: 'node2', // String id
},
],
};
const graph = new Graph({
container: document.getElementById('container') as HTMLElement,
background: {
color: '#191F27', //
},
grid: {
size: 10, // 10px
type: 'dot',
visible: false, //
args: {
color: '#fff', // 线/
thickness: 0.5, // 线/
},
},
snapline: {
enabled: true,
className: 'my-snapline',
},
});
graph.fromJSON(data);
},
});
</script>
<style lang="less" scoped>
#container {
width: 1000px;
height: 100%;
}
</style>

17
lib/component/form/button/button.vue

@ -0,0 +1,17 @@
<template>
<a-button>
<slot></slot>
<template #icon>
<slot name="icon"></slot>
</template>
</a-button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsButton',
});
</script>
<style lang="less" scoped></style>

4
lib/component/form/button/index.ts

@ -0,0 +1,4 @@
import button from './button.vue';
import { withInstall } from '/nerv-lib/util';
export const NsButton = withInstall(button);

31
lib/component/form/cascader/cascader.vue

@ -0,0 +1,31 @@
<template>
<a-cascader :load-data="loadData" :options="options" change-on-select>
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"> </slot>
</template>
</a-cascader>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import type { CascaderProps } from 'ant-design-vue';
export default defineComponent({
name: 'NsCascader',
setup(props, { attrs }) {
let options = ref<any[]>(attrs['options'] || []);
let loadData: CascaderProps['loadData'] | null = (selectOptions) => {
attrs['loadData'](selectOptions, options, attrs);
};
if (attrs['loadData']) {
loadData(options);
} else {
loadData = null;
}
return {
loadData,
options,
};
},
});
</script>
<style lang="less" scoped></style>

5
lib/component/form/cascader/index.ts

@ -0,0 +1,5 @@
import cascader from './cascader.vue';
import { withInstall } from '/nerv-lib/util';
export const NsCascader = withInstall(cascader);

180
lib/component/form/checkbox/checkbox-allgroup.vue

@ -0,0 +1,180 @@
<template>
<div class="all-group">
<!-- 样式修改 -->
<div class="message-list">
<a-checkbox
v-model:checked="checkAll"
:indeterminate="indeterminate"
@change="onCheckAllChange"
:disabled="promptWord.checked"
>
全选
</a-checkbox>
</div>
<div class="warp" v-if="options.length">
<a-checkbox-group
v-model:value="checkedList"
:options="options"
:disabled="promptWord.checked"
>
<slot></slot>
</a-checkbox-group>
<!-- <ns-checkbox-group v-model:value="checkedList" :options="options" /> -->
</div>
<div class="no-warp" v-if="!options.length">
<ul class="prompt-information">
<ns-icon class="icon" name="account1" size="25" style="margin-right: 5px" />
<li>暂无数据, 请先维护</li
><li @click="jumpMaintenanceInformation()">{{ promptWord.maintenanceInformation }}</li></ul
>
</div>
<p class="remark-prompt">{{ promptWord.message }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, watch, computed, ref } from 'vue';
import { http } from '/nerv-lib/util/http';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'NsCheckboxAllGroup',
props: {
api: String,
field: String,
title: String,
message: String,
maintenanceInformation: String, //
route: String, //
optionsName: Array,
resultField: String,
checked: String,
options: Array
},
setup(props) {
const options = ref([]); //options
const state = reactive({
indeterminate: false,
checkAll: false,
checkedList: [],
});
const router = useRouter();
//
const promptWord = computed(() => {
return props;
});
//
const getData = () => {
options.value = [];
if(props.options){
options.value= props.options
}
http.get(props.api).then((res) => {
if (promptWord.value.resultField == 'data') {
res.data.forEach((element) => {
element['label'] = element[promptWord.value.optionsName[0]];
element['value'] = element[promptWord.value.optionsName[1]];
});
options.value = res.data;
} else {
res.data.data.forEach((element) => {
element['label'] = element[promptWord.value.optionsName[0]];
element['value'] = element[promptWord.value.optionsName[1]];
});
options.value = res.data.data;
}
});
};
const jumpMaintenanceInformation = () => {
router.push(props.route);
};
getData();
//
const onCheckAllChange = (e: any) => {
state.checkedList = [];
if (e.target.checked) {
(state.indeterminate = false),
options.value.forEach((item) => {
state.checkedList.push(item['value']);
});
}
state.indeterminate = false;
};
//
watch(
() => state.checkedList,
(val) => {
state.indeterminate = !!val.length && val.length < options.value.length;
state.checkAll = val.length === options.value.length;
}
);
return {
...toRefs(state),
onCheckAllChange,
promptWord,
options,
jumpMaintenanceInformation,
};
},
});
</script>
<style lang="less" scoped>
.all-group {
// margin-left: 22.22%;
}
.warp {
width: 100%;
height: 100%;
border: 1px solid #dcdfe2;
background: #f8f9fc;
}
.no-warp {
width: 100%;
height: 100px;
border: 1px solid #dcdfe2;
background: #f8f9fc;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.prompt-information {
list-style: none;
display: flex;
padding-left: 7px;
// position: absolute;
// left: 50%;
// top: 40%;
li:nth-of-type(1) {
color: #828282;
}
li:nth-of-type(2) {
color: #6865ea;
}
}
}
.message-list {
display: flex;
}
.message-list p {
margin-right: 5px;
color: #606060;
}
.ant-checkbox-group {
margin: 22px 18px 26px !important;
}
:deep(.ant-checkbox-group-item) {
margin-right: 110px !important;
}
.remark-prompt {
font-family: PingFang SC;
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 22px;
color: #c1c1c1;
margin-top: 7px;
}
</style>

14
lib/component/form/checkbox/checkbox-group.vue

@ -0,0 +1,14 @@
<template>
<a-checkbox-group>
<slot></slot>
</a-checkbox-group>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsCheckboxGroup',
});
</script>
<style lang="less" scoped></style>

12
lib/component/form/checkbox/checkbox.vue

@ -0,0 +1,12 @@
<template>
<a-checkbox><slot></slot></a-checkbox>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsCheckbox',
});
</script>
<style lang="less" scoped></style>

11
lib/component/form/checkbox/index.ts

@ -0,0 +1,11 @@
import nsCheckbox from './checkbox.vue';
import nsCheckboxGroup from './checkbox-group.vue';
import nsCheckboxAllGroup from './checkbox-allgroup.vue';
import type { App } from 'vue';
export const NsCheckbox = function (app: App) {
app.component(nsCheckbox.name, nsCheckbox);
app.component(nsCheckboxGroup.name, nsCheckboxGroup);
app.component(nsCheckboxAllGroup.name, nsCheckboxAllGroup);
return app;
};

1188
lib/component/form/date-picker/cron-antd.vue

File diff suppressed because it is too large

13
lib/component/form/date-picker/date-picker.vue

@ -0,0 +1,13 @@
<!-- @format -->
<template>
<a-date-picker />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsDatePicker',
});
</script>
<style lang="less" scoped></style>

21
lib/component/form/date-picker/index.ts

@ -0,0 +1,21 @@
import type { App } from 'vue';
import nsDatePicker from './date-picker.vue';
import nsMonthPicker from './month-picker.vue';
import nsRangePicker from './range-picker.vue';
import nsWeekPicker from './week-picker.vue';
import nsYearPicker from './year-picker.vue';
import vueCron from './v-cron.vue';
nsDatePicker.RangePicker = nsRangePicker;
nsDatePicker.MonthPicker = nsMonthPicker;
nsDatePicker.WeekPicker = nsWeekPicker;
nsDatePicker.YearPicker = nsYearPicker;
nsDatePicker.vueCron = vueCron;
export const NsDatePicker = function (app: App) {
app.component(nsDatePicker.name, nsDatePicker);
app.component(nsDatePicker.RangePicker.name, nsDatePicker.RangePicker);
app.component(nsDatePicker.MonthPicker.name, nsDatePicker.MonthPicker);
app.component(nsDatePicker.WeekPicker.name, nsDatePicker.WeekPicker);
app.component(nsDatePicker.YearPicker.name, nsDatePicker.YearPicker);
app.component(nsDatePicker.vueCron.name, nsDatePicker.vueCron);
return app;
};

11
lib/component/form/date-picker/month-picker.vue

@ -0,0 +1,11 @@
<template>
<a-month-picker />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsMonthPicker',
});
</script>
<style lang="less" scoped></style>

11
lib/component/form/date-picker/range-picker.vue

@ -0,0 +1,11 @@
<template>
<a-range-picker />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsRangePicker',
});
</script>
<style lang="less" scoped></style>

74
lib/component/form/date-picker/v-cron.vue

@ -0,0 +1,74 @@
<template>
<div class="components-input-demo-presuffix">
<a-input @click="openModal" placeholder="corn表达式" v-model:value="cron" @change="handleOK">
<template #prefix>
<a-icon type="schedule" title="corn控件" />
</template>
<template #suffix>
<a-icon v-if="cron" type="close-circle" @click="handleEmpty" title="清空" />
</template>
</a-input>
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
</div>
</template>
<script>
import JCronModal from './cron-antd.vue';
export default {
name: 'vCron',
components: {
JCronModal,
},
props: {
value: {
required: false,
type: String,
default: () => {
return '0 * * * * ? *';
},
},
},
data() {
return {
cron: this.value,
};
},
watch: {
value(val) {
this.cron = val;
},
},
methods: {
openModal() {
this.$refs.innerVueCron.show();
},
handleOK(val) {
if(typeof(val) !== 'string') return ;
this.cron = val;
this.$emit('change', this.cron);
//this.$emit("change", Object.assign({}, this.cron));
},
handleEmpty() {
this.handleOK('');
},
},
model: {
prop: 'value',
event: 'change',
},
};
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>

11
lib/component/form/date-picker/week-picker.vue

@ -0,0 +1,11 @@
<template>
<a-week-picker />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsWeekPicker',
});
</script>
<style lang="less" scoped></style>

11
lib/component/form/date-picker/year-picker.vue

@ -0,0 +1,11 @@
<template>
<a-date-picker picker="year" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'NsYearPicker',
});
</script>
<style lang="less" scoped></style>

176
lib/component/form/disk-combination/disk-combination.vue

@ -0,0 +1,176 @@
<template>
<a-form ref="formRef" :model="formModel">
<!--items-->
<div class="data" style="display: flex" v-for="(d, index) in formModel.data" :key="d">
<!--op-->
<div class="combination-item operation-item" v-if="remove">
<a class="op" @click="onDelete(index)">
删除
<!--<ns-icon name="delete" size="15"/>-->
</a>
</div>
<div class="combination-item" v-for="item in combinationOptions" :key="item.label">
<a-form-item
:label="item.label"
:name="['data', index, item.field]"
:rules="formRules[item.field]"
>
<component
:is="item.component"
v-bind="item['componentProps']"
:v-model="d[item.field]"
@change="valueChanges($event, index, item.field)"
/>
</a-form-item>
</div>
</div>
<!--op-->
<div class="combination-item operation-item" v-if="add">
<a class="add" @click="onAdd" :class="{ disabled: leftCount == 0 }">
<!--<ns-icon name="add" size="15"/>-->
添加
<span class="op-label">{{ addName }}</span>
</a>
<span class="op-limit-msg"
>{{ limitMsg }} <span class="leftCount">{{ leftCount }}</span></span
>
</div>
</a-form>
</template>
<script>
import { defineComponent, reactive, ref, toRaw } from 'vue';
export default defineComponent({
name: 'NsDiskCombination',
props: {
combinationOptions: {
type: Array,
default: () => [],
},
add: {
type: Boolean,
default: () => false,
},
addName: {
type: String,
default: () => '',
},
//
// addIcon: {
// type: String,
// default: () => '',
// },
//
remove: {
type: Boolean,
default: () => true,
},
limitCount: Number,
limitMsg: {
type: String,
default: () => '',
},
//
// tip: {
// type: String,
// default: () => '',
// },
},
emits: ['change'],
setup(props) {
const { limitCount } = props;
let leftCount = ref(0);
leftCount.value = limitCount;
const formModel = reactive({
data: [{}],
});
if (formModel.data) {
leftCount.value = limitCount - formModel.data.length;
}
let formRules = {};
props.combinationOptions.forEach((it) => {
formRules[it['field']] = it['rules'];
});
const onSubmit = function (thisObj) {
thisObj.$refs.formRef
.validate()
.then(() => {
console.log('values', toRaw(formModel));
})
.catch((error) => {
console.log('error', error);
});
};
const valueChanges = function (e, index, fieldName) {
let value = e;
if (typeof e == 'object' && e && e.target) {
value = e.target.value;
}
formModel['data'][index][fieldName] = value;
// onSubmit(this);
};
const onAdd = () => {
if (limitCount > 0 && limitCount <= formModel.data.length) {
return;
}
formModel['data'].push(reactive({}));
leftCount.value = limitCount - formModel.data.length;
};
const onDelete = (index) => {
formModel['data'].splice(index, 1);
leftCount.value = limitCount - formModel.data.length;
};
return {
formModel,
formRules,
leftCount,
valueChanges,
onAdd,
onDelete,
};
},
});
</script>
<style scoped="scoped" lang="less">
.combination-wrapper {
display: flex;
margin-bottom: 10px;
}
.combination-item {
/*flex: 0 0 33%;*/
margin-right: 10px;
.item-label {
margin-right: 4px;
}
&.operation-item {
.op {
line-height: 30px;
}
.op-limit-msg {
margin-left: 15px;
color: #b0bec5;
}
.add.disabled {
color: #bfbfbf;
cursor: not-allowed;
}
}
}
</style>

7
lib/component/form/disk-combination/index.ts

@ -0,0 +1,7 @@
import type { App } from 'vue';
import nsDiskCombination from './disk-combination.vue';
export const NsDiskCombination = function (app: App) {
app.component(nsDiskCombination.name, nsDiskCombination);
return app;
};

264
lib/component/form/editTable/custom-edit-table.vue

@ -0,0 +1,264 @@
<!-- @format -->
<template>
<div class="edittable-contain">
<div class="operation">
<a-button type="primary" @click="operation('get')" v-if="api">获取数据</a-button>
<a-button type="primary" @click="operation('clear')">清除数据</a-button>
</div>
<NsBasicTable
:columns="columns"
:data-source="dataSource"
align="center"
rowKey="familyUuid"
@change="change"
:scroll="{ x: '800px' }"
>
<template #headerCell="{ title, column }">
<template v-if="column.required">
<div class="mainColum">{{ title }}</div>
</template>
</template>
<template #bodyCell="{ column, text, record, index }">
<template v-if="column.dataIndex !== 'action'">
<div>
<component
style="min-width: 120px"
v-model:value="record[column.dataIndex]"
:is="column.component"
:record="record"
v-bind="column.formProps || column.componentProps"
/>
<p v-if="column.validator" style="color: red">
{{ column.validator(record) === 'error' ? column.errorTitle : '' }}
</p>
</div>
</template>
<template v-if="column.dataIndex === 'action'">
<div style="min-width: 120px" class="column-operation">
<span style="cursor: pointer; color: rgb(27, 182, 182)" @click="remove(index)"
>移除</span
>
<span
style="cursor: pointer; color: rgb(27, 182, 182)"
@click="setOptions('down', index)"
>向下插入</span
>
<span style="cursor: pointer; color: rgb(27, 182, 182)" @click="setOptions('up', index)"
>向上插入</span
>
</div>
</template>
</template>
<template #footer v-if="!readonly">
<span style="cursor: pointer; color: rgb(27, 182, 182)" @click="add"> 添加</span>
</template>
</NsBasicTable>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, PropType, nextTick, watch, unref, computed } from 'vue';
import { cloneDeep, get, isArray, isEqual, isFunction, isString, isUndefined } from 'lodash-es';
import { HttpRequestConfig, useApi } from '/nerv-lib/use/use-api';
import { useParams } from '/nerv-lib/use/use-params';
export default defineComponent({
name: 'NsCustomEditTable',
props: {
api: {
type: [String, Object, Function] as PropType<string | Function | HttpRequestConfig>,
required: true,
},
params: {
type: Object,
default: () => ({}),
},
resultField: {
type: String,
default: 'data.data',
},
columns: {
type: Array,
default: () => [],
},
initData: {
type: Array,
default: () => [],
},
readonly: {
type: Boolean,
default: () => false,
},
value: {
type: Array,
default: () => [],
},
formModel: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
scroll: {
type: Object,
default: () => ({ x: '800px' }),
},
},
emits: ['change', 'validateChange'],
setup(props, { emit }) {
// const components = inject("components");
const columns = ref([]);
const dataSource = ref([]);
const { getParams } = useParams();
columns.value = props.columns;
dataSource.value = props.initData;
const add = () => {
// let info = JSON.parse(JSON.stringify(props.initItem));
// if (
// dataSource.value.length === 0
// ) {
dataSource.value.push({});
// }
console.log(dataSource.value);
};
const pagination = ref({
current: 1,
pageSize: 10,
});
const remove = (i: number) => {
let pagebase = (pagination.value.current - 1) * 10;
dataSource.value.splice(i + pagebase, 1);
};
const change = (pag, filters, sorter, { currentDataSource }) => {
pagination.value = pag;
};
/**
* 获取数据
*/
const fetch = () => {
const requestConfig: HttpRequestConfig = { method: 'get' };
const { api, params: _params, resultField, filterData, dynamicParams } = props;
let params: Recordable = cloneDeep(_params);
// if (props.filterFiled && filterFiledRef.value) {
// params[props.filterFiled] = filterFiledRef.value;
// }
// let data;
if (dynamicParams) {
params = getParams(props.formModel, dynamicParams, { ...params });
// console.log('getParams', data);
}
const { httpRequest } = useApi();
httpRequest({ api, params, requestConfig })
.then((res: Recordable) => {
emit('validateChange', { help: undefined });
if (resultField) {
// debugger;
let data = get(res, resultField) || [];
// data = data.splice(Math.floor(Math.random() * 3), Math.floor(Math.random() * 5 + 5));
if (isFunction(filterData)) {
dataSource.value = data.filter(filterData);
} else {
dataSource.value = data;
}
emit('change', dataSource.value);
}
})
.catch((error: any) => {
if (error?.response?.status === 403) {
emit('validateChange', { help: '暂无权限', validateStatus: 'error' });
}
dataSource.value = [];
});
};
const operation = (name) => {
if (name === 'get') {
fetch();
}
if (name === 'clear') {
dataSource.value.splice(0);
emit('change', dataSource.value);
}
};
const setOptions = (name, i) => {
let pagebase = (pagination.value.current - 1) * 10;
let index = i + pagebase;
if (name === 'up') {
if (index === 0) {
dataSource.value.unshift({});
} else {
dataSource.value.splice(index, 0, {});
}
}
if (name === 'down') {
dataSource.value.splice(index + 1, 0, {});
}
emit('change', dataSource.value);
};
watch(
() => props.value,
(val) => {
dataSource.value = props.value;
emit('change', val);
},
{ deep: true, immediate: true }
);
return {
remove,
add,
dataSource,
columns,
operation,
setOptions,
change,
};
},
// watch: {
// dataSource: {
// handler(val) {
// console.log(val);
// this.$emit('change', val);
// },
// deep: true,
// },
// },
});
</script>
<style lang="less" scoped>
:deep(.ant-table-thead)
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
display: none;
}
.mainColum {
&::before {
display: inline-block;
margin-right: 4 px;
color: #ff4d4f;
font-size: 12px;
font-family: SimSun, sans-serif;
line-height: 1;
content: '*';
}
}
:deep(.ant-form-item-explain.ant-form-item-explain-error) {
display: flex;
min-width: 130px !important;
width: 140px !important;
}
.operation {
padding-bottom: 8px;
.ant-btn {
margin-right: 16px;
}
}
.column-operation {
span {
margin-right: 8px;
}
}
</style>

44
lib/component/form/editTable/edit-table.vue

@ -0,0 +1,44 @@
<template>
<ns-table ref="nsTableRef" v-bind="getBindValue" v-model="dataSourceRef">
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data"> </slot>
</template>
</ns-table>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue';
import { tableProps } from '/nerv-lib/component/table/props';
import { PropTypes } from '/nerv-lib/util/type';
export default defineComponent({
name: 'NsEditTable',
props: {
...tableProps,
// value: PropTypes.array.def(() => []),
},
setup(props, { attrs, emit }) {
const nsTableRef = ref();
const getBindValue = computed(() => ({
...attrs,
...props,
}));
const dataSourceRef = ref(props.dataSource);
watch(
() => dataSourceRef.value,
() => {
// console.log('getDataSource.value', dataSourceRef.value);
emit('update:value', dataSourceRef.value);
emit('change', dataSourceRef.value);
},
{
deep: true,
}
);
return { getBindValue, nsTableRef, dataSourceRef };
},
});
</script>
<style lang="less" scoped></style>

12
lib/component/form/editTable/index.ts

@ -0,0 +1,12 @@
import type { App } from 'vue';
import editTable from './edit-table.vue';
import nsCustomEditTable from './custom-edit-table.vue';
import { withInstall } from '/nerv-lib/util';
// export const NsEditTable = withInstall(editTable);
export const NsEditTable = function (app: App) {
app.component(editTable.name, editTable);
app.component(nsCustomEditTable.name, nsCustomEditTable);
return app;
};

679
lib/component/form/etable/etable.vue

@ -0,0 +1,679 @@
<template>
<div style="display: flex" v-bind="$attrs">
<!-- {{ $attrs.value }} -->
<!-- {{simpleDayOptions}} -->
<!-- <div v-if="columsType == 'complexTable'" style="color: red; display: block; width: 100%">
服务频次对应每天每周每月每年均需要设置起止时间
</div>
<br /> -->
<div v-if="columsType == 'complexTable'">
<div v-if="columsType == 'complexTable'" style="color: red; display: block; width: 100%">
&nbsp;&nbsp;&nbsp; 服务频次对应每天每周每月每年均需要设置起止时间
<br />
<br />
</div>
<a-table :columns="colums" :data-source="$attrs.value" bordered :pagination="false">
<template
v-for="(item, index) in colums"
:key="index"
#[item.dataIndex]="text, record, index"
>
<!-- {{ text }} -->
<a-input
v-if="
item.otherConditions
? item.otherConditions && item.inputType == 'input'
: item.inputType == 'input'
"
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex, item)"
/>
<div v-else-if="item.inputType == 'select'">
<div v-if="text.record[item.otherConditions] === 3 && item.dataIndex == 'day'">
<div style="display: inline-block">
<a-select
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
>
<a-select-option
v-for="(i, index) in simpleDayOptions"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
<div style="display: inline-block"> </div>
</div>
<div v-else-if="text.record[item.otherConditions] === 4 && item.dataIndex == 'month'">
<div style="display: inline-block">
<a-select
style="margin: -5px 0; display: inline-block"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
>
<a-select-option
v-for="(i, index) in item.componentProps.options"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
<div style="display: inline-block"></div>
</div>
<div v-else-if="text.record[item.otherConditions] === 4 && item.dataIndex == 'day'">
<div style="display: inline-block">
<a-select
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
>
<a-select-option
v-for="(i, index) in item.componentProps.options"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
<div style="display: inline-block"></div>
</div>
<div v-else-if="text.record[item.otherConditions] === 2 && item.dataIndex == 'week'">
<a-select
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
>
<a-select-option
v-for="(i, index) in item.componentProps.options"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
</div>
<!-- <a-select
v-else-if="
text.record[item.otherConditions] == item.otherValue && item.inputType == 'select'
"
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e.target.value, index, 'name')"
>
<a-select-option v-for="(i, index) in item.componentProps.options" :key="index">
{{ i.label }}
</a-select-option>
</a-select> -->
<a-time-picker
v-else-if="
item.otherConditions
? item.otherConditions && item.inputType == 'date'
: item.inputType == 'date'
"
style="width: 100px"
size="small"
:defaultValue="text.text ? moment(text.text, 'HH:mm:ss') : ''"
@change="(val, dateStrings) => changeTime(val, dateStrings, text.index, 'startTime')"
format="HH:mm:ss"
/>
<template
v-else-if="
item.otherConditions
? item.otherConditions && item.inputType == 'modal'
: item.inputType == 'modal'
"
>请选择</template
>
<template
v-else-if="
otherConditions
? otherConditions && item.inputType == 'input'
: item.inputType == 'operation'
"
>删除</template
>
<template v-else>{{ text.text }}</template>
</template>
</a-table>
</div>
<div v-else-if="columsType == 'normalTable'">
<!-- {{ $attrs.value }}--900099 -->
<a-table :columns="colums" :data-source="$attrs.value" bordered :pagination="false">
<template
v-for="(item, index) in colums"
:key="index"
#[item.dataIndex]="text, record, index"
>
<!-- {{ item }} -->
<a-input
v-if="item.inputType == 'input'"
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex, item)"
/>
<div v-else-if="item.inputType == 'select'">
<a-select
style="margin: -5px 0; display: inline-block"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
>
<a-select-option
v-for="(i, index) in item.componentProps.options"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
<div v-else-if="item.inputType == 'selectElse'">
<!-- {{ item.componentProps }} -->
<a-select
style="margin: -5px 0; display: inline-block"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex, item)"
>
<a-select-option
v-for="(i, index) in item.componentProps.options"
:key="index"
:value="i.value"
>
{{ i.label }}
</a-select-option>
</a-select>
</div>
<div v-else-if="item.inputType == 'selectApi'">
<NsSelectApi
v-bind="item.componentProps"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex)"
/>
</div>
<div v-else-if="item.inputType == 'upload'">
<NvUpload v-bind="item.componentProps" :value="text.text" />
</div>
<div v-else-if="item.inputType == 'date'">
<a-time-picker
style="width: 100px"
size="small"
:value="text.text ? moment(text.text, 'HH:mm:ss') : ''"
:defaultValue="text.text ? moment(text.text, 'HH:mm:ss') : ''"
@change="
(val, dateStrings) => changeTime(val, dateStrings, text.index, item.dataIndex)
"
format="HH:mm:ss"
/></div>
<template v-else-if="item.inputType == 'modal'">
<!-- 请选择 -->
<NsModalTable
v-bind="{
...item.componentProps,
record: text.record,
valueP: text.text,
dataIndex: item.dataIndex,
}"
@changeTable="changeTable"
:value="text.text"
/>
</template>
<template v-else-if="item.inputType == 'operation'">
<span v-for="(it, indexs) in item.componentProps.actionOptions" :key="indexs">
<span
:style="{ color: it.color }"
style="margin-left: 5px"
@click="optionA(it.value, text.record, text.index)"
>{{ it.name }}</span
>
</span>
</template>
<template v-else>{{ text.text }}</template>
</template>
<template #footer="currentPageData">
<!-- {{ currentPageData }} -->
<span style="font-size: 22px; margin-left: 50%" @click="add(currentPageData)">+</span>
</template>
</a-table>
</div>
<div v-else>
<a-row>
<a-col :span="12">
<div>未选择</div>
<br />
<a-row>
<a-col :span="12">护理类别</a-col>
<a-col :span="12"
><ns-select :options="optionSlect" @change="handleChangeViewList"
/></a-col>
</a-row>
<br />
<div>
<a-table
:columns="viewColums"
:data-source="careItems"
:row-selection="rowSelection"
:pagination="false"
bordered
/>
</div>
</a-col>
<a-col :span="12">
<div>已选择</div>
<!-- <br /> -->
<div style="height: 8px"></div>
<br />
<br />
<br />
<a-table :columns="colums" :data-source="$attrs.value" :pagination="false" bordered>
<template
v-for="(item, index) in colums"
:key="index"
#[item.dataIndex]="text, record, index"
>
<!-- {{ text }} -->
<a-input
v-if="item.inputType == 'input'"
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex, item)"
/>
<a-select
v-else-if="item.inputType == 'select'"
style="margin: -5px 0"
:value="text.text"
@change="(e) => handleChangeIndex(e, text.index, item.dataIndex, item)"
>
<a-select-option v-for="(i, index) in item.componentProps.options" :key="index">
{{ i.label }}
</a-select-option>
</a-select>
<template v-else-if="item.inputType == 'modal'">请选择</template>
<template v-else-if="item.inputType == 'operation'"
><span @click="handleDelete(text.index)" style="cursor: pointer">
删除
</span></template
>
<template v-else>{{ text.text }}</template>
</template>
</a-table>
</a-col>
</a-row>
</div>
</div>
</template>
<script lang="ts">
import moment from 'moment';
import { defineComponent, ref, watch } from 'vue';
import { NsMessage } from '../../message';
import { http } from '/nerv-lib/util/http';
import NsModalTable from './modal-table.vue';
import NvUpload from './uplods.vue';
export default defineComponent({
name: 'NsEtable',
components: {
NsModalTable,
NvUpload,
},
props: {
colums: {
type: Array,
},
viewColums: {
type: Array,
},
tableData: {
type: Array,
},
attribute: {
type: String,
},
onchange: {
type: Function,
},
columsType: {
type: String,
},
getChildrenOptions: {
type: Function,
},
addlist: {
type: Object,
},
},
emits: ['change', 'updata:longLat', 'validate', 'getChildrenOptions'],
setup(props, context) {
let data = [];
let propsData = ref();
let ids = ref(9);
const dependency = ref();
const selectElseOption = ref([]);
const rowSelection = {
type: 'checkbox',
onChange: (selectedRowKeys: [], selectedRows: []) => {
let dpData = context.attrs.value ? JSON.parse(JSON.stringify(context.attrs)) : [];
const newData = [...dpData.value];
selectedRows.forEach((item) => {
newData.push({
nursingItemUuid: item['uuid'],
nursingItemName: item['itemName'],
frequency: 1,
times: 1,
});
});
context.emit('change', newData);
},
};
const optionSlect = [
{
label: '全部',
value: 'all',
},
{
label: '生活护理',
value: 1,
},
{
label: '医疗护理',
value: 2,
},
{
label: '康复护理',
value: 3,
},
];
propsData = context.attrs.value ? JSON.parse(JSON.stringify(context.attrs.value)) : [];
console.log(propsData, '98777');
let simpleDayOptions = ref([]);
for (let i = 0; i < 28; i++) {
let item = {
label: '' + (i + 1),
value: i + 1,
};
simpleDayOptions.value.push(item);
}
let careItems = ref([]);
const handleChangeViewList = (value) => {
let api = '/api/pension/pension/objs/NursingItem';
http
.get(api, {
itemType: value == 'all' ? '' : value,
enable: true,
pageSize: 10,
})
.then((res) => {
if (res && res.success) {
careItems.value = res.data.data;
// NsMessage.success('');
}
})
.catch(() => {
NsMessage.error('列表获取失败');
});
};
handleChangeViewList('all');
const add = (e) => {
console.log(props.addlist, 'opopop');
// let data = {
// regionUuid: null,
// inspectionTime: null,
// personList: null,
// relation: null,
// // uuid:
// };
let list = JSON.parse(JSON.stringify(props.addlist));
let data = { ...list };
let edList = context.attrs.value ? JSON.parse(JSON.stringify(context.attrs.value)) : [];
ids.value += 1;
edList.push({ ...data, uuid: ids.value + 'uuid' });
context.emit('change', edList);
// console.log(e, JSON.parse(JSON.stringify(context.attrs.value)), 'add');
};
const changeTable = (item) => {
let currentList = JSON.parse(JSON.stringify(context.attrs.value));
currentList &&
currentList instanceof Array &&
currentList.length > 0 &&
currentList.forEach((its) => {
if (its.uuid == item.uuid) {
its = Object.assign(its, item);
}
});
context.emit('change', currentList);
};
//
const optionA = (name, actionInfo, ind) => {
let edList = context.attrs.value ? JSON.parse(JSON.stringify(context.attrs.value)) : [];
if (name == 'delete') {
edList.splice(ind, 1);
// console.log(edList, 'edlist');
} else if (name == 'copy') {
// console.log('00000');
ids.value += 1;
edList.push({ ...actionInfo, uuid: ids.value + 'uuid' });
}
context.emit('change', edList);
};
const getElseOptins = (name, value, fieldName, index) => {
console.log(name, value, fieldName, index, 'zhougg');
let acinfo = props && props.colums && props.colums[index];
console.log(acinfo, 'acinfo');
let api = acinfo.componentProps.api;
let params = {
...acinfo.componentProps.params,
[name]: value,
};
console.log(params, 'acparams');
// let api = '/api/pension/pension/objs/NursingItem';
http
.get(api, { ...params })
.then((res) => {
if (res && res.success) {
console.log(acinfo.componentProps.selectElseOption, 'acinfo.componentProps');
res.data &&
res.data.forEach((item) => {
if (item.dictValue == value) {
let arr = [];
item.subList &&
item.subList.forEach((it) => {
arr.push({
label: it.dictName,
value: it.dictValue,
});
});
selectElseOption.value = arr;
console.log(item.subList, 'sublist');
}
});
// careItems.value = res.data.data;
// NsMessage.success('');
}
})
.catch(() => {
NsMessage.error('列表获取失败');
});
};
// dependency
watch(dependency, (newValue, oldValue) => {
console.log('old', oldValue);
console.log('new', newValue);
});
return {
data,
simpleDayOptions,
rowSelection,
optionSlect,
handleChangeViewList,
careItems,
add,
changeTable,
optionA,
getElseOptins,
selectElseOption,
};
},
data() {
return {
editingKey: '',
propsList: [],
Month2Day: {
1: 31,
2: 28,
3: 31,
4: 30,
5: 31,
6: 30,
7: 31,
8: 31,
9: 30,
10: 31,
11: 30,
12: 31,
},
};
},
created() {
this.propsList = this.$attrs.value ? JSON.parse(JSON.stringify(this.$attrs.value)) : [];
},
methods: {
moment,
changeTime(val, dateStrings, key, type) {
let datapro = this.$attrs.value ? JSON.parse(JSON.stringify(this.$attrs.value)) : [];
const newData = [...datapro];
const target = newData.filter((item, index) => key === index)[0];
if (type == 'startTime' && target['endTime']) {
if (this.timeCompare(dateStrings, target['endTime'])) {
NsMessage.error('开始时间不能大于结束时间');
}
} else if (type == 'endTime') {
if (this.timeCompare(target['startTime'], dateStrings)) {
NsMessage.error('开始时间不能大于结束时间');
}
}
if (target) {
target[type] = dateStrings;
this.$emit('change', newData);
// this.$emit('validate', false);
// this.propsList = newData;
}
},
timeCompare(start, end) {
let flag = true;
if (!start || !end) return true;
let startH = +start.split(':')[0];
let startM = +start.split(':')[1];
let endH = +end.split(':')[0];
let endM = +end.split(':')[1];
//
if (startH > endH) {
flag = false;
} else if (startH == endH) {
//
if (startM >= endM) {
flag = false;
}
}
return flag;
},
handleChangeIndex(value, key, type, item) {
console.log(value, key, type, 'zhouhaha');
let propsInfo = this.$attrs.value ? JSON.parse(JSON.stringify(this.$attrs.value)) : [];
const newData = [...propsInfo];
console.log(newData, 'newData');
console.log([...this.propsList], 'newData');
console.log(this.propsList, 'newData');
const target = newData.filter((item, index) => key === index)[0];
if (type == 'month') {
this.colums &&
this.colums.forEach((item: any) => {
if (item.dataIndex == 'day') {
item.componentProps.options = this.setOptions(this.Month2Day[value], 'dayOptions');
}
});
}
console.log(target, 'target');
if (target) {
target[type] = value;
console.log(newData, 'newData');
this.$emit('change', newData);
// this.propsList = newData;
}
this.colums &&
this.colums.forEach((item, index) => {
console.log(item, item.dependency, '564');
if (item.dependency == type) {
// console.log('lopppp');
this.$emit('getChildrenOptions', item.dependency, value, item.dataIndex);
this.getElseOptins(item.dependency, value, item.dataIndex, index);
}
});
},
handleDelete(e) {
let data = this.$attrs.value ? JSON.parse(JSON.stringify(this.$attrs.value)) : [];
let newData = data.filter((item, index) => e !== index);
// console.log(newData);
this.$emit('change', newData);
},
setOptions(number: number, type: string) {
let arr = [];
for (let i = 0; i < number; i++) {
let item = {
label: '' + (i + 1),
value: i + 1,
};
arr.push(item);
}
return arr;
},
},
});
</script>
<style lang="less" scoped>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
#map-container {
width: 600px;
height: 400px;
}
.leftBox,
.rightBox {
flex: 1;
}
.inputArea {
display: flex;
line-height: 30px;
}
.inputSearch {
.inputAdress {
width: 300px;
margin-right: 10px;
}
}
</style>

4
lib/component/form/etable/index.ts

@ -0,0 +1,4 @@
import etable from './etable.vue';
import { withInstall } from '/nerv-lib/util';
export const NsEtable = withInstall(etable);

308
lib/component/form/etable/modal-table.vue

@ -0,0 +1,308 @@
<template>
<div style="">
<span @click="chose" v-if="!label" style="cursor: pointer">请选择</span>
<span @click="chose" v-else style="cursor: pointer; color: rgb(14, 210, 191)">
{{ label }}
</span>
</div>
<div id="inputarea">
<a-modal
v-model:visible="visible"
:maskClosable="false"
@ok="handleOk"
@cancel="cancel"
width="900px"
style="top: 3%"
:title="title"
>
<!-- <p class="title">{{ title }}</p> -->
<div v-if="ifTab">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane v-for="(item, index) in tabs" :key="index">
<template #tab>
<span> {{ item.name }} </span>
</template>
<ns-view-list-table v-bind="item.tableConfig" :row-selection="rowSelection" />
</a-tab-pane>
</a-tabs>
</div>
<div v-else>
<ns-view-list-table v-bind="tableConfig" :row-selection="rowSelection" />
</div>
</a-modal>
</div>
</template>
<script lang="ts">
import { runInThisContext } from 'node:vm';
import { defineComponent, ref } from 'vue';
import AppVue from '/@/App.vue';
// let rowSelection='';
export default defineComponent({
name: 'NsModalTable',
props: {
api: {
type: String,
default: '',
},
defaultKey: {
type: String || Array,
default: '',
},
alert: {
type: String,
default: '',
},
addonAfter: {
type: String,
default: '',
},
ifTab: {
type: Boolean,
default: false,
},
tabs: {
type: Array,
default: () => [],
},
tableConfig: {
type: Object,
default: () => {},
},
labelValue: {
type: String,
default: '',
},
keyValue: {
type: String || Array,
default: '',
},
field: {
type: String,
},
type: {
type: String,
default: 'radio',
},
formModel: {
type: Object,
},
allowClear: { type: Boolean },
size: { type: String },
disabled: { type: Boolean },
id: { type: String },
title: {
type: String,
},
changeTable: {
type: Function,
},
record: {
type: Object,
},
dataIndex: {
type: String,
},
//
extraSelect: {
type: Array,
},
valueP: {
type: Object || String,
},
//
showLabelValue: {
type: String,
},
},
emits: ['change', 'blur', 'changeTable'],
setup(props, { emit, attrs }) {
// console.log(JSON.parse(JSON.stringify(props.record)), 'zhouzhoutedfg');
// console.log(attrs, 'attrs123');
let newValue = attrs.value;
var label = '';
let showlabel = ref();
showlabel = props.showLabelValue;
// console.log(props.dataIndex, 'zhouzhou');
let actionInfo = props.record;
if (props.dataIndex) {
label = actionInfo && actionInfo[props.dataIndex + 'Name'];
// console.log(label, 'zhouxxlabel');
}
if (showlabel) {
label = props.record && props.record[showlabel];
let labelType = props.valueP;
if (labelType && labelType instanceof Array && labelType.length > 0) {
let arr: any[] = [];
labelType.forEach((element: { [x: string]: any }) => {
if (element[showlabel]) {
arr.push(element[showlabel]);
}
});
label = arr.join(',');
}
// console.log(props.valueP, 'valueP');
emit('change', props.valueP);
}
if (newValue) {
// console.log(newValue, 'label');
label = newValue;
emit('change', props.valueP);
}
return {
label,
};
},
data(props) {
return {
activeKey: 0,
visible: false,
key: props.defaultKey || '' || [],
roomUuid: '',
//
iflabelValue: '',
ifkeyValue: props.defaultKey || '' || [],
selectedRows: [],
// label: '',
rowSelection: {
type: props.type,
//
getCheckboxProps(record: { [x: string]: string }) {
let uuid = [] || '';
//table
if (props.ifTab) {
for (const iterator of props.tabs) {
(uuid as Array<any>).push(iterator.tableConfig.rowKey);
}
//
if (props.type == 'radio') {
for (const iterator of uuid) {
if (iterator in record) {
return {
defaultChecked: record[iterator] == props.defaultKey, //
};
}
}
}
//
else if (props.type == 'checkbox') {
for (const iterator of uuid) {
if (iterator in record) {
for (const defaultKey of props.defaultKey) {
return {
defaultChecked: record[iterator] == defaultKey, //
};
}
}
}
}
}
//table
else {
if (props.type == 'radio') {
return {
defaultChecked: record[props.keyValue] == props.defaultKey, //
};
} else if (props.type == 'checkbox') {
for (const iterator of props.defaultKey) {
return {
defaultChecked: record[props.keyValue] == iterator, //
};
}
}
}
},
onChange: (selectedRowKeys: [], selectedRows: []) => {
let labelDemo = [];
let selectedRowsValue = JSON.parse(JSON.stringify(selectedRows));
this.selectedRows = JSON.parse(JSON.stringify(selectedRows));
if (props.type == 'radio') {
this.ifkeyValue = selectedRowKeys[0];
this.iflabelValue = selectedRowsValue[0][this.labelValue];
} else if (props.type == 'checkbox') {
for (const iterator of selectedRowsValue) {
labelDemo.push(iterator[this.labelValue]);
}
this.ifkeyValue = selectedRowKeys;
this.iflabelValue = labelDemo.join();
}
},
},
};
},
mounted() {
// this.set(obj, "newField", "newData")
},
methods: {
chose() {
this.visible = true;
},
handleOk() {
//--
let that = this;
this.label = this.iflabelValue;
this.key = this.ifkeyValue;
// console.log(this.label, this.key, '');
let data = {
name: this.label,
value: this.key,
};
let moreSelects: any;
let selectedItems = JSON.parse(JSON.stringify(this.selectedRows));
// console.log(selectedItems, 'selectedItemszhouzhou ');
selectedItems &&
selectedItems.forEach((it) => {
if (this.extraSelect) {
this.extraSelect.forEach((element) => {
moreSelects = {
...moreSelects,
[element.name]: it[element.value],
};
});
// item = Object.assign(item, moreSelects);
}
});
let changeData = Object.assign(this.record, moreSelects);
changeData[this.dataIndex] = this.key;
changeData[this.dataIndex + `Name`] = this.label;
// console.log(changeData, 'changeData');
this.$emit('changeTable', changeData);
this.visible = false;
},
cancel() {
console.log('cancle');
//-
this.visible = false;
},
},
});
</script>
<style lang="less" scoped>
.ns-area:hover,
:focus {
color: white !important;
}
.ns-area {
color: white;
}
:deep(.ant-input) {
background: rgba(223, 227, 233, 0.5);
border-right: 0;
}
:deep(.ant-input:focus) {
border-color: rgba(223, 227, 233, 0.5);
border-right-width: 0 !important;
outline: 0;
box-shadow: none;
}
:deep(.ns-area) {
border: 0;
}
</style>

323
lib/component/form/etable/uplods.vue

@ -0,0 +1,323 @@
<template>
<div>
<div>
<a-upload
:before-upload="beforeUpload"
@change="handleChange"
:multiple="count != 1"
:customRequest="selfUpload"
:showUploadList="false"
>
<!-- {{ $attrs.value }} -->
<span style="color: #17be6b">{{ $attrs.value ? '证书文件' : fileName }}&nbsp;&nbsp;</span>
<span>上传pdf文件</span>
</a-upload>
<!-- 上传图片框/内置input /-->
</div>
<!-- 错误消息提示 -->
<div class="err-msg" v-if="!isJpgOrPngOrJpeg">
<p>{{ fileName }} 文件上传失败</p>
<p :style="{ color: 'red' }"> 请选择{{ fileType.join(',') }}123 </p>
</div>
<div class="err-msg" v-if="isJpgOrPngOrJpeg ? !isLt5M : ''">
<p>{{ fileName }} 文件上传失败</p>
<p :style="{ color: 'red' }"> 请选择{{ maxSize / 1024 / 1024 }} </p>
</div>
<!-- 错误消息提示 /-->
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { http } from '/nerv-lib/util/http';
import { NsMessage } from '../../message';
import { context } from 'ant-design-vue/es/vc-image/src/PreviewGroup';
interface FileItem {
uid: string;
type: string;
size: number;
name?: string;
status?: string;
response?: string;
percent?: number;
url?: string;
preview?: string;
originFileObj?: any;
}
interface FileInfo {
file: FileItem;
fileList: FileItem[];
}
// base64
function getBase64(file: File) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
}
export default defineComponent({
name: 'NvUpload',
components: {},
props: {
//
url: {
type: String,
require: true,
},
//
// maxSize: {
// type: Number,
// / default: 5242880,
// },
//
fileType: {
type: Array,
default: () => {
return ['jpg', 'png', 'jpeg'];
},
},
//
count: {
type: Number,
default: 1,
},
// 0-1-2-
uploadType: {
type: Number,
default: 1,
},
},
emits: ['change'],
setup(props, { emit, attrs }) {
console.log(props, 'propslk');
const host = window.location.host;
const previewVisible = ref<boolean>(false);
const isLt5M = ref<boolean>(true);
const isJpgOrPngOrJpeg = ref<boolean>(true);
const previewImage = ref<string | undefined>('');
const fileName = ref<string | undefined>('');
const fileList = ref<FileItem[]>([]);
const currentImg = ref<string[]>([]);
const maskShow = ref<boolean[]>([]);
const fileUuid = ref<string>('');
// console.log(attrs.value, 'uuuuiiiii');
// fileName = `http://` + host + `/api/pension/pension/objs/CertificateFile/` + attrs.value;
const acceptType = computed(() =>
props.fileType.map((item: String) => {
return 'application/' + item;
})
);
const beforeUpload = (file: FileItem) => {
// true
isLt5M.value = true;
isJpgOrPngOrJpeg.value = true;
// ,gif
if (file.type === 'image/gif') {
NsMessage.warn('不支持gif图片');
return false;
}
console.log(file.type, acceptType.value, 'zhouzhuupload');
isJpgOrPngOrJpeg.value = acceptType.value.includes(file.type);
//
// if (file.size > props.maxSize) {
// isLt5M.value = false;
// }
fileName.value = file.name;
return isLt5M.value && isJpgOrPngOrJpeg.value;
};
const handleChange = ({ fileList: newFileList }: FileInfo) => {
console.log(fileList, 'FileInfo');
//
// if (props.count === 1) {
// // newFileList.length = 0
// // !isLt5M.valuetrue
// if (!isLt5M.value || !isJpgOrPngOrJpeg.value || newFileList.length - 1 < 0) {
// //
// fileList.value = [];
// } else {
// // \
// // newFileList[newFileList.length - 1]
// fileList.value = [newFileList[newFileList.length - 1]];
// }
// } else {
// //
// if (!isLt5M.value || !isJpgOrPngOrJpeg.value) {
// fileList.value = newFileList.slice(0, newFileList.length - 1);
// } else {
// fileList.value = newFileList;
// }
// }
};
const selfUpload = async ({ file }) => {
console.log(file, 'file');
const params = {
uploadType: props.uploadType,
};
const formData = new FormData();
formData.append('file', file);
formData.append('uploadType', props.uploadType);
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
params: params,
};
console.log('uploading....', props.url, formData, config);
http
.post(props.url, formData, config)
.then((res) => {
fileUuid.value = res.data.fileUuid;
emit('change', fileUuid.value);
})
.catch((e) => {
console.log(e, 'e');
NsMessage.error('上传失败,请重试');
});
};
const handlePreview = async (index: number) => {
previewImage.value = (await getBase64(fileList.value[index].originFileObj)) as string;
previewVisible.value = true;
};
const deleteImg = (index: number) => {
currentImg.value.splice(index, 1);
fileList.value.splice(index, 1);
};
const handleCancel = () => {
previewVisible.value = false;
};
const mouseEnter = (index: number) => {
maskShow.value[index] = true;
};
const mouseLeave = (index: number) => {
maskShow.value[index] = false;
};
return {
previewVisible,
previewImage,
fileList,
isLt5M,
isJpgOrPngOrJpeg,
fileName,
currentImg,
maskShow,
fileUuid,
selfUpload,
handleCancel,
handlePreview,
handleChange,
beforeUpload,
mouseEnter,
mouseLeave,
deleteImg,
};
},
});
</script>
<style>
.ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card {
margin: 0;
width: 80px;
height: 80px;
border: 1px solid #d9d9d9;
}
.ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card:hover {
border-color: #d9d9d9;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
color: #666;
font-size: 12px;
}
.ant-upload-picture-card-wrapper .ant-upload-list-picture-card-container {
width: 88px;
height: 80px;
}
.ant-upload-picture-card-wrapper .ant-upload-list-picture-card .ant-upload-list-item {
margin: 0;
padding: 0;
width: 80px;
height: 80px;
}
.title,
.err-msg {
text-align: left;
}
.err-msg p {
margin: 0;
}
.container {
display: flex;
}
.imgList {
display: flex;
}
.imgContainer {
margin-right: 16px;
border: 1px solid #d9d9d9;
position: relative;
}
.imgCover {
width: 80px;
height: 80px;
object-fit: contain;
}
.imgContainer .mask {
position: absolute;
top: 0;
left: 0;
width: 80px;
height: 80px;
background: rgba(0, 0, 0, 0.5);
text-align: center;
line-height: 80px;
}
.mask .anticon-eye {
color: white;
margin-right: 18px;
}
.mask .anticon-eye:hover {
color: #00acff;
margin-right: 18px;
}
.mask .anticon-delete {
color: white;
}
.mask .anticon-delete:hover {
color: #00acff;
}
</style>

27
lib/component/form/field-registry.ts

@ -0,0 +1,27 @@
import type { Component } from 'vue';
class FieldRegistry {
private static readonly _instance: FieldRegistry = new FieldRegistry();
private readonly _map: Map<string, Component>;
constructor() {
this._map = new Map<string, Component>();
}
push(compName: string, component: Component) {
this._map.set(compName, component);
}
static get instance() {
return this._instance;
}
get(compName: string) {
return this._map.get(compName);
}
get map() {
return this._map;
}
}
const fieldRegistry = FieldRegistry.instance;
export { fieldRegistry };

51
lib/component/form/filter-table/filter-table.vue

@ -0,0 +1,51 @@
<template>
<div class="dropdown">
<ns-form
ref="nsTableRef"
class="ns-table-form"
v-bind="formConfig"
:model="formModel"
@finish="formFinish" />
</div>
<div class="table">
<ns-table v-bind="tableConfig" :params="params" />
</div>
</template>
<script lang="ts">
import { defineComponent, unref, ref, reactive } from 'vue';
export default defineComponent({
name: 'NsFilterTable',
props: {
tableConfig: Object,
formConfig: Object,
},
setup(props) {
const nsTableRef = ref();
const formConfig = props['formConfig'];
let params = reactive(props['tableConfig']['params']);
unref(formConfig)['schemas'].forEach((item) => {
if (item['immediate']) {
item['componentProps'][item['immediateName']] = function () {
unref(nsTableRef).triggerSubmit();
};
}
});
const formModel = reactive<Record<string, any>>({});
const formFinish = function (data: object) {
params = Object.assign(params, data);
};
return {
nsTableRef,
formModel,
params,
formFinish,
};
},
});
</script>
<style lang="less" scoped></style>

7
lib/component/form/filter-table/index.ts

@ -0,0 +1,7 @@
import type { App } from 'vue';
import nsFilterTable from './filter-table.vue';
export const NsFilterTable = function (app: App) {
app.component(nsFilterTable.name, nsFilterTable);
return app;
};

58
lib/component/form/form-util.ts

@ -0,0 +1,58 @@
import { isArray, isUndefined } from 'lodash-es';
import { dateUtil } from '/nerv-lib/util/date-util';
const DATE_TYPE = [
'NsDatePicker',
'NsMonthPicker',
'NsWeekPicker',
'NsTimePicker',
'NsRangePicker',
'ADatePicker',
'AMonthPicker',
'AWeekPicker',
'ATimePicker',
'ARangePicker',
];
const INPUT_TYPE = ['NsInput', 'AInput'];
/**
*
*/
export function isDateType(component: string | undefined) {
if (isUndefined(component)) return false;
return DATE_TYPE.includes(component);
}
/**
*
*/
export function isInputType(component: string | undefined) {
if (isUndefined(component)) return false;
return INPUT_TYPE.includes(component);
}
/**
*moment格式
* @param component
* @param value
*/
export const transformDate = (component: string, value: any, format?: any) => {
if (value && isDateType(component)) {
if (isArray(value)) {
return value.map((item) => validTime(item, format));
}
return validTime(value, format);
}
};
/**
*
* @param time
* @param format
*/
export function validTime(time: any, format: string) {
if (dateUtil(time, format, true).isValid()) {
return time;
} else {
return dateUtil(time).format(format);
}
}

122
lib/component/form/form/child-form.vue

@ -0,0 +1,122 @@
<!-- @format -->
<template>
<a-row class="ns-form-body" :justify="formLayout.justify">
<a-col :span="24" class="ns-child-form-title" v-html="title" v-if="title" />
<template v-for="(schema, index) in getSchema" :key="schema.field">
<ns-form-item
:span="getComponentSpan(schema)"
:schema="schema"
:index="index"
:formModel="formModel">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</ns-form-item>
</template>
<!-- 占位解决三列中部为空的问题 -->
<a-col :span="formLayout.span" class="ns-form-item ns-form-item-placeholder" />
</a-row>
<a-divider class="ns-child-form-divider" />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { computed, defineComponent, inject, ref, watch } from 'vue';
import { useFormModel } from '/nerv-lib/component/form/form/use-form-model';
export default defineComponent({
name: 'NsChildForm',
props: {
title: {
type: String,
default: '',
},
schemas: {
type: Array as PropType<FormSchema[]>,
default: () => [],
},
formModel: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
},
setup(props, { attrs, slots }) {
const formElRef = ref();
// const addChildForm = inject('addChildForm');
const formLayout = inject('formLayout');
// formElRef.value ? addChildForm(formElRef) : '';
// addChildForm(formElRef);
const getBindValue = computed(() => ({
...attrs,
...props,
}));
const getComponentSpan = (schema: FormSchema) => {
if (schema.class?.includes('ns-form-item-full')) {
return 24;
}
return formLayout?.span;
};
const getSchema = computed((): FormSchema[] => {
const { schemas } = props;
const formSchemas = schemas.filter((schema) => schema.component);
return formSchemas as FormSchema[];
});
const isInitDefaultValueRef = ref(false);
const {
handleFormModel,
initFormModel,
resetFormModel,
setFormModel,
unsetFormModel,
getFormModel,
} = useFormModel({
schemas: props.schemas,
formModel: props.formModel,
formElRef,
});
watch(
() => getSchema.value,
() => {
if (isInitDefaultValueRef.value) {
return;
}
initFormModel();
isInitDefaultValueRef.value = true;
},
{
immediate: true,
deep: true,
},
);
return {
formElRef,
getBindValue,
formLayout,
getSchema,
getComponentSpan,
};
},
});
</script>
<style lang="less" scoped>
.ns-child-form-title {
font-size: 16px;
font-weight: bold;
line-height: 24px;
padding: 32px 0 24px 0;
}
.ns-child-form-divider {
margin: 8px 0 0 0;
}
.ns-form-item-placeholder {
min-height: 0;
height: 0;
}
</style>

335
lib/component/form/form/form-item.vue

@ -0,0 +1,335 @@
<!-- @format -->
<template>
<a-col
:span="span"
class="ns-form-item"
:class="getFormItemClass"
v-if="getIfShow"
v-show="getShow">
<a-form-item v-if="getFormItemDisplay" v-bind="formItemProps">
<template #[item]="data" v-for="item in Object.keys(getSlots)">
<slot :name="schema.field + '_' + item" v-bind="data || {}"></slot>
</template>
<component :is="schema.component" v-bind="formProps">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</component>
<!-- <div class="ns-tips">-->
<!-- <slot :name="`${schema.field}_tips`"></slot>-->
<!-- </div>-->
</a-form-item>
<template v-else>
<component :is="schema.component" v-bind="formProps">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</component>
<!-- <div class="ns-tips">-->
<!-- <slot :name="`${schema.field}_tips`"></slot>-->
<!-- </div>-->
</template>
</a-col>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { computed, defineComponent, inject, nextTick, provide, ref, unref } from 'vue';
import {
forEach,
isArray,
isBoolean,
isFunction,
isObject,
isString,
isUndefined,
mapKeys,
upperFirst,
get,
} from 'lodash-es';
import { isInputType } from '/nerv-lib/component/form/form-util';
import { useParams } from '/nerv-lib/use/use-params';
export default defineComponent({
name: 'NsFormItem',
components: {},
props: {
span: Number,
schema: {
type: Object as PropType<FormSchema>,
default: () => ({}),
},
show: {
type: Boolean,
default: true,
},
formModel: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
index: {
type: Number,
default: 0,
},
},
setup(props, { slots }) {
const components = inject('components', () => ({}))();
const setFormModel = inject('setFormModel') as Function;
const getFormModel = inject('getFormModel') as Function;
const submit = inject('submit') as Function;
const validateRef = ref({});
const formLayout = inject('formLayout');
const { getParams } = useParams();
const getSlots = computed(() => {
const { schema } = props;
return mapKeys(slots, (_val, key) => {
return schema.field ? key.replace(schema.field + '_', '') : key;
});
});
provide(
'rules',
computed(() => {
return props.schema?.rules || [];
}),
);
const formItemProps = computed(() => {
const {
schema: { field, rules, label, component, autoLink, formItemProps, extra },
} = props;
const tableComponent = ['nsTable'];
let nsClass = '';
component && tableComponent.includes(component) && (nsClass = 'ns-form-item-validate-self');
return {
class: nsClass,
name: field,
rules: rules || [],
label: label,
autoLink: autoLink || true,
extra: extra,
...formItemProps,
...validateRef.value,
};
});
const getFormItemDisplay = computed(() => {
const { schema } = props;
return schema.displayFormItem !== false;
});
const getFormItemClass = computed(() => {
const { schema } = props;
return schema.class;
});
const getDisabled = computed(() => {
const { dynamicDisabled } = props.schema;
if (dynamicDisabled) {
if (isBoolean(dynamicDisabled)) {
return { disabled: dynamicDisabled };
}
if (isFunction(dynamicDisabled)) {
return { disabled: dynamicDisabled(props.formModel) };
}
return {};
}
return {};
});
const getIfShow = computed(() => {
const { ifShow } = props.schema;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(props.formModel);
}
//
if (!isIfShow) {
setFormModel(props.schema.field, null);
}
return isIfShow && props.show;
});
const getShow = computed(() => {
const { show } = props.schema;
let isShow = true;
if (isBoolean(show)) {
isShow = show;
}
if (isFunction(show)) {
isShow = show(props.formModel);
}
return isShow;
});
// console.log(props.schema?.field, getShow.value, props.schema);
const getDynamicParams = computed(() => {
const { dynamicParams, defaultParams, componentProps = {} } = props.schema;
const { params = {} } = componentProps;
if (dynamicParams) {
const data = getParams(props.formModel, dynamicParams, { ...params, ...defaultParams });
// console.log('getParams', data);
return data;
} else {
return { ...params, ...defaultParams };
}
});
/**
* 检测requiredParams是否全部获得数据
* @param params
*/
function checkRequiredParams(params: Recordable) {
const { dynamicParams } = props.schema;
let { requiredParams } = props.schema;
if (requiredParams) {
if (requiredParams === true) requiredParams = dynamicParams as any;
if (isFunction(requiredParams)) {
console.error(
'Property dynamicParams of props cannot set to Function when using requiredParams',
);
return false;
} else {
if (isString(requiredParams)) {
if (isUndefined(params[requiredParams])) return false;
} else if (isArray(requiredParams)) {
for (let i = 0, l = requiredParams.length; i < l; i++) {
if (isUndefined(params[requiredParams[i]])) return false;
}
} else if (isObject(requiredParams)) {
const keys = Object.keys(requiredParams);
for (let i = 0, l = keys.length; i < l; i++) {
if (isUndefined(params[keys[i]])) return false;
}
}
}
return true;
}
return true;
}
const formProps = computed(() => {
const { formModel } = props;
const {
component,
field,
dynamicParams,
changeEvent = 'change',
valueField,
addModel = [],
autoAddLink = false,
autoSubmit = false,
} = props.schema;
const isCheck =
component && ['NsSwitch', 'NsCheckbox', 'Switch', 'Checkbox'].includes(component);
const eventKey = `on${upperFirst(changeEvent)}`;
const attr: Recordable = {};
if (isInputType(component)) {
attr.allowClear = true;
}
const propsData: Recordable = {
field,
dynamicParams,
...attr,
size: 'default',
...unref(getComponentsProps),
...unref(getDisabled),
};
//
if (getDynamicParams.value) {
if (props.schema?.requiredParams) {
if (checkRequiredParams(getDynamicParams.value)) {
propsData.params = getDynamicParams.value;
propsData.checkRequiredParams = true;
} else {
propsData.checkRequiredParams = false;
}
} else {
propsData.params = getDynamicParams.value;
}
}
const bindValue: Recordable = {
[valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
};
const on = {
[eventKey]: (...args: Nullable<Recordable>[]) => {
const [e] = args;
if (propsData[eventKey]) {
(propsData[eventKey] as Function)(...args);
}
const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e;
setFormModel(field, value);
if (isArray(addModel)) {
addModel.forEach((item) => {
setFormModel(item, args[1] && args[1][item]);
});
} else {
forEach(addModel as any, (value, key) => {
setFormModel(key, args[1] && get(args[1], value));
});
}
if (autoAddLink) {
const fieldLink = getFormModel('fieldLink') || {};
fieldLink[field] = args[1];
setFormModel('fieldLink', fieldLink);
}
autoSubmit && nextTick(submit as any);
},
onValidateChange: (text: Object | undefined) => {
if (isUndefined(text)) text = {};
validateRef.value = text;
},
};
return {
...propsData,
...on,
...bindValue,
formModel,
};
});
const getComponentsProps = computed(() => {
const { componentProps = {} } = unref(props).schema;
return componentProps;
});
return {
formItemProps,
formProps,
getShow,
getIfShow,
components,
getFormItemDisplay,
getFormItemClass,
getSlots,
formLayout,
};
},
beforeCreate() {
this.$options.components = this.components;
},
});
</script>
<style lang="less" scoped>
.ns-tips {
height: auto;
line-height: 32px;
width: auto;
display: inline-block;
}
</style>

37
lib/component/form/form/form.d.ts

@ -0,0 +1,37 @@
import type { RuleObject } from 'ant-design-vue/es/form/interface';
declare global {
type Rule = RuleObject & {
trigger?: 'blur' | 'change' | ['change', 'blur'];
};
interface FormSchema {
field: string;
label?: string;
changeEvent?: string; //表单更新事件名称
valueField?: string;
component?: string; // 不定义,则为隐藏发送参数
rules?: Recordable[];
defaultValue?: any;
componentProps?: any;
formItemProps?: any;
viewOnly?: Boolean; // 是否只展示,不发送参数
ifShow?: Boolean | Function; //dom隐藏
show?: Boolean | Function; //css隐藏
// disabled?: Boolean; //是否禁用 (已废弃、和动态有重复部分)
dynamicDisabled?: Boolean | Function; //是否禁用
fieldMap?: Array<string> | Boolean | Props; //把一个值分割成多个值
keepField?: Boolean; //映射时是否保留原值
defaultParams?: Object; // 默认接口参数
dynamicParams?: String | Array<string> | Recordable | Function; //动态接口参数,合并defaultParams赋值给params
requiredParams?: Boolean | String | Array<string> | Recordable;
addModel?: Array<string> | Props; // 额外发送参数,例如select中
autoAddLink?: Boolean; //把需要联动的值映射到 formModel.fieldLink.field
displayFormItem?: Boolean; //是否显示formItem (label)
autoLink?: true; //是否使用规则自动绑定
class?: String; //添加额外样式
autoSubmit: Boolean; //是否操作后提交表单
format: Function;
}
}

338
lib/component/form/form/form.vue

@ -0,0 +1,338 @@
<!-- @format -->
<template>
<a-form
class="ns-form"
:class="getFormClass.class"
v-bind="getBindValue"
ref="formElRef"
:model="formModel">
<a-row class="ns-form-body" :justify="getFormClass.justify" :gutter="getFormClass.gutter">
<template v-for="(schema, index) in getSchema" :key="schema.field">
<ns-form-item
:show="expandRef || index < splitNumber"
:span="getComponentSpan(schema)"
:schema="schema"
:index="index"
:formModel="formModel">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</ns-form-item>
</template>
<a-col
v-if="
showAction
? expandRef && getSchema.length % splitNumber === 0
: (getSchema.length + 1) % splitNumber === 0
"
:span="getFormClass.span" />
<a-col v-if="showAction" :span="getFormClass.span" class="ns-operate">
<a-button @click="reset">重置</a-button>
<a-button type="primary" html-type="submit" :loading="loading">搜索</a-button>
<a-button
type="link"
class="ns-operate-expand"
@click="expandRef = !expandRef"
v-if="getSchema.length > splitNumber && showExpand">
<template v-if="expandRef">
收起
<UpOutlined />
</template>
<template v-else>
展开
<DownOutlined />
</template>
</a-button>
</a-col>
</a-row>
<!-- <template v-for="(schema, index) in getSchema" :key="schema.field">-->
<!-- <ns-form-item :schema="schema" :index="index" :formModel="formModel">-->
<!-- <template #[item]="data" v-for="item in Object.keys($slots)">-->
<!-- <slot :name="item" v-bind="data || {}"></slot>-->
<!-- </template>-->
<!-- </ns-form-item>-->
<!-- </template>-->
<!-- <a-row-->
<!-- v-if="showAction"-->
<!-- style="text-align: right; margin-left: auto"-->
<!-- class="ns-form-item ns-form-item-op">-->
<!-- <a-col span="24" class="operate">-->
<!-- <a-button @click="reset">重置</a-button>-->
<!-- <a-button style="margin-left: 10px" type="primary" html-type="submit" :loading="loading"-->
<!-- >搜索</a-button-->
<!-- >-->
<!-- </a-col>-->
<!-- </a-row>-->
</a-form>
</template>
<!--suppress TypeScriptCheckImport -->
<script lang="ts">
import { computed, defineComponent, nextTick, provide, ref, toRefs, watch } from 'vue';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
import NsFormItem from '/nerv-lib/component/form/form/form-item.vue';
import { useFormModel } from '/nerv-lib/component/form/form/use-form-model';
import { formConfig } from '/nerv-base/config/form.config';
import { formProps } from '/nerv-lib/component/form/form/props';
import { isBoolean, isFunction } from 'lodash-es';
import FormValidator from 'async-validator';
import { watchDebounced, useElementSize } from '@vueuse/core';
export default defineComponent({
name: 'NsForm',
components: { NsFormItem, DownOutlined, UpOutlined },
props: formProps,
emits: ['finish', 'submit', 'reset'],
setup(props, { attrs, emit }) {
const formElRef = ref<null | Recordable>(null);
const validateResult = ref(false);
const { schemas } = toRefs(props);
const isInitDefaultValueRef = ref(false);
const expandRef = ref(props.expand);
const formModel = computed(() => {
return props.model;
});
const childForms = ref<any[]>([]);
function addChildForm(form: any) {
childForms.value.push(form);
}
let splitNumber = ref(3);
const { width: formWidth } = useElementSize(formElRef);
provide('addChildForm', addChildForm);
const getFormClass = computed(() => {
if (props.formLayout === 'flexVertical') {
return formConfig.formLayout.flexVertical;
}
if (props.formLayout === 'flex') {
return formConfig.formLayout.flex;
}
if (props.formLayout === 'flexv2') {
return formConfig.formLayout.flexv2;
}
return formConfig.formLayout.vertical;
});
const FULL_SPAN_LIST = ['NsChildForm'];
const getComponentSpan = (schema: FormSchema) => {
if (FULL_SPAN_LIST.includes(schema.component || '')) {
return 24;
}
if (schema.class?.includes('ns-form-item-full')) {
return 24;
}
return getFormClass.value.span;
};
provide('formLayout', getFormClass.value);
const getBindValue = computed(() => ({
...getFormClass.value,
...attrs,
...props,
title: '',
onSubmit: submit,
onFinish: finish,
}));
const getSchema = computed((): FormSchema[] => {
const { schemas } = props;
const formSchemas = schemas.filter((schema) => schema.component);
return formSchemas as FormSchema[];
});
watchDebounced(
() => formModel.value,
() => {
const rules = {};
setRules(rules, getSchema.value);
const validator = new FormValidator(rules);
validator
.validate(formModel.value)
.then(() => {
validateResult.value = true;
})
.catch(() => {
validateResult.value = false;
});
},
{
debounce: 100,
deep: true,
},
);
watchDebounced(
() => formWidth.value,
(val) => {
if (val <= 768 && getFormClass.value['sm']) {
getFormClass.value.span = getFormClass.value['sm'];
splitNumber.value = 2;
}
if (val > 768 && getFormClass.value['lg']) {
getFormClass.value.span = getFormClass.value['lg'];
splitNumber.value = 3;
}
},
{
debounce: 100,
deep: true,
},
);
function setRules(rules: Recordable, schemas: any[]) {
schemas.forEach((schema) => {
if (schema.rules) {
const { ifShow } = schema;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(formModel.value);
}
// console.log('ifShow', isIfShow);
if (isIfShow) {
rules[schema.field] = schema.rules;
}
}
if (schema.componentProps?.schemas) {
setRules(rules, schema.componentProps.schemas);
}
});
}
const {
handleFormModel,
initFormModel,
resetFormModel,
setFormModel,
unsetFormModel,
getFormModel,
} = useFormModel({
schemas,
formModel,
formElRef,
emit,
});
watch(
() => getSchema.value,
() => {
if (isInitDefaultValueRef.value) {
return;
}
initFormModel();
isInitDefaultValueRef.value = true;
},
{
immediate: true,
deep: true,
},
);
/**
* 数据验证成功后回调
*/
const submit = () => {
emit('submit', formModel.value);
return new Promise((resolve) => {
resolve(formModel.value);
});
};
/**
* 提交表单且数据验证成功后回调事件
*/
function finish() {
const data = handleFormModel();
emit('finish', data);
return new Promise((resolve) => {
resolve(data);
});
}
/**
* 主动执行提交
* @param val
*/
function triggerSubmit(val?: string[]) {
return submit().then(() => {
return nextTick().then(() => {
return new Promise((resolve, reject) => {
(formElRef as Recordable).value
.validate(val)
.then((_) => {
resolve(finish());
})
.catch((e) => {
reject(e);
});
});
});
});
}
/**
* 重置表单
*/
const reset = () => {
resetFormModel();
};
provide('setFormModel', setFormModel);
provide('unsetFormModel', unsetFormModel);
provide('getFormModel', getFormModel);
provide('submit', triggerSubmit);
return {
expandRef,
formElRef,
getBindValue,
getSchema,
formModel,
setFormModel,
getFormClass,
validateResult,
reset,
triggerSubmit,
getComponentSpan,
splitNumber,
finish,
};
},
});
</script>
<style lang="less" scoped>
.ns-form {
.ant-row {
flex: 1;
}
.ns-operate {
margin-bottom: 16px;
text-align: right;
margin-left: auto;
.ns-operate-expand {
display: inline-block;
padding: 4px 2px;
border: unset !important;
.anticon {
margin-left: 4px;
}
&:hover {
border: unset !important;
}
&:focus {
border: unset !important;
}
}
.ant-btn {
margin-left: 6px;
}
}
}
</style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save