diff --git a/.env.development b/.env.development index 76dead8..e57c979 100644 --- a/.env.development +++ b/.env.development @@ -8,3 +8,5 @@ VITE_BASE_URL = / VITE_BASE_API_URL = '/api' VITE_DROP_CONSOLE = false + +VITE_LINT_CODE = false diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index bd1d447..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "typescript.tsdk": "./node_modules/typescript/lib", - "volar.tsPlugin": true, - "volar.tsPluginStatus": false, - "npm.packageManager": "pnpm", - "editor.tabSize": 2, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "files.eol": "\n", - "search.exclude": { - "**/node_modules": true, - "**/*.log": true, - "**/*.log*": true, - "**/bower_components": true, - "**/dist": true, - "**/elehukouben": true, - "**/.git": true, - "**/.gitignore": true, - "**/.svn": true, - "**/.DS_Store": true, - "**/.idea": true, - "**/.vscode": false, - "**/yarn.lock": true, - "**/tmp": true, - "out": true, - "dist": true, - "node_modules": true, - "CHANGELOG.md": true, - "examples": true, - "res": true, - "screenshots": true, - "yarn-error.log": true, - "**/.yarn": true - }, - "files.exclude": { - "**/.cache": true, - "**/.editorconfig": true, - "**/.eslintcache": true, - "**/bower_components": true, - "**/.idea": true, - "**/tmp": true, - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true - }, - "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/.vscode/**": true, - "**/node_modules/**": true, - "**/tmp/**": true, - "**/bower_components/**": true, - "**/dist/**": true, - "**/yarn.lock": true - }, - "stylelint.enable": true, - "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], - "path-intellisense.mappings": { - "@/": "${workspaceRoot}/src" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[less]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[scss]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[markdown]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit" - }, - "[vue]": { - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "i18n-ally.localesPaths": ["src/locales/lang"], - "i18n-ally.keystyle": "nested", - "i18n-ally.sortKeys": true, - "i18n-ally.namespace": true, - "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", - "i18n-ally.enabledParsers": ["json"], - "i18n-ally.sourceLanguage": "en", - "i18n-ally.displayLanguage": "zh-CN", - "i18n-ally.enabledFrameworks": ["vue", "react"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bf0290..2477bc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11154,8 +11154,8 @@ packages: next-tick: 1.1.0 dev: true - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + /tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} dev: true /tiny-pinyin@1.3.2: @@ -11726,10 +11726,10 @@ packages: fast-glob: 3.3.2 fs-extra: 11.2.0 npm-run-path: 4.0.1 - semver: 7.5.4 + semver: 7.6.0 strip-ansi: 6.0.1 stylelint: 16.2.1(typescript@5.3.3) - tiny-invariant: 1.3.1 + tiny-invariant: 1.3.3 typescript: 5.3.3 vite: 5.1.4(@types/node@20.11.16)(less@4.2.0)(lightningcss@1.24.0)(terser@5.28.1) vscode-languageclient: 7.0.0 @@ -11821,7 +11821,7 @@ packages: engines: {vscode: ^1.52.0} dependencies: minimatch: 3.1.2 - semver: 7.5.4 + semver: 7.6.0 vscode-languageserver-protocol: 3.16.0 dev: true @@ -11986,7 +11986,7 @@ packages: dependencies: '@volar/typescript': 1.11.1 '@vue/language-core': 1.8.27(typescript@5.3.3) - semver: 7.5.4 + semver: 7.6.0 typescript: 5.3.3 dev: true diff --git a/src/api/backend/api/contract.ts b/src/api/backend/api/contract.ts new file mode 100644 index 0000000..d66d493 --- /dev/null +++ b/src/api/backend/api/contract.ts @@ -0,0 +1,85 @@ +import { request, type RequestOptions } from '@/utils/request'; + +/** 获取合同列表 GET /api/contract */ +export async function contractList(params: API.ContractListParams, options?: RequestOptions) { + return request<{ + items?: API.ContractEntity[]; + meta?: { + itemCount?: number; + totalItems?: number; + itemsPerPage?: number; + totalPages?: number; + currentPage?: number; + }; + }>('/api/contract', { + method: 'GET', + params: { + // page has a default value: 1 + page: '1', + // pageSize has a default value: 10 + pageSize: '10', + + ...params, + }, + ...(options || {}), + }); +} + +/** 新增合同 POST /api/contract */ +export async function contractCreate(body: API.ContractDto, options?: RequestOptions) { + return request('/api/contract', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || { successMsg: '创建成功' }), + }); +} + +/** 获取合同信息 GET /api/contract/${param0} */ +export async function contractInfo( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.ContractInfoParams, + options?: RequestOptions, +) { + const { id: param0, ...queryParams } = params; + return request(`/api/contract/${param0}`, { + method: 'GET', + params: { ...queryParams }, + ...(options || {}), + }); +} + +/** 更新合同 PUT /api/contract/${param0} */ +export async function contractUpdate( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.ContractUpdateParams, + body: API.ContractUpdateDto, + options?: RequestOptions, +) { + const { id: param0, ...queryParams } = params; + return request(`/api/contract/${param0}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + params: { ...queryParams }, + data: body, + ...(options || { successMsg: '更新成功' }), + }); +} + +/** 删除合同 DELETE /api/contract/${param0} */ +export async function contractDelete( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.ContractDeleteParams, + options?: RequestOptions, +) { + const { id: param0, ...queryParams } = params; + return request(`/api/contract/${param0}`, { + method: 'DELETE', + params: { ...queryParams }, + ...(options || { successMsg: '删除成功' }), + }); +} diff --git a/src/api/backend/api/index.ts b/src/api/backend/api/index.ts index 4f4b7e1..6bb58f3 100644 --- a/src/api/backend/api/index.ts +++ b/src/api/backend/api/index.ts @@ -25,6 +25,7 @@ import * as systemSse from './systemSse'; import * as netDiskManage from './netDiskManage'; import * as netDiskOverview from './netDiskOverview'; import * as businessTodo from './businessTodo'; +import * as contract from './contract'; export default { auth, account, @@ -49,4 +50,5 @@ export default { netDiskManage, netDiskOverview, businessTodo, + contract, }; diff --git a/src/api/backend/api/systemDictItem.ts b/src/api/backend/api/systemDictItem.ts index 76ad096..8c22167 100644 --- a/src/api/backend/api/systemDictItem.ts +++ b/src/api/backend/api/systemDictItem.ts @@ -31,6 +31,14 @@ export async function dictItemList( }); } +/** 一次性获取所有的字典项(不分页) GET /api/system/dict-type/select-options */ +export async function dictItemGetAllByTypeId(typeId: number, options?: RequestOptions) { + return request(`/api/system/dict-item/all/${typeId}`, { + method: 'GET', + ...(options || {}), + }); +} + /** 新增字典项 POST /api/system/dict-item */ export async function dictItemCreate(body: API.DictItemDto, options?: RequestOptions) { return request('/api/system/dict-item', { diff --git a/src/api/backend/api/typings.d.ts b/src/api/backend/api/typings.d.ts index aaf506d..6e71ea2 100644 --- a/src/api/backend/api/typings.d.ts +++ b/src/api/backend/api/typings.d.ts @@ -1268,4 +1268,82 @@ declare namespace API { type UserUpdateParams = { id: number; }; + + type ContractListParams = { + page?: number; + pageSize?: number; + field?: string; + order?: 'ASC' | 'DESC'; + _t?: number; + }; + type ContractEntity = { + /** 合同编号 */ + contractNumber: string; + /** 合同标题 */ + title: string; + /** 合同类型(字典) */ + type: number; + /** 甲方 */ + partyA: string; + /** 乙方 */ + partyB: string; + /** 签订日期 */ + signingDate: Date; + /** 交付期限 */ + deliveryDeadline: Date; + /** 审核状态(字典) */ + status: number; + id: number; + createdAt: string; + updatedAt: string; + }; + type ContractDto = { + /** 合同编号 */ + contractNumber: string; + /** 合同标题 */ + title: string; + /** 合同类型(字典) */ + type: number; + /** 甲方 */ + partyA: string; + /** 乙方 */ + partyB: string; + /** 签订日期 */ + signingDate: string; + /** 交付期限 */ + deliveryDeadline: string; + /** 审核状态(字典) */ + status: number; + }; + + type ContractUpdateParams = { + id: number; + }; + + type ContractUpdateDto = { + /** 合同编号 */ + contractNumber: string; + /** 合同标题 */ + title: string; + /** 合同类型(字典) */ + type: number; + /** 甲方 */ + partyA: string; + /** 乙方 */ + partyB: string; + /** 签订日期 */ + signingDate: string; + /** 交付期限 */ + deliveryDeadline: string; + /** 审核状态(字典) */ + status: number; + }; + + type ContractDeleteParams = { + id: number; + }; + + type ContractInfoParams = { + id: number; + }; } diff --git a/src/enums/contractEnum.ts b/src/enums/contractEnum.ts new file mode 100644 index 0000000..94b1ad3 --- /dev/null +++ b/src/enums/contractEnum.ts @@ -0,0 +1,6 @@ +export enum ContractStatusEnum { + Pending = 0, // 待审核 + Approved = 1, // 已通过 + Rejected = 2, // 已拒绝 +} +// 使用es6数组方法遍历枚举 diff --git a/src/enums/dictEnum.ts b/src/enums/dictEnum.ts new file mode 100644 index 0000000..4f16588 --- /dev/null +++ b/src/enums/dictEnum.ts @@ -0,0 +1,3 @@ +export enum DictEnum { + ContractType = 'contract_type', +} diff --git a/src/permission/permCode.ts b/src/permission/permCode.ts index 807ee42..3fcc1c7 100644 --- a/src/permission/permCode.ts +++ b/src/permission/permCode.ts @@ -3,81 +3,79 @@ * @description 权限列表, 仅供开发时提供 ts 类型提示,无实际作用 */ const permissions = [ - 'system:role:list', - 'system:role:create', - 'system:role:read', - 'system:role:update', - 'system:role:delete', - 'system:menu:list', - 'system:menu:create', - 'system:menu:read', - 'system:menu:update', - 'system:menu:delete', - 'system:param-config:list', - 'system:param-config:create', - 'system:param-config:read', - 'system:param-config:update', - 'system:param-config:delete', - 'system:user:list', - 'system:user:create', - 'system:user:read', - 'system:user:update', - 'system:user:delete', - 'system:user:password:update', - 'system:user:pass:reset', - 'system:log:task:list', - 'system:log:login:list', - 'system:log:captcha:list', - 'app:health:network', - 'app:health:database', - 'app:health:memory-heap', - 'app:health:memory-rss', - 'app:health:disk', - 'netdisk:manage:list', - 'netdisk:manage:create', - 'netdisk:manage:info', - 'netdisk:manage:update', - 'netdisk:manage:delete', - 'netdisk:manage:mkdir', - 'netdisk:manage:token', - 'netdisk:manage:mark', - 'netdisk:manage:download', - 'netdisk:manage:rename', - 'netdisk:manage:cut', - 'netdisk:manage:copy', - 'system:dept:list', - 'system:dept:create', - 'system:dept:read', - 'system:dept:update', - 'system:dept:delete', - 'system:dict-item:list', - 'system:dict-item:create', - 'system:dict-item:read', - 'system:dict-item:update', - 'system:dict-item:delete', - 'system:dict-type:list', - 'system:dict-type:create', - 'system:dict-type:read', - 'system:dict-type:update', - 'system:dict-type:delete', - 'system:online:list', - 'system:online:kick', - 'system:task:list', - 'system:task:create', - 'system:task:read', - 'system:task:update', - 'system:task:delete', - 'system:task:once', - 'system:task:start', - 'system:task:stop', - 'todo:list', - 'todo:create', - 'todo:read', - 'todo:update', - 'todo:delete', - 'tool:storage:list', - 'tool:storage:delete', - 'upload:upload', + "system:user:list", + "system:role:list", + "system:menu:list", + "system:online:list", + "system:log:login:list", + "system:serve:stat", + "system:task:list", + "system:user:create", + "system:user:delete", + "system:user:update", + "system:user:read", + "system:role:create", + "system:role:delete", + "system:role:update", + "system:role:read", + "system:menu:create", + "system:menu:delete", + "system:menu:update", + "system:menu:read", + "system:online:kick", + "system:task:create", + "system:task:delete", + "system:task:once", + "system:task:read", + "system:task:start", + "system:task:stop", + "system:task:update", + "system:log:task:list", + "system:tools:email", + "tools:email:send", + "tool:storage:list", + "upload:upload", + "tool:storage:delete", + "system:user:password", + "system:dict-type:list", + "system:dict-type:create", + "system:dict-type:update", + "system:dict-type:delete", + "system:dict-type:info", + "system:dept:list", + "system:dept:create", + "system:dept:update", + "system:dept:delete", + "system:dept:read", + "app:health:network", + "app:health: database", + "system:param-config:list", + "system:param-config:read", + "system:param-config:create", + "system:param-config:update", + "system:param-config:delete", + "system:dict-item:list", + "system:dict-item:create", + "system:dict-item:update", + "system:dict-item:delete", + "system:dict-item:info", + "netdisk:manage:list", + "netdisk:manage:create", + "netdisk:manage:read", + "netdisk:manage:update", + "netdisk:manage:delete", + "netdisk:manage:token", + "netdisk:manage:mark", + "netdisk:manage:download", + "netdisk:manage:rename", + "netdisk:manage:copy", + "netdisk:manage:cut", + "netdisk:overview:desc", + "app:contract:list", + "app:contract:update", + "app:contract:delete", + "app:contract:read", + "app:contract:create" ] as const; export type PermissionType = (typeof permissions)[number]; diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..e31a1f7 --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,34 @@ +import { ref } from 'vue'; +import { defineStore } from 'pinia'; +import Api from '@/api'; +import { store } from '@/store'; +// interface DictState { +// /** 需要缓存的路由组件名称列表 */ +// list: API.DictItemDto[]; +// } + +export const useDictStore = defineStore('dict', () => { + const dictTypes = ref([]); + const getDictTypes = async () => { + dictTypes.value = await Api.systemDictType.dictTypeGetAll(); + }; + + const getDictItemsByCode = async (code: string): Promise => { + try { + const dictType = dictTypes.value.find((item) => item.code === code); + if (dictType) { + return await Api.systemDictItem.dictItemGetAllByTypeId(dictType.id!); + } + return Promise.resolve([]); + } catch (error) { + return Promise.reject(error); + } + }; + + return { dictTypes, getDictTypes, getDictItemsByCode }; +}); + +// 在组件setup函数外使用 +export function useDictStoreWithOut() { + return useDictStore(store); +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index e72a567..b3011d8 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,5 +1,6 @@ import { ref, watch } from 'vue'; import { defineStore } from 'pinia'; +import { useDictStore } from './dict'; import type { RouteRecordRaw } from 'vue-router'; import { store } from '@/store'; import Api from '@/api/'; @@ -97,6 +98,9 @@ export const useUserStore = defineStore('user', () => { const afterLogin = async () => { try { const { accountProfile } = Api.account; + const dictStore = useDictStore(); + // 获取所有字典类型 + await dictStore.getDictTypes(); // const wsStore = useWsStore(); const userInfoData = await accountProfile(); diff --git a/src/views/contract/columns.tsx b/src/views/contract/columns.tsx new file mode 100644 index 0000000..b20d011 --- /dev/null +++ b/src/views/contract/columns.tsx @@ -0,0 +1,118 @@ +import type { TableColumn } from '@/components/core/dynamic-table'; +import { ContractStatusEnum } from '@/enums/contractEnum'; +import { formatToDate } from '@/utils/dateUtil'; +import { Tag } from 'ant-design-vue'; + +export type TableListItem = API.ContractEntity; +export type TableColumnItem = TableColumn; + +export const baseColumns = (ctx: { contractTypes: API.DictItemEntity[] }): TableColumnItem[] => { + const { contractTypes } = ctx; + return [ + { + title: '合同编号', + width: 120, + dataIndex: 'contractNumber', + }, + { + title: '合同标题', + width: 200, + dataIndex: 'title', + }, + { + title: '合同类型', + width: 80, + formItemProps: { + component: 'Select', + componentProps: { + options: contractTypes.map(({ label, id }) => ({ value: id, label })), + }, + }, + dataIndex: 'type', + customRender: ({ record }) => { + return contractTypes?.length + ? contractTypes.find((item) => item.id === record.type)?.label || '' + : ''; + }, + }, + { + title: '甲方', + width: 150, + dataIndex: 'partyA', + }, + { + title: '乙方', + width: 150, + dataIndex: 'partyB', + }, + { + title: '签订时间', + width: 100, + hideInSearch: true, + dataIndex: 'signingDate', + customRender: ({ record }) => { + return formatToDate(record.signingDate); + }, + }, + { + title: '交付期限', + width: 100, + hideInSearch: true, + dataIndex: 'deliveryDeadline', + customRender: ({ record }) => { + return formatToDate(record.deliveryDeadline); + }, + }, + { + title: '审核结果', + dataIndex: 'status', + maxWidth: 60, + width: 60, + formItemProps: { + component: 'Select', + componentProps: { + options: Object.values(ContractStatusEnum) + .filter((value) => typeof value === 'number') + .map((item) => formatStatus(item as ContractStatusEnum)), + }, + }, + customRender: ({ record }) => { + const { color, label } = formatStatus(record.status); + return {label}; + }, + }, + ]; +}; + +export function formatStatus(status: ContractStatusEnum): { + color: string; + label: string; + value: number; +} { + switch (status) { + case ContractStatusEnum.Pending: + return { + color: '#ccc', + label: '待审核', + value: ContractStatusEnum.Pending, + }; + case ContractStatusEnum.Approved: + return { + color: 'green', + label: '已通过', + value: ContractStatusEnum.Approved, + }; + case ContractStatusEnum.Rejected: + return { + color: 'red', + label: '已拒绝', + value: ContractStatusEnum.Rejected, + }; + default: + return { + color: '#ccc', + label: '待审核', + value: ContractStatusEnum.Pending, + }; + } +} diff --git a/src/views/contract/formSchemas.ts b/src/views/contract/formSchemas.ts new file mode 100644 index 0000000..2b30f17 --- /dev/null +++ b/src/views/contract/formSchemas.ts @@ -0,0 +1,115 @@ +import type { FormSchema } from '@/components/core/schema-form/'; +import { ContractStatusEnum } from '@/enums/contractEnum'; +import { formatStatus } from './columns'; + +export const contractSchemas = ( + contractTypes: API.DictItemEntity[], +): FormSchema[] => [ + { + field: 'contractNumber', + component: 'Input', + label: '合同编号', + rules: [{ required: true, type: 'string' }], + colProps: { + span: 12, + }, + }, + { + field: 'title', + component: 'Input', + label: '合同标题', + rules: [{ required: true, type: 'string' }], + colProps: { + span: 12, + }, + }, + + { + field: 'partyA', + component: 'Input', + label: '甲方', + rules: [{ required: true, type: 'string' }], + colProps: { + span: 12, + }, + }, + { + field: 'partyB', + component: 'Input', + label: '乙方', + rules: [{ required: true, type: 'string' }], + colProps: { + span: 12, + }, + }, + + { + field: 'signingDate', + label: '签订时间', + component: 'DatePicker', + // defaultValue: new Date(), + colProps: { span: 12 }, + componentProps: {}, + }, + { + field: 'deliveryDeadline', + label: '交付期限', + component: 'DatePicker', + // defaultValue: new Date(), + colProps: { span: 12 }, + }, + { + field: 'type', + label: '合同类型', + component: 'Select', + required: true, + colProps: { + span: 12, + }, + componentProps: { + options: contractTypes.map(({ label, id }) => ({ value: id, label })), + }, + }, + { + field: 'status', + label: '审核结果', + component: 'Select', + required:true, + defaultValue: 0, + colProps: { + span: 12, + }, + componentProps: { + allowClear: false, + options: Object.values(ContractStatusEnum) + .filter((value) => typeof value === 'number') + .map((item) => formatStatus(item as ContractStatusEnum)), + }, + }, + // { + // field: 'remark', + // component: 'InputTextArea', + // label: '备注', + // }, + // { + // field: 'menuIds', + // component: 'Tree', + // label: '菜单权限', + // componentProps: { + // checkable: true, + // vModelKey: 'checkedKeys', + // fieldNames: { + // title: 'name', + // key: 'id', + // }, + // style: { + // height: '350px', + // paddingTop: '5px', + // overflow: 'auto', + // borderRadius: '6px', + // border: '1px solid #dcdfe6', + // resize: 'vertical', + // }, + // }, + // }, +]; diff --git a/src/views/contract/index.vue b/src/views/contract/index.vue index 6cd4e3e..906301a 100644 --- a/src/views/contract/index.vue +++ b/src/views/contract/index.vue @@ -1,11 +1,128 @@ diff --git a/vite.config.ts b/vite.config.ts index d2deb00..d22018d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -27,8 +27,11 @@ const __APP_INFO__ = { // https://vitejs.dev/config/ export default ({ command, mode }: ConfigEnv): UserConfig => { // 环境变量 - const { VITE_BASE_URL, VITE_DROP_CONSOLE /* VITE_MOCK_IN_PROD */ } = loadEnv(mode, CWD); - + const { VITE_BASE_URL, VITE_DROP_CONSOLE /* VITE_MOCK_IN_PROD */, VITE_LINT_CODE } = loadEnv( + mode, + CWD, + ); + const isLintCode = VITE_LINT_CODE === 'true'; const isDev = command === 'serve'; // const isBuild = command === 'build'; @@ -81,6 +84,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { }), // https://github.com/fi3ework/vite-plugin-checker isDev && + isLintCode && checker({ typescript: true, vueTsc: true,