xuziqiang
6 months ago
commit
d0155dbe3c
7296 changed files with 1832517 additions and 0 deletions
@ -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 |
@ -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 |
@ -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= |
@ -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 |
@ -0,0 +1,15 @@ |
|||
|
|||
*.sh |
|||
node_modules |
|||
*.md |
|||
*.woff |
|||
*.ttf |
|||
.vscode |
|||
.idea |
|||
dist |
|||
/public |
|||
/docs |
|||
.husky |
|||
.local |
|||
/bin |
|||
Dockerfile |
@ -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', |
|||
}, |
|||
], |
|||
}, |
|||
}); |
@ -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 |
@ -0,0 +1,5 @@ |
|||
/dist/* |
|||
/node_modules/** |
|||
**/*.svg |
|||
**/*.sh |
|||
/public/* |
@ -0,0 +1,3 @@ |
|||
/dist/* |
|||
/public/* |
|||
public/* |
@ -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. |
@ -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 |
@ -0,0 +1,2 @@ |
|||
const build = async () => {}; |
|||
build(); |
@ -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), |
|||
}; |
|||
} |
@ -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; |
|||
} |
@ -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, |
|||
)}`,
|
|||
); |
|||
} |
@ -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'; |
@ -0,0 +1,9 @@ |
|||
import { viteMockServe } from './index'; |
|||
|
|||
export function configMockPlugin({ enable, mockPath }: { enable: boolean; mockPath: string }) { |
|||
return viteMockServe({ |
|||
// ignore: /.mjs$/,
|
|||
mockPath: mockPath, |
|||
enable, |
|||
}); |
|||
} |
@ -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'; |
|||
} |
@ -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); |
|||
}); |
|||
} |
@ -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) {} |
|||
} |
|||
}, |
|||
}, |
|||
]; |
|||
} |
@ -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__'; |
@ -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) {} |
|||
}); |
|||
}, |
|||
}; |
|||
}; |
@ -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; |
|||
} |
@ -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(), |
|||
}; |
|||
} |
|||
}, |
|||
}; |
|||
} |
@ -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})`; |
|||
} |
@ -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; |
|||
} |
@ -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, |
|||
}); |
|||
} |
@ -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('-')); |
|||
} |
@ -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; |
|||
}); |
|||
} |
@ -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; |
|||
}); |
|||
} |
@ -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; |
|||
}); |
|||
} |
@ -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; |
@ -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> |
@ -0,0 +1,4 @@ |
|||
import VNode from './v-node'; |
|||
import { withInstall } from '/nerv-lib/util'; |
|||
|
|||
export const NsVNode = withInstall(VNode); |
@ -0,0 +1,13 @@ |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'NsVNode', |
|||
props: { |
|||
content: { |
|||
type: [Object, String], |
|||
}, |
|||
}, |
|||
render() { |
|||
return this.content; |
|||
}, |
|||
}); |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -0,0 +1,4 @@ |
|||
import ColorPicker from './color-picker.vue'; |
|||
import { withInstall } from '/nerv-lib/util'; |
|||
|
|||
export const NsColorPicker = withInstall(ColorPicker); |
@ -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,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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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 |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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; |
|||
} |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -0,0 +1,4 @@ |
|||
import button from './button.vue'; |
|||
import { withInstall } from '/nerv-lib/util'; |
|||
|
|||
export const NsButton = withInstall(button); |
@ -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> |
@ -0,0 +1,5 @@ |
|||
import cascader from './cascader.vue'; |
|||
|
|||
import { withInstall } from '/nerv-lib/util'; |
|||
|
|||
export const NsCascader = withInstall(cascader); |
@ -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> |
@ -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> |
@ -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> |
@ -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; |
|||
}; |
File diff suppressed because it is too large
@ -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> |
@ -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; |
|||
}; |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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; |
|||
}; |
@ -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> |
@ -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> |
@ -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; |
|||
}; |
@ -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%"> |
|||
注:服务频次对应每天、每周、每月、每年,均需要设置起止时间 |
|||
<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> |
@ -0,0 +1,4 @@ |
|||
import etable from './etable.vue'; |
|||
import { withInstall } from '/nerv-lib/util'; |
|||
|
|||
export const NsEtable = withInstall(etable); |
@ -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> |
@ -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 }} </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.value为true |
|||
// 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> |
@ -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 }; |
@ -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> |
@ -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; |
|||
}; |
@ -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); |
|||
} |
|||
} |
@ -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> |
@ -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> |
@ -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; |
|||
} |
|||
} |
@ -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…
Reference in new issue