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