feat(crm): 优化合同管理及待办功能
- 移除安灯报警提醒项
- 优化合同审核和提醒列表,修复详情跳转逻辑错误
- 添加合同编号和订单号链接跳转功能
- 显示合同审核状态标签和订单信息
- 优化客户相关待办列表,添加合同数量和金额统计
- 重构待办数量获取逻辑,支持独立加载避免单个接口失败影响其他待办
- 添加合同文档下载功能,支持Word格式导出
- 优化回款审核和计划提醒功能,修复数据关联错误
- 从车间管理中移除面积字段相关显示和配置
- 添加操作权限控制和流程实例验证
| | |
| | | ); |
| | | } |
| | | |
| | | /** æè´éå忍¡æ¿ä¸è½½ Word æä»¶ */ |
| | | export function downloadContractDocument(id: number) { |
| | | return requestClient.download('/crm/contract/download-document', { params: { id } }); |
| | | } |
| | | |
| | | /** æ¥è¯¢åå详æ
*/ |
| | | export function getContract(id: number) { |
| | | return requestClient.get<CrmContractApi.Contract>( |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | import { downloadContractDocument } from '#/api/crm/contract'; |
| | | |
| | | export async function downloadContractWord(id: number, no?: string) { |
| | | const source = await downloadContractDocument(id); |
| | | const suffix = (no || String(id)).replaceAll(/[\\/:*?"<>|]/g, '_'); |
| | | downloadFileFromBlobPart({ fileName: `è´éåå-${suffix}.docx`, source }); |
| | | } |
| | |
| | | <script lang="ts" setup> |
| | | import type { Ref } from 'vue'; |
| | | |
| | | import { computed, onActivated, onMounted, ref, watch } from 'vue'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | |
| | | leftMenu.value = item.menu; |
| | | } |
| | | |
| | | /** è·åæ°é */ |
| | | async function getCount() { |
| | | customerTodayContactCount.value = await getTodayContactCustomerCount(); |
| | | customerPutPoolRemindCount.value = await getPutPoolRemindCustomerCount(); |
| | | customerFollowCount.value = await getFollowCustomerCount(); |
| | | clueFollowCount.value = await getFollowClueCount(); |
| | | if (!automaticApproval.value) contractAuditCount.value = await getAuditContractCount(); |
| | | contractRemindCount.value = await getRemindContractCount(); |
| | | if (!automaticApproval.value) receivableAuditCount.value = await getAuditReceivableCount(); |
| | | receivablePlanRemindCount.value = await getReceivablePlanRemindCount(); |
| | | /** ç¬ç«å è½½åç±»æ°éï¼å个æ¥å£å¤±è´¥ä¸é»æå
¶ä»å¾
åã */ |
| | | let countRequest: Promise<void> | undefined; |
| | | function getCount() { |
| | | if (countRequest) return countRequest; |
| | | const requests: [Ref<number>, () => Promise<number>][] = [ |
| | | [customerTodayContactCount, getTodayContactCustomerCount], |
| | | [customerPutPoolRemindCount, getPutPoolRemindCustomerCount], |
| | | [customerFollowCount, getFollowCustomerCount], |
| | | [clueFollowCount, getFollowClueCount], |
| | | [contractRemindCount, getRemindContractCount], |
| | | [receivablePlanRemindCount, getReceivablePlanRemindCount], |
| | | ]; |
| | | if (!automaticApproval.value) { |
| | | requests.push( |
| | | [contractAuditCount, getAuditContractCount], |
| | | [receivableAuditCount, getAuditReceivableCount], |
| | | ); |
| | | } |
| | | countRequest = Promise.allSettled( |
| | | requests.map(async ([count, request]) => { |
| | | count.value = await request(); |
| | | }), |
| | | ).then(() => undefined).finally(() => { |
| | | countRequest = undefined; |
| | | }); |
| | | return countRequest; |
| | | } |
| | | |
| | | /** æ¿æ´»æ¶ */ |
| | |
| | | </template> |
| | | </List> |
| | | </Card> |
| | | <component class="ml-4 w-4/5" :is="currentComponent" /> |
| | | <component class="ml-4 w-4/5" :is="currentComponent" @refresh="getCount" /> |
| | | </div> |
| | | </Page> |
| | | </template> |
| | |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | import { Button, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getContractPage } from '#/api/crm/contract'; |
| | |
| | | } |
| | | /** æå¼å®¢æ·è¯¦æ
*/ |
| | | function handleCustomerDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmCustomerDetail', params: { id: row.id } }); |
| | | if (row.customerId) push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); |
| | | } |
| | | |
| | | /** æå¼è系人详æ
*/ |
| | | function handleContactDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmContactDetail', params: { id: row.id } }); |
| | | if (row.signContactId) push({ name: 'CrmContactDetail', params: { id: row.signContactId } }); |
| | | } |
| | | |
| | | /** æå¼åæºè¯¦æ
*/ |
| | | function handleBusinessDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmBusinessDetail', params: { id: row.id } }); |
| | | if (row.businessId) push({ name: 'CrmBusinessDetail', params: { id: row.businessId } }); |
| | | } |
| | | |
| | | function handleViewOrder(row: CrmContractApi.Contract) { |
| | | if (row.orderId) push({ name: 'ErpSaleOrder', query: { id: row.orderId } }); |
| | | } |
| | | |
| | | const [Grid] = useVbenVxeGrid({ |
| | |
| | | |
| | | <template> |
| | | <Grid> |
| | | <template #no="{ row }"> |
| | | <Button type="link" @click="handleContractDetail(row)"> |
| | | {{ row.no }} |
| | | </Button> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleContractDetail(row)"> |
| | | {{ row.name }} |
| | |
| | | {{ row.businessName }} |
| | | </Button> |
| | | </template> |
| | | <template #contactName="{ row }"> |
| | | <template #signContactName="{ row }"> |
| | | <Button type="link" @click="handleContactDetail(row)"> |
| | | {{ row.contactName }} |
| | | {{ row.signContactName }} |
| | | </Button> |
| | | </template> |
| | | <template #auditStatus="{ row }"> |
| | | <Tag v-if="row.auditStatus === 0">æªæäº¤</Tag> |
| | | <Tag v-else-if="row.auditStatus === 10" color="warning">审æ¹ä¸</Tag> |
| | | <Tag v-else-if="row.auditStatus === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-else-if="row.auditStatus === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | <Tag v-else-if="row.auditStatus === 40">已忶</Tag> |
| | | <Tag v-else-if="row.auditStatus === 50" color="error">å·²ä½åº</Tag> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #orderNo="{ row }"> |
| | | <Button v-if="row.orderId" type="link" @click="handleViewOrder(row)">{{ row.orderNo }}</Button> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | label: 'æ¥ç审æ¹', |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | auth: ['crm:contract:update'], |
| | | ifShow: !!row.processInstanceId, |
| | | onClick: handleProcessDetail.bind(null, row), |
| | | }, |
| | | ]" |
| | |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | import { Button, Tag } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getContractPage } from '#/api/crm/contract'; |
| | |
| | | |
| | | /** æå¼å®¢æ·è¯¦æ
*/ |
| | | function handleCustomerDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmCustomerDetail', params: { id: row.id } }); |
| | | if (row.customerId) push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); |
| | | } |
| | | |
| | | /** æå¼è系人详æ
*/ |
| | | function handleContactDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmContactDetail', params: { id: row.id } }); |
| | | if (row.signContactId) push({ name: 'CrmContactDetail', params: { id: row.signContactId } }); |
| | | } |
| | | |
| | | /** æå¼åæºè¯¦æ
*/ |
| | | function handleBusinessDetail(row: CrmContractApi.Contract) { |
| | | push({ name: 'CrmBusinessDetail', params: { id: row.id } }); |
| | | if (row.businessId) push({ name: 'CrmBusinessDetail', params: { id: row.businessId } }); |
| | | } |
| | | |
| | | function handleViewOrder(row: CrmContractApi.Contract) { |
| | | if (row.orderId) push({ name: 'ErpSaleOrder', query: { id: row.orderId } }); |
| | | } |
| | | |
| | | const [Grid] = useVbenVxeGrid({ |
| | |
| | | |
| | | <template> |
| | | <Grid> |
| | | <template #no="{ row }"> |
| | | <Button type="link" @click="handleContractDetail(row)"> |
| | | {{ row.no }} |
| | | </Button> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleContractDetail(row)"> |
| | | {{ row.name }} |
| | |
| | | {{ row.signContactName }} |
| | | </Button> |
| | | </template> |
| | | <template #auditStatus="{ row }"> |
| | | <Tag v-if="row.auditStatus === 0">æªæäº¤</Tag> |
| | | <Tag v-else-if="row.auditStatus === 10" color="warning">审æ¹ä¸</Tag> |
| | | <Tag v-else-if="row.auditStatus === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-else-if="row.auditStatus === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | <Tag v-else-if="row.auditStatus === 40">已忶</Tag> |
| | | <Tag v-else-if="row.auditStatus === 50" color="error">å·²ä½åº</Tag> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #orderNo="{ row }"> |
| | | <Button v-if="row.orderId" type="link" @click="handleViewOrder(row)">{{ row.orderNo }}</Button> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | label: 'æ¥ç审æ¹', |
| | | type: 'link', |
| | | auth: ['crm:contract:update'], |
| | | ifShow: !!row.processInstanceId, |
| | | onClick: handleProcessDetail.bind(null, row), |
| | | }, |
| | | ]" |
| | |
| | | import type { CrmCustomerApi } from '#/api/crm/customer'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | import { erpNumberFormatter } from '@vben/utils'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> |
| | | </template> |
| | | <template #contracts="{ row }"> |
| | | <span v-if="row.contractCount">{{ row.contractCount }} 份 · ¥{{ erpNumberFormatter(row.contractTotalPrice, 2) }}</span> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">æ¥ç详æ
</Button> |
| | | </template> |
| | |
| | | import type { CrmCustomerApi } from '#/api/crm/customer'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | import { erpNumberFormatter } from '@vben/utils'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getCustomerPage } from '#/api/crm/customer'; |
| | | import { getPutPoolRemindCustomerPage } from '#/api/crm/customer'; |
| | | import { useGridColumns } from '#/views/crm/customer/data'; |
| | | |
| | | import { SCENE_TYPES } from '../data'; |
| | |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getCustomerPage({ |
| | | return await getPutPoolRemindCustomerPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | pool: true, // åºå® å
¬æµ·åæ°ä¸º true |
| | | ...formValues, |
| | | }); |
| | | }, |
| | |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> |
| | | </template> |
| | | <template #contracts="{ row }"> |
| | | <span v-if="row.contractCount">{{ row.contractCount }} 份 · ¥{{ erpNumberFormatter(row.contractTotalPrice, 2) }}</span> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">æ¥ç详æ
</Button> |
| | | </template> |
| | |
| | | import type { CrmCustomerApi } from '#/api/crm/customer'; |
| | | |
| | | import { useRouter } from 'vue-router'; |
| | | import { erpNumberFormatter } from '@vben/utils'; |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | |
| | | <template #name="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.name }}</Button> |
| | | </template> |
| | | <template #contracts="{ row }"> |
| | | <span v-if="row.contractCount">{{ row.contractCount }} 份 · ¥{{ erpNumberFormatter(row.contractTotalPrice, 2) }}</span> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">æ¥ç详æ
</Button> |
| | | </template> |
| | |
| | | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getReceivablePage, updateReceivableStatus } from '#/api/crm/receivable'; |
| | | import { useGridColumns } from '#/views/crm/receivable/data'; |
| | | |
| | | import { AUDIT_STATUS } from '../data'; |
| | | |
| | | const { push } = useRouter(); |
| | | const emit = defineEmits<{ refresh: [] }>(); |
| | | |
| | | /** å®¡æ ¸ */ |
| | | async function handleAudit(row: CrmReceivableApi.Receivable) { |
| | |
| | | try { |
| | | await updateReceivableStatus(row.id!, 20); |
| | | message.success('å®¡æ ¸æå'); |
| | | gridApi.query(); |
| | | await gridApi.query(); |
| | | emit('refresh'); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | |
| | | |
| | | /** æå¼åå详æ
*/ |
| | | function handleContractDetail(row: CrmReceivableApi.Receivable) { |
| | | push({ name: 'CrmContractDetail', params: { id: row.contractId } }); |
| | | if (row.contractId) push({ name: 'CrmContractDetail', params: { id: row.contractId } }); |
| | | } |
| | | |
| | | function handleInvoiceDetail(row: CrmReceivableApi.Receivable) { |
| | | if (row.invoiceId) push({ name: 'CrmInvoiceDetail', params: { id: row.invoiceId } }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | schema: [ |
| | | { |
| | | fieldName: 'auditStatus', |
| | | label: 'ååç¶æ', |
| | | label: 'å®¡æ ¸ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | |
| | | return await getReceivablePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | sceneType: 1, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | |
| | | </Button> |
| | | </template> |
| | | <template #contractNo="{ row }"> |
| | | <Button type="link" @click="handleContractDetail(row)"> |
| | | {{ row.contractNo }} |
| | | <Button v-if="row.contractId" type="link" @click="handleContractDetail(row)"> |
| | | {{ row.contract?.no ?? row.contractNo ?? 'â' }} |
| | | </Button> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #invoiceNo="{ row }"> |
| | | <template v-if="row.invoice"> |
| | | <Button type="link" @click="handleInvoiceDetail(row)">{{ row.invoice.no }}</Button> |
| | | <span> / {{ row.invoice.invoiceNo ?? 'â' }} / Â¥{{ row.invoice.price ?? 0 }}</span> |
| | | </template> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Button |
| | | v-if="row.auditStatus === 10" |
| | | type="link" |
| | | @click="handleAudit(row)" |
| | | > |
| | | å®¡æ ¸ |
| | | </Button> |
| | | <TableAction :actions="[{ |
| | | label: 'å®¡æ ¸', |
| | | type: 'link', |
| | | auth: ['crm:receivable:update'], |
| | | ifShow: row.auditStatus === 10, |
| | | onClick: handleAudit.bind(null, row), |
| | | }]" /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| | |
| | | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getReceivablePlanPage } from '#/api/crm/receivable/plan'; |
| | | import Form from '#/views/crm/receivable/modules/form.vue'; |
| | | import { useGridColumns } from '#/views/crm/receivable/plan/data'; |
| | |
| | | import { RECEIVABLE_REMIND_TYPE } from '../data'; |
| | | |
| | | const { push } = useRouter(); |
| | | const emit = defineEmits<{ refresh: [] }>(); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | emit('refresh'); |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** æå¼å款详æ
*/ |
| | | /** æå¼å款计å详æ
*/ |
| | | function handleDetail(row: CrmReceivablePlanApi.Plan) { |
| | | push({ name: 'CrmReceivableDetail', params: { id: row.id } }); |
| | | push({ name: 'CrmReceivablePlanDetail', params: { id: row.id } }); |
| | | } |
| | | |
| | | /** æå¼å®¢æ·è¯¦æ
*/ |
| | |
| | | |
| | | /** åå»ºåæ¬¾ */ |
| | | function handleCreateReceivable(row: CrmReceivablePlanApi.Plan) { |
| | | if (row.receivableId) return; |
| | | formModalApi.setData({ plan: row }).open(); |
| | | } |
| | | |
| | | const [Grid] = useVbenVxeGrid({ |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: [ |
| | | { |
| | | fieldName: 'remindType', |
| | | label: 'ååç¶æ', |
| | | label: '忬¾ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | |
| | | return await getReceivablePlanPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | sceneType: 1, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | |
| | | |
| | | <template> |
| | | <div> |
| | | <FormModal /> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template #customerName="{ row }"> |
| | | <Button type="link" @click="handleCustomerDetail(row)"> |
| | |
| | | <Button type="link" @click="handleDetail(row)">{{ row.period }}</Button> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <Button type="link" @click="handleCreateReceivable(row)"> |
| | | åå»ºåæ¬¾ |
| | | </Button> |
| | | <TableAction :actions="[{ |
| | | label: 'åå»ºåæ¬¾', |
| | | type: 'link', |
| | | auth: ['crm:receivable:create'], |
| | | ifShow: !row.receivableId, |
| | | onClick: handleCreateReceivable.bind(null, row), |
| | | }]" /> |
| | | </template> |
| | | </Grid> |
| | | </div> |
| | |
| | | import { OperateLog } from '#/components/operate-log'; |
| | | import { ACTION_ICON, TableAction } from '#/components/table-action'; |
| | | import { $t } from '#/locales'; |
| | | import { downloadContractWord } from '#/utils/contract-document'; |
| | | import { FollowUp } from '#/views/crm/followup'; |
| | | import { PermissionList, TransferForm } from '#/views/crm/permission'; |
| | | import { ProductDetailsList } from '#/views/crm/product/components'; |
| | |
| | | onClick: handleEdit, |
| | | }, |
| | | { |
| | | label: 'ä¸è½½åå', |
| | | type: 'primary', |
| | | icon: 'lucide:download', |
| | | auth: ['crm:contract:query'], |
| | | onClick: () => downloadContractWord(contractId, contract.no), |
| | | }, |
| | | { |
| | | label: '转移', |
| | | type: 'primary', |
| | | ifShow: permissionListRef?.validateOwnerUser, |
| | |
| | | } from '#/api/crm/contract'; |
| | | import { $t } from '#/locales'; |
| | | import { automaticApproval } from '#/store/audit-policy'; |
| | | import { downloadContractWord } from '#/utils/contract-document'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'ä¸è½½åå', |
| | | type: 'link', |
| | | icon: 'lucide:download', |
| | | auth: ['crm:contract:query'], |
| | | onClick: () => downloadContractWord(row.id, row.no), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | |
| | | getSaleQuotationPage, |
| | | } from '#/api/crm/saleQuotation'; |
| | | import { $t } from '#/locales'; |
| | | import { downloadContractWord } from '#/utils/contract-document'; |
| | | |
| | | import type { CrmSaleQuotationAiApi } from '#/api/crm/saleQuotation/ai'; |
| | | |
| | |
| | | async onOk() { |
| | | const contractId = await convertSaleQuotationToContract(row.id!); |
| | | message.success('转ååæå'); |
| | | gridApi.query(); |
| | | try { |
| | | await downloadContractWord(contractId); |
| | | } catch { |
| | | message.warning('ååå·²åå»ºï¼æä»¶ä¸è½½å¤±è´¥ï¼å¯å¨åå详æ
ä¸éæ°ä¸è½½'); |
| | | } |
| | | push({ name: 'CrmContractDetail', params: { id: contractId } }); |
| | | }, |
| | | }); |
| | |
| | | ifShow: !!row.contractId, |
| | | }, |
| | | { |
| | | label: 'ä¸è½½åå', |
| | | type: 'link', |
| | | auth: ['crm:contract:query'], |
| | | onClick: () => downloadContractWord(row.contractId!, row.contractNo), |
| | | ifShow: !!row.contractId, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| | | </template> |
| | |
| | | /** å¾
åæéåè¡¨ï¼æ ç¾ãæè¿°ã徿 ãç®æ è·¯ç±åç§°ãæ°é */ |
| | | const alertItems = computed(() => [ |
| | | { |
| | | count: props.summary.andonActiveCount, |
| | | desc: 'æªå¤ç½®çå®ç¯å¼å«', |
| | | icon: 'lucide:bell-ring', |
| | | iconClass: 'bg-red-50 text-red-500', |
| | | label: 'å®ç¯æ¥è¦', |
| | | routeName: 'MesProAndonRecord', |
| | | }, |
| | | { |
| | | count: props.summary.repairActiveCount, |
| | | desc: 'å¾
å¤çç维修订å', |
| | | icon: 'lucide:wrench', |
| | |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>ç¼ç ï¼{{ selectedItem.code || '-' }}</div> |
| | | <div>åç§°ï¼{{ selectedItem.name || '-' }}</div> |
| | | <div> |
| | | é¢ç§¯ï¼ |
| | | {{ selectedItem.area !== null ? `${selectedItem.area} ã¡` : '-' }} |
| | | </div> |
| | | <div>è´è´£äººï¼{{ selectedItem.chargeUserName || '-' }}</div> |
| | | </div> |
| | | </template> |
| | |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'area', |
| | | label: 'é¢ç§¯', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'chargeUserId', |
| | | label: 'è´è´£äºº', |
| | | component: 'ApiSelect', |
| | |
| | | field: 'name', |
| | | title: '车é´åç§°', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'area', |
| | | title: 'é¢ç§¯', |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'chargeUserName', |