Browse Source
* upgrade electron version:11->18. upgrade electron version:11->18. Packaging failed because the original electronic version was too old. 将electron 11 改成18.因为11太老导致打包错误。 * main->electron-main合并代码 * main->electron-main合并代码 * main->electron-main合并代码electron-v2
committed by
GitHub
276 changed files with 9306 additions and 7202 deletions
@ -1,8 +0,0 @@ |
|||||
module.exports = { |
|
||||
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], |
|
||||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], |
|
||||
'package.json': ['prettier --write'], |
|
||||
'*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'], |
|
||||
'*.{scss,less,styl,html}': ['stylelint --fix', 'prettier --write'], |
|
||||
'*.md': ['prettier --write'], |
|
||||
}; |
|
||||
@ -1,48 +0,0 @@ |
|||||
# test directories |
|
||||
__tests__ |
|
||||
test |
|
||||
tests |
|
||||
powered-test |
|
||||
|
|
||||
# asset directories |
|
||||
docs |
|
||||
doc |
|
||||
website |
|
||||
images |
|
||||
assets |
|
||||
|
|
||||
# examples |
|
||||
example |
|
||||
examples |
|
||||
|
|
||||
# code coverage directories |
|
||||
coverage |
|
||||
.nyc_output |
|
||||
|
|
||||
# build scripts |
|
||||
Makefile |
|
||||
Gulpfile.js |
|
||||
Gruntfile.js |
|
||||
|
|
||||
# configs |
|
||||
appveyor.yml |
|
||||
circle.yml |
|
||||
codeship-services.yml |
|
||||
codeship-steps.yml |
|
||||
wercker.yml |
|
||||
.tern-project |
|
||||
.gitattributes |
|
||||
.editorconfig |
|
||||
.*ignore |
|
||||
.eslintrc |
|
||||
.jshintrc |
|
||||
.flowconfig |
|
||||
.documentup.json |
|
||||
.yarn-metadata.json |
|
||||
.travis.yml |
|
||||
|
|
||||
# misc |
|
||||
*.md |
|
||||
|
|
||||
!istanbul-reports/lib/html/assets |
|
||||
!istanbul-api/node_modules/istanbul-reports/lib/html/assets |
|
||||
@ -1,21 +0,0 @@ |
|||||
// TODO
|
|
||||
import type { GetManualChunk } from 'rollup'; |
|
||||
|
|
||||
//
|
|
||||
const vendorLibs: { match: string[]; output: string }[] = [ |
|
||||
// {
|
|
||||
// match: ['xlsx'],
|
|
||||
// output: 'xlsx',
|
|
||||
// },
|
|
||||
]; |
|
||||
|
|
||||
// @ts-ignore
|
|
||||
export const configManualChunk: GetManualChunk = (id: string) => { |
|
||||
if (/[\\/]node_modules[\\/]/.test(id)) { |
|
||||
const matchItem = vendorLibs.find((item) => { |
|
||||
const reg = new RegExp(`[\\/]node_modules[\\/]_?(${item.match.join('|')})(.*)`, 'ig'); |
|
||||
return reg.test(id); |
|
||||
}); |
|
||||
return matchItem ? matchItem.output : null; |
|
||||
} |
|
||||
}; |
|
||||
@ -1,25 +0,0 @@ |
|||||
import type { Plugin } from 'vite'; |
|
||||
|
|
||||
/** |
|
||||
* TODO |
|
||||
* Temporarily solve the Vite circular dependency problem, and wait for a better solution to fix it later. I don't know what problems this writing will bring. |
|
||||
* @returns |
|
||||
*/ |
|
||||
|
|
||||
export function configHmrPlugin(): Plugin { |
|
||||
return { |
|
||||
name: 'singleHMR', |
|
||||
handleHotUpdate({ modules, file }) { |
|
||||
if (file.match(/xml$/)) return []; |
|
||||
|
|
||||
modules.forEach((m) => { |
|
||||
if (!m.url.match(/\.(css|less)/)) { |
|
||||
m.importedModules = new Set(); |
|
||||
m.importers = new Set(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return modules; |
|
||||
}, |
|
||||
}; |
|
||||
} |
|
||||
@ -1,36 +0,0 @@ |
|||||
export default { |
|
||||
preset: 'ts-jest', |
|
||||
roots: ['<rootDir>/tests/'], |
|
||||
clearMocks: true, |
|
||||
moduleDirectories: ['node_modules', 'src'], |
|
||||
moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'], |
|
||||
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'], |
|
||||
testMatch: [ |
|
||||
'**/tests/**/*.[jt]s?(x)', |
|
||||
'**/?(*.)+(spec|test).[tj]s?(x)', |
|
||||
'(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$', |
|
||||
], |
|
||||
testPathIgnorePatterns: [ |
|
||||
'<rootDir>/tests/server/', |
|
||||
'<rootDir>/tests/__mocks__/', |
|
||||
'/node_modules/', |
|
||||
], |
|
||||
transform: { |
|
||||
'^.+\\.tsx?$': 'ts-jest', |
|
||||
}, |
|
||||
transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'], |
|
||||
// A map from regular expressions to module names that allow to stub out resources with a single module
|
|
||||
moduleNameMapper: { |
|
||||
'\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': |
|
||||
'<rootDir>/tests/__mocks__/fileMock.ts', |
|
||||
'\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts', |
|
||||
'\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts', |
|
||||
'^/@/(.*)$': '<rootDir>/src/$1', |
|
||||
}, |
|
||||
testEnvironment: 'jsdom', |
|
||||
verbose: true, |
|
||||
collectCoverage: false, |
|
||||
coverageDirectory: 'coverage', |
|
||||
collectCoverageFrom: ['src/**/*.{js,ts,vue}'], |
|
||||
coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'], |
|
||||
}; |
|
||||
@ -0,0 +1,325 @@ |
|||||
|
import { MockMethod } from 'vite-plugin-mock'; |
||||
|
import { resultSuccess } from '../_util'; |
||||
|
|
||||
|
const areaList: any[] = [ |
||||
|
{ |
||||
|
id: '530825900854620160', |
||||
|
code: '430000', |
||||
|
parentCode: '100000', |
||||
|
levelType: 1, |
||||
|
name: '湖南省', |
||||
|
province: '湖南省', |
||||
|
city: null, |
||||
|
district: null, |
||||
|
town: null, |
||||
|
village: null, |
||||
|
parentPath: '430000', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 16:33:42', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530825900883980288', |
||||
|
code: '430100', |
||||
|
parentCode: '430000', |
||||
|
levelType: 2, |
||||
|
name: '长沙市', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: null, |
||||
|
town: null, |
||||
|
village: null, |
||||
|
parentPath: '430000,430100', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 16:33:42', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530825900951089152', |
||||
|
code: '430102', |
||||
|
parentCode: '430100', |
||||
|
levelType: 3, |
||||
|
name: '芙蓉区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '芙蓉区', |
||||
|
town: null, |
||||
|
village: null, |
||||
|
parentPath: '430000,430100,430102', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 16:33:42', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530825901014003712', |
||||
|
code: '430104', |
||||
|
parentCode: '430100', |
||||
|
levelType: 3, |
||||
|
name: '岳麓区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '岳麓区', |
||||
|
town: null, |
||||
|
village: null, |
||||
|
parentPath: '430000,430100,430104', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 16:33:42', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530825900988837888', |
||||
|
code: '430103', |
||||
|
parentCode: '430100', |
||||
|
levelType: 3, |
||||
|
name: '天心区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: null, |
||||
|
village: null, |
||||
|
parentPath: '430000,430100,430103', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 16:33:42', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530826672489115648', |
||||
|
code: '430103002', |
||||
|
parentCode: '430103', |
||||
|
levelType: 4, |
||||
|
name: '坡子街街道', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: null, |
||||
|
parentPath: '430000,430100,430103,430103002', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-12-14 15:26:43', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241171607552', |
||||
|
code: '430103002001', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '八角亭社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '八角亭社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002001', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2021-01-20 14:07:23', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241200967680', |
||||
|
code: '430103002002', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '西牌楼社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '西牌楼社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002002', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241230327808', |
||||
|
code: '430103002003', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '太平街社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '太平街社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002003', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241259687936', |
||||
|
code: '430103002005', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '坡子街社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '坡子街社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002005', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241284853760', |
||||
|
code: '430103002006', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '青山祠社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '青山祠社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002006', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241310019584', |
||||
|
code: '430103002007', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '沙河社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '沙河社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002007', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241381322752', |
||||
|
code: '430103002008', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '碧湘社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '碧湘社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002008', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241410682880', |
||||
|
code: '430103002009', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '创远社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '创远社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002009', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241431654400', |
||||
|
code: '430103002010', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '楚湘社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '楚湘社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002010', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241465208832', |
||||
|
code: '430103002011', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '西湖社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '西湖社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002011', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241502957568', |
||||
|
code: '430103002012', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '登仁桥社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '登仁桥社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002012', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
{ |
||||
|
id: '530840241553289216', |
||||
|
code: '430103002013', |
||||
|
parentCode: '430103002', |
||||
|
levelType: 5, |
||||
|
name: '文庙坪社区', |
||||
|
province: '湖南省', |
||||
|
city: '长沙市', |
||||
|
district: '天心区', |
||||
|
town: '坡子街街道', |
||||
|
village: '文庙坪社区', |
||||
|
parentPath: '430000,430100,430103,430103002,430103002013', |
||||
|
createTime: '2020-11-30 15:47:31', |
||||
|
updateTime: '2020-11-30 17:30:41', |
||||
|
customized: false, |
||||
|
usable: true, |
||||
|
}, |
||||
|
]; |
||||
|
export default [ |
||||
|
{ |
||||
|
url: '/basic-api/cascader/getAreaRecord', |
||||
|
timeout: 1000, |
||||
|
method: 'post', |
||||
|
response: ({ body }) => { |
||||
|
const { parentCode } = body || {}; |
||||
|
if (!parentCode) { |
||||
|
return resultSuccess(areaList.filter((it) => it.code === '430000')); |
||||
|
} |
||||
|
return resultSuccess(areaList.filter((it) => it.parentCode === parentCode)); |
||||
|
}, |
||||
|
}, |
||||
|
] as MockMethod[]; |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { defHttp } from '/@/utils/http/axios'; |
||||
|
import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel'; |
||||
|
|
||||
|
enum Api { |
||||
|
AREA_RECORD = '/cascader/getAreaRecord', |
||||
|
} |
||||
|
|
||||
|
export const areaRecord = (data: AreaParams) => |
||||
|
defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data }); |
||||
@ -0,0 +1,12 @@ |
|||||
|
export interface AreaModel { |
||||
|
id: string; |
||||
|
code: string; |
||||
|
parentCode: string; |
||||
|
name: string; |
||||
|
levelType: number; |
||||
|
[key: string]: string | number; |
||||
|
} |
||||
|
|
||||
|
export interface AreaParams { |
||||
|
parentCode: string; |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
export enum MODE { |
||||
|
JSON = 'application/json', |
||||
|
HTML = 'htmlmixed', |
||||
|
JS = 'javascript', |
||||
|
} |
||||
@ -0,0 +1,198 @@ |
|||||
|
<template> |
||||
|
<a-cascader |
||||
|
v-model:value="state" |
||||
|
:options="options" |
||||
|
:load-data="loadData" |
||||
|
change-on-select |
||||
|
@change="handleChange" |
||||
|
:displayRender="handleRenderDisplay" |
||||
|
> |
||||
|
<template #suffixIcon v-if="loading"> |
||||
|
<LoadingOutlined spin /> |
||||
|
</template> |
||||
|
<template #notFoundContent v-if="loading"> |
||||
|
<span> |
||||
|
<LoadingOutlined spin class="mr-1" /> |
||||
|
{{ t('component.form.apiSelectNotFound') }} |
||||
|
</span> |
||||
|
</template> |
||||
|
</a-cascader> |
||||
|
</template> |
||||
|
<script lang="ts"> |
||||
|
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue'; |
||||
|
import { Cascader } from 'ant-design-vue'; |
||||
|
import { propTypes } from '/@/utils/propTypes'; |
||||
|
import { isFunction } from '/@/utils/is'; |
||||
|
import { get, omit } from 'lodash-es'; |
||||
|
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
||||
|
import { LoadingOutlined } from '@ant-design/icons-vue'; |
||||
|
import { useI18n } from '/@/hooks/web/useI18n'; |
||||
|
interface Option { |
||||
|
value: string; |
||||
|
label: string; |
||||
|
loading?: boolean; |
||||
|
isLeaf?: boolean; |
||||
|
children?: Option[]; |
||||
|
} |
||||
|
export default defineComponent({ |
||||
|
name: 'ApiCascader', |
||||
|
components: { |
||||
|
LoadingOutlined, |
||||
|
[Cascader.name]: Cascader, |
||||
|
}, |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Array, |
||||
|
}, |
||||
|
api: { |
||||
|
type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>, |
||||
|
default: null, |
||||
|
}, |
||||
|
numberToString: propTypes.bool, |
||||
|
resultField: propTypes.string.def(''), |
||||
|
labelField: propTypes.string.def('label'), |
||||
|
valueField: propTypes.string.def('value'), |
||||
|
childrenField: propTypes.string.def('children'), |
||||
|
asyncFetchParamKey: propTypes.string.def('parentCode'), |
||||
|
immediate: propTypes.bool.def(true), |
||||
|
// init fetch params |
||||
|
initFetchParams: { |
||||
|
type: Object as PropType<Recordable>, |
||||
|
default: () => ({}), |
||||
|
}, |
||||
|
// 是否有下级,默认是 |
||||
|
isLeaf: { |
||||
|
type: Function as PropType<(arg: Recordable) => boolean>, |
||||
|
default: null, |
||||
|
}, |
||||
|
displayRenderArray: { |
||||
|
type: Array, |
||||
|
}, |
||||
|
}, |
||||
|
emits: ['change', 'defaultChange'], |
||||
|
setup(props, { emit }) { |
||||
|
const apiData = ref<any[]>([]); |
||||
|
const options = ref<Option[]>([]); |
||||
|
const loading = ref<boolean>(false); |
||||
|
const emitData = ref<any[]>([]); |
||||
|
const isFirstLoad = ref(true); |
||||
|
const { t } = useI18n(); |
||||
|
// Embedded in the form, just use the hook binding to perform form verification |
||||
|
const [state] = useRuleFormItem(props, 'value', 'change', emitData); |
||||
|
|
||||
|
watch( |
||||
|
apiData, |
||||
|
(data) => { |
||||
|
const opts = generatorOptions(data); |
||||
|
options.value = opts; |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
); |
||||
|
|
||||
|
function generatorOptions(options: any[]): Option[] { |
||||
|
const { labelField, valueField, numberToString, childrenField, isLeaf } = props; |
||||
|
return options.reduce((prev, next: Recordable) => { |
||||
|
if (next) { |
||||
|
const value = next[valueField]; |
||||
|
const item = { |
||||
|
...omit(next, [labelField, valueField]), |
||||
|
label: next[labelField], |
||||
|
value: numberToString ? `${value}` : value, |
||||
|
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false, |
||||
|
}; |
||||
|
const children = Reflect.get(next, childrenField); |
||||
|
if (children) { |
||||
|
Reflect.set(item, childrenField, generatorOptions(children)); |
||||
|
} |
||||
|
prev.push(item); |
||||
|
} |
||||
|
return prev; |
||||
|
}, [] as Option[]); |
||||
|
} |
||||
|
|
||||
|
async function initialFetch() { |
||||
|
const api = props.api; |
||||
|
if (!api || !isFunction(api)) return; |
||||
|
apiData.value = []; |
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const res = await api(props.initFetchParams); |
||||
|
if (Array.isArray(res)) { |
||||
|
apiData.value = res; |
||||
|
return; |
||||
|
} |
||||
|
if (props.resultField) { |
||||
|
apiData.value = get(res, props.resultField) || []; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.warn(error); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function loadData(selectedOptions: Option[]) { |
||||
|
const targetOption = selectedOptions[selectedOptions.length - 1]; |
||||
|
targetOption.loading = true; |
||||
|
|
||||
|
const api = props.api; |
||||
|
if (!api || !isFunction(api)) return; |
||||
|
try { |
||||
|
const res = await api({ |
||||
|
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'), |
||||
|
}); |
||||
|
if (Array.isArray(res)) { |
||||
|
const children = generatorOptions(res); |
||||
|
targetOption.children = children; |
||||
|
return; |
||||
|
} |
||||
|
if (props.resultField) { |
||||
|
const children = generatorOptions(get(res, props.resultField) || []); |
||||
|
targetOption.children = children; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error(e); |
||||
|
} finally { |
||||
|
targetOption.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
props.immediate && initialFetch(); |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => props.initFetchParams, |
||||
|
() => { |
||||
|
!unref(isFirstLoad) && initialFetch(); |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
); |
||||
|
|
||||
|
function handleChange(keys, args) { |
||||
|
emitData.value = keys; |
||||
|
emit('defaultChange', keys, args); |
||||
|
} |
||||
|
|
||||
|
function handleRenderDisplay({ labels, selectedOptions }) { |
||||
|
if (unref(emitData).length === selectedOptions.length) { |
||||
|
return labels.join(' / '); |
||||
|
} |
||||
|
if (props.displayRenderArray) { |
||||
|
return props.displayRenderArray.join(' / '); |
||||
|
} |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
state, |
||||
|
options, |
||||
|
loading, |
||||
|
t, |
||||
|
handleChange, |
||||
|
loadData, |
||||
|
handleRenderDisplay, |
||||
|
}; |
||||
|
}, |
||||
|
}); |
||||
|
</script> |
||||
@ -0,0 +1,130 @@ |
|||||
|
<!-- |
||||
|
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component |
||||
|
--> |
||||
|
<template> |
||||
|
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange"> |
||||
|
<template v-for="item in getOptions" :key="`${item.value}`"> |
||||
|
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled"> |
||||
|
{{ item.label }} |
||||
|
</RadioButton> |
||||
|
<Radio v-else :value="item.value" :disabled="item.disabled"> |
||||
|
{{ item.label }} |
||||
|
</Radio> |
||||
|
</template> |
||||
|
</RadioGroup> |
||||
|
</template> |
||||
|
<script lang="ts"> |
||||
|
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue'; |
||||
|
import { Radio } from 'ant-design-vue'; |
||||
|
import { isFunction } from '/@/utils/is'; |
||||
|
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
||||
|
import { useAttrs } from '/@/hooks/core/useAttrs'; |
||||
|
import { propTypes } from '/@/utils/propTypes'; |
||||
|
import { get, omit } from 'lodash-es'; |
||||
|
import { useI18n } from '/@/hooks/web/useI18n'; |
||||
|
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean }; |
||||
|
|
||||
|
export default defineComponent({ |
||||
|
name: 'ApiRadioGroup', |
||||
|
components: { |
||||
|
RadioGroup: Radio.Group, |
||||
|
RadioButton: Radio.Button, |
||||
|
Radio, |
||||
|
}, |
||||
|
props: { |
||||
|
api: { |
||||
|
type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>, |
||||
|
default: null, |
||||
|
}, |
||||
|
params: { |
||||
|
type: [Object, String] as PropType<Recordable | string>, |
||||
|
default: () => ({}), |
||||
|
}, |
||||
|
value: { |
||||
|
type: [String, Number, Boolean] as PropType<string | number | boolean>, |
||||
|
}, |
||||
|
isBtn: { |
||||
|
type: [Boolean] as PropType<boolean>, |
||||
|
default: false, |
||||
|
}, |
||||
|
numberToString: propTypes.bool, |
||||
|
resultField: propTypes.string.def(''), |
||||
|
labelField: propTypes.string.def('label'), |
||||
|
valueField: propTypes.string.def('value'), |
||||
|
immediate: propTypes.bool.def(true), |
||||
|
}, |
||||
|
emits: ['options-change', 'change'], |
||||
|
setup(props, { emit }) { |
||||
|
const options = ref<OptionsItem[]>([]); |
||||
|
const loading = ref(false); |
||||
|
const isFirstLoad = ref(true); |
||||
|
const emitData = ref<any[]>([]); |
||||
|
const attrs = useAttrs(); |
||||
|
const { t } = useI18n(); |
||||
|
// Embedded in the form, just use the hook binding to perform form verification |
||||
|
const [state] = useRuleFormItem(props); |
||||
|
|
||||
|
// Processing options value |
||||
|
const getOptions = computed(() => { |
||||
|
const { labelField, valueField, numberToString } = props; |
||||
|
|
||||
|
return unref(options).reduce((prev, next: Recordable) => { |
||||
|
if (next) { |
||||
|
const value = next[valueField]; |
||||
|
prev.push({ |
||||
|
label: next[labelField], |
||||
|
value: numberToString ? `${value}` : value, |
||||
|
...omit(next, [labelField, valueField]), |
||||
|
}); |
||||
|
} |
||||
|
return prev; |
||||
|
}, [] as OptionsItem[]); |
||||
|
}); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
props.immediate && fetch(); |
||||
|
}); |
||||
|
|
||||
|
watch( |
||||
|
() => props.params, |
||||
|
() => { |
||||
|
!unref(isFirstLoad) && fetch(); |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
); |
||||
|
|
||||
|
async function fetch() { |
||||
|
const api = props.api; |
||||
|
if (!api || !isFunction(api)) return; |
||||
|
options.value = []; |
||||
|
try { |
||||
|
loading.value = true; |
||||
|
const res = await api(props.params); |
||||
|
if (Array.isArray(res)) { |
||||
|
options.value = res; |
||||
|
emitChange(); |
||||
|
return; |
||||
|
} |
||||
|
if (props.resultField) { |
||||
|
options.value = get(res, props.resultField) || []; |
||||
|
} |
||||
|
emitChange(); |
||||
|
} catch (error) { |
||||
|
console.warn(error); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function emitChange() { |
||||
|
emit('options-change', unref(getOptions)); |
||||
|
} |
||||
|
|
||||
|
function handleChange(_, ...args) { |
||||
|
emitData.value = args; |
||||
|
} |
||||
|
|
||||
|
return { state, getOptions, attrs, loading, t, handleChange, props }; |
||||
|
}, |
||||
|
}); |
||||
|
</script> |
||||
@ -0,0 +1,90 @@ |
|||||
|
<template> |
||||
|
<a-tree v-bind="getAttrs" @change="handleChange"> |
||||
|
<template #[item]="data" v-for="item in Object.keys($slots)"> |
||||
|
<slot :name="item" v-bind="data || {}"></slot> |
||||
|
</template> |
||||
|
<template #suffixIcon v-if="loading"> |
||||
|
<LoadingOutlined spin /> |
||||
|
</template> |
||||
|
</a-tree> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'; |
||||
|
import { Tree } from 'ant-design-vue'; |
||||
|
import { isArray, isFunction } from '/@/utils/is'; |
||||
|
import { get } from 'lodash-es'; |
||||
|
import { propTypes } from '/@/utils/propTypes'; |
||||
|
import { LoadingOutlined } from '@ant-design/icons-vue'; |
||||
|
export default defineComponent({ |
||||
|
name: 'ApiTree', |
||||
|
components: { ATree: Tree, LoadingOutlined }, |
||||
|
props: { |
||||
|
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> }, |
||||
|
params: { type: Object }, |
||||
|
immediate: { type: Boolean, default: true }, |
||||
|
resultField: propTypes.string.def(''), |
||||
|
afterFetch: { type: Function as PropType<Fn> }, |
||||
|
}, |
||||
|
emits: ['options-change', 'change'], |
||||
|
setup(props, { attrs, emit }) { |
||||
|
const treeData = ref<Recordable[]>([]); |
||||
|
const isFirstLoaded = ref<Boolean>(false); |
||||
|
const loading = ref(false); |
||||
|
const getAttrs = computed(() => { |
||||
|
return { |
||||
|
...(props.api ? { treeData: unref(treeData) } : {}), |
||||
|
...attrs, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
function handleChange(...args) { |
||||
|
emit('change', ...args); |
||||
|
} |
||||
|
|
||||
|
watch( |
||||
|
() => props.params, |
||||
|
() => { |
||||
|
!unref(isFirstLoaded) && fetch(); |
||||
|
}, |
||||
|
{ deep: true }, |
||||
|
); |
||||
|
|
||||
|
watch( |
||||
|
() => props.immediate, |
||||
|
(v) => { |
||||
|
v && !isFirstLoaded.value && fetch(); |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
props.immediate && fetch(); |
||||
|
}); |
||||
|
|
||||
|
async function fetch() { |
||||
|
const { api, afterFetch } = props; |
||||
|
if (!api || !isFunction(api)) return; |
||||
|
loading.value = true; |
||||
|
treeData.value = []; |
||||
|
let result; |
||||
|
try { |
||||
|
result = await api(props.params); |
||||
|
} catch (e) { |
||||
|
console.error(e); |
||||
|
} |
||||
|
if (afterFetch && isFunction(afterFetch)) { |
||||
|
result = afterFetch(result); |
||||
|
} |
||||
|
loading.value = false; |
||||
|
if (!result) return; |
||||
|
if (!isArray(result)) { |
||||
|
result = get(result, props.resultField); |
||||
|
} |
||||
|
treeData.value = (result as Recordable[]) || []; |
||||
|
isFirstLoaded.value = true; |
||||
|
emit('options-change', treeData.value); |
||||
|
} |
||||
|
return { getAttrs, loading, handleChange }; |
||||
|
}, |
||||
|
}); |
||||
|
</script> |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue