feat: contract 模块

This commit is contained in:
louis 2024-02-29 16:51:37 +08:00
parent a3a0246c35
commit 434b634375
16 changed files with 658 additions and 190 deletions

View File

@ -8,3 +8,5 @@ VITE_BASE_URL = /
VITE_BASE_API_URL = '/api'
VITE_DROP_CONSOLE = false
VITE_LINT_CODE = false

106
.vscode/settings.json vendored
View File

@ -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"]
}

View File

@ -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

View File

@ -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<any>('/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.ContractEntity>(`/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<any>(`/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<any>(`/api/contract/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || { successMsg: '删除成功' }),
});
}

View File

@ -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,
};

View File

@ -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.DictItemEntity[]>(`/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<any>('/api/system/dict-item', {

View File

@ -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;
};
}

View File

@ -0,0 +1,6 @@
export enum ContractStatusEnum {
Pending = 0, // 待审核
Approved = 1, // 已通过
Rejected = 2, // 已拒绝
}
// 使用es6数组方法遍历枚举

3
src/enums/dictEnum.ts Normal file
View File

@ -0,0 +1,3 @@
export enum DictEnum {
ContractType = 'contract_type',
}

View File

@ -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];

34
src/store/modules/dict.ts Normal file
View File

@ -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<API.DictTypeDto[]>([]);
const getDictTypes = async () => {
dictTypes.value = await Api.systemDictType.dictTypeGetAll();
};
const getDictItemsByCode = async (code: string): Promise<API.DictItemEntity[]> => {
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);
}

View File

@ -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();

View File

@ -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<TableListItem>;
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 <Tag color={color}>{label}</Tag>;
},
},
];
};
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,
};
}
}

View File

@ -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<API.ContractEntity>[] => [
{
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',
// },
// },
// },
];

View File

@ -1,11 +1,128 @@
<template>
<div>test</div>
<div v-if="columns?.length">
<DynamicTable
row-key="id"
header-title="合同管理"
title-tooltip=""
:data-request="Api.contract.contractList"
:columns="columns"
bordered
size="small"
>
<template #toolbar>
<a-button
type="primary"
:disabled="!$auth('system:role:create')"
@click="openEditModal({})"
>
新增
</a-button>
</template>
</DynamicTable>
</div>
</template>
<script setup lang="ts">
import { useTable } from '@/components/core/dynamic-table';
import { baseColumns, type TableColumnItem, type TableListItem } from './columns';
import Api from '@/api/';
import { useDictStore } from '@/store/modules/dict';
import { onMounted, ref } from 'vue';
import { DictEnum } from '@/enums/dictEnum';
import { useFormModal } from '@/hooks/useModal';
import { contractSchemas } from './formSchemas';
import { formatToDate } from '@/utils/dateUtil';
defineOptions({
name: 'Contract',
});
const [DynamicTable, dynamicTableInstance] = useTable();
const [showModal] = useFormModal();
const { getDictItemsByCode } = useDictStore();
const contractTypes = ref<API.DictItemEntity[]>([]);
const getContractTypes = async () => {
contractTypes.value = await getDictItemsByCode(DictEnum.ContractType);
};
// contractList;
let columns = ref<TableColumnItem[]>();
onMounted(() => {
getContractTypes().then((res) => {
columns.value = [
...baseColumns({
contractTypes: contractTypes.value,
}),
{
title: '操作',
maxWidth: 100,
width: 100,
minWidth:100,
dataIndex: 'ACTION',
hideInSearch: true,
fixed: 'right',
actions: ({ record }) => [
{
icon: 'ant-design:edit-outlined',
tooltip: '编辑',
auth: {
perm: 'app:contract:update',
effect: 'disable',
},
onClick: () => openEditModal(record),
},
{
icon: 'ant-design:delete-outlined',
color: 'red',
tooltip: '删除此合同',
auth: 'app:contract:delete',
popConfirm: {
title: '你确定要删除吗?',
placement: 'left',
onConfirm: () => delRowConfirm(record.id),
},
},
],
},
];
});
});
/**
* @description 打开新增/编辑弹窗
*/
const openEditModal = async (record: Partial<TableListItem>) => {
const [formRef] = await showModal({
modalProps: {
title: `${record.id ? '编辑' : '新增'}合同`,
width: '50%',
onFinish: async (values) => {
const params = {
...values,
signingDate: formatToDate(values.signingDate),
deliveryDeadline: formatToDate(values.deliveryDeadline),
};
if (record.id) {
await Api.contract.contractUpdate({ id: record.id }, params);
} else {
await Api.contract.contractCreate(params);
}
dynamicTableInstance?.reload();
},
},
formProps: {
labelWidth: 100,
schemas: contractSchemas(contractTypes.value),
},
});
//
if (record.id) {
const info = await Api.contract.contractInfo({ id: record.id });
formRef?.setFieldsValue({
...info,
});
}
};
function delRowConfirm(record) {}
</script>
<style lang="less" scoped></style>

View File

@ -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,