| | |
| | | <template> |
| | | <Tooltip :title="t('layout.header.tooltipErrorLog')" placement="bottom" :mouseEnterDelay="0.5" @click="handleToErrorList"> |
| | | <Badge :count="getCount" :offset="[0, 10]" :overflowCount="99"> |
| | | <Badge :count="0" :offset="[0, 10]" :overflowCount="99"> |
| | | <Icon icon="ion:bug-outline" /> |
| | | </Badge> |
| | | </Tooltip> |
| | |
| | | </template> |
| | | <script lang="ts"> |
| | | import { computed, defineComponent, ref, unref, reactive, onMounted, getCurrentInstance } from 'vue'; |
| | | import { Popover, Tabs, Badge } from 'ant-design-vue'; |
| | | import { Badge } from 'ant-design-vue'; |
| | | import { BellOutlined } from '@ant-design/icons-vue'; |
| | | import { tabListData } from './data'; |
| | | import { listCementByUser, editCementSend } from './notify.api'; |
| | | import NoticeList from './NoticeList.vue'; |
| | | import DetailModal from '/@/views/monitor/mynews/DetailModal.vue'; |
| | | import DynamicNotice from '/@/views/monitor/mynews/DynamicNotice.vue'; |
| | | import { useModal } from '/@/components/Modal'; |
| | |
| | | import { useGlobSetting } from '/@/hooks/setting'; |
| | | import { useUserStore } from '/@/store/modules/user'; |
| | | import { connectWebSocket, onWebSocket } from '/@/hooks/web/useWebSocket'; |
| | | import { readAllMsg } from '/@/views/monitor/mynews/mynews.api'; |
| | | import { getToken } from '/@/utils/auth'; |
| | | import md5 from 'crypto-js/md5'; |
| | | |
| | |
| | | |
| | | export default defineComponent({ |
| | | components: { |
| | | Popover, |
| | | BellOutlined, |
| | | Tabs, |
| | | TabPane: Tabs.TabPane, |
| | | Badge, |
| | | NoticeList, |
| | | DetailModal, |
| | | DynamicNotice, |
| | | SysMessageModal, |
| | |
| | | const [registerDetail, detailModal] = useModal(); |
| | | const listData = ref(tabListData); |
| | | const count = computed(() => { |
| | | let count = 0; |
| | | for (let i = 0; i < listData.value.length; i++) { |
| | | count += listData.value[i].count; |
| | | } |
| | | return count; |
| | | return 0; |
| | | }); |
| | | |
| | | const [registerMessageModal, { openModal: openMessageModal }] = useModal(); |
| | |
| | | openMessageModal(true, {}); |
| | | } |
| | | |
| | | const popoverVisible = ref<boolean>(false); |
| | | onMounted(() => { |
| | | initWebSocket(); |
| | | }); |
| | |
| | | // 获取系统消息 |
| | | async function loadData() { |
| | | try { |
| | | let { anntMsgList, sysMsgList, anntMsgTotal, sysMsgTotal } = await listCementByUser({ |
| | | let { anntMsgList, sysMsgList } = await listCementByUser({ |
| | | pageSize: 5, |
| | | }); |
| | | listData.value[0].list = anntMsgList.map(mapAnnouncement); |
| | | listData.value[1].list = sysMsgList.map(mapAnnouncement); |
| | | listData.value[0].count = anntMsgTotal; |
| | | listData.value[1].count = sysMsgTotal; |
| | | // 始终设置count为0,不显示通知数字 |
| | | listData.value[0].count = 0; |
| | | listData.value[1].count = 0; |
| | | } catch (e) { |
| | | console.warn('系统消息通知异常:', e); |
| | | } |
| | |
| | | isUpdate: true, |
| | | }); |
| | | } |
| | | popoverVisible.value = false; |
| | | } |
| | | |
| | | // 初始化 WebSocket |
| | |
| | | } |
| | | } |
| | | |
| | | // 清空消息 |
| | | function onEmptyNotify() { |
| | | popoverVisible.value = false; |
| | | readAllMsg({}, loadData); |
| | | } |
| | | async function reloadCount(id) { |
| | | try { |
| | | await editCementSend(id); |
| | |
| | | registerMessageModal, |
| | | reloadCount, |
| | | onNoticeClick, |
| | | onEmptyNotify, |
| | | numberStyle: {}, |
| | | popoverVisible, |
| | | registerDetail, |
| | | dynamicNoticeProps, |
| | | }; |
| | |
| | | about: 'About', |
| | | workbench: 'Workbench', |
| | | analysis: 'Analysis', |
| | | welcome: 'Welcome', |
| | | }; |
| | |
| | | about: '关于', |
| | | workbench: '工作台', |
| | | analysis: '分析页', |
| | | welcome: '欢迎', |
| | | }; |
| | |
| | | |
| | | //update-begin---author:wangshuai ---date:20220629 for:[issues/I5BG1I]vue3不支持auth2登录------------ |
| | | //update-begin---author:wangshuai ---date:20221111 for: [VUEN-2472]分享免登录------------ |
| | | const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH,SYS_FILES_PATH, TOKEN_LOGIN ]; |
| | | const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH, SYS_FILES_PATH, TOKEN_LOGIN]; |
| | | //update-end---author:wangshuai ---date:20221111 for: [VUEN-2472]分享免登录------------ |
| | | //update-end---author:wangshuai ---date:20220629 for:[issues/I5BG1I]vue3不支持auth2登录------------ |
| | | |
| | |
| | | let homePathJumpCount = 0; |
| | | |
| | | router.beforeEach(async (to, from, next) => { |
| | | const userInfo = userStore.getUserInfo; |
| | | |
| | | if ( |
| | | // 【#6861】跳转到自定义首页的逻辑,只跳转一次即可 |
| | | homePathJumpCount < 1 && |
| | |
| | | userStore.getUserInfo.homePath && |
| | | userStore.getUserInfo.homePath !== PageEnum.BASE_HOME |
| | | ) { |
| | | // 代理商用户跳过homePath跳转 |
| | | if (userInfo?.username === 'dls') { |
| | | next({ path: '/dashboard/welcome', replace: true }); |
| | | return; |
| | | } |
| | | homePathJumpCount++; |
| | | next(userStore.getUserInfo.homePath); |
| | | return; |
| | |
| | | }; |
| | | |
| | | // Basic routing without permission |
| | | export const basicRoutes = [ |
| | | export const basicRoutes: AppRouteRecordRaw[] = [ |
| | | LoginRoute, |
| | | RootRoute, |
| | | ...mainOutRoutes, |
| | |
| | | import { LAYOUT } from '/@/router/constant'; |
| | | import { t } from '/@/hooks/web/useI18n'; |
| | | |
| | | const dashboard: AppRouteModule = { |
| | | const about: AppRouteModule = { |
| | | path: '/about', |
| | | name: 'About', |
| | | component: LAYOUT, |
| | | redirect: '/about/index', |
| | | redirect: '/about/welcome', |
| | | meta: { |
| | | hideChildrenInMenu: true, |
| | | icon: 'simple-icons:about-dot-me', |
| | | title: t('routes.dashboard.about'), |
| | | orderNo: 100000, |
| | | orderNo: 100, |
| | | icon: 'ion:home-outline', |
| | | title: '导航', |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'index', |
| | | name: 'AboutPage', |
| | | component: () => import('/@/views/sys/about/index.vue'), |
| | | path: 'welcome', |
| | | name: 'AboutWelcome', |
| | | component: () => import('/@/views/dashboard/Welcome/index.vue'), |
| | | meta: { |
| | | title: t('routes.dashboard.about'), |
| | | icon: 'simple-icons:about-dot-me', |
| | | hideMenu: true, |
| | | title: '欢迎', |
| | | ignoreKeepAlive: true, |
| | | }, |
| | | }, |
| | | { |
| | | path: 'test', |
| | | name: 'AboutTest', |
| | | meta: { |
| | | title: '测试', |
| | | ignoreKeepAlive: true, |
| | | }, |
| | | component: () => import('/@/views/demo/system/test/index.vue'), |
| | | }, |
| | | { |
| | | path: 'account', |
| | | name: 'AboutAccount', |
| | | meta: { |
| | | title: '账号', |
| | | ignoreKeepAlive: false, |
| | | }, |
| | | component: () => import('/@/views/demo/system/account/index.vue'), |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | export default dashboard; |
| | | export default about; |
| | |
| | | }, |
| | | children: [ |
| | | { |
| | | path: 'welcome', |
| | | name: 'Welcome', |
| | | component: () => import('/@/views/dashboard/Welcome/index.vue'), |
| | | meta: { |
| | | title: t('routes.dashboard.welcome'), |
| | | }, |
| | | }, |
| | | { |
| | | path: 'analysis', |
| | | name: 'Analysis', |
| | | component: () => import('/@/views/dashboard/Analysis/index.vue'), |
| | |
| | | |
| | | //update-begin---author:wangshuai---date:2024-04-03---for:【issues/1102】设置单点登录后页面,进入首页提示404,也没有绘制侧边栏 #1102--- |
| | | let ticket = getUrlParam('ticket'); |
| | | let homePath = (userInfo && userInfo.homePath) || PageEnum.BASE_HOME; |
| | | |
| | | // 代理商用户跳转到欢迎页面 |
| | | if (userInfo?.username === 'dls') { |
| | | homePath = '/dashboard/welcome'; |
| | | } |
| | | |
| | | if(ticket){ |
| | | goHome && (window.location.replace((userInfo && userInfo.homePath) || PageEnum.BASE_HOME)); |
| | | goHome && (window.location.replace(homePath)); |
| | | }else{ |
| | | goHome && (await router.replace((userInfo && userInfo.homePath) || PageEnum.BASE_HOME)); |
| | | goHome && (await router.replace(homePath)); |
| | | } |
| | | //update-end---author:wangshuai---date:2024-04-03---for:【issues/1102】设置单点登录后页面,进入首页提示404,也没有绘制侧边栏 #1102--- |
| | | } |
| | |
| | | label: "客户名称", |
| | | field: "customerName", |
| | | component: 'Input', |
| | | componentProps: { |
| | | style: { width: '160px' } // 增加宽度,确保标签显示完整 |
| | | }, |
| | | }, |
| | | { |
| | | label: "代理商名称", |
| | | field: "agentsName", |
| | | component: 'Input', |
| | | componentProps: { |
| | | style: { width: '160px' } // 增加宽度,确保标签显示完整 |
| | | }, |
| | | }, |
| | | { |
| | | label: "审核状态", |
| | |
| | | component: 'JDictSelectTag', |
| | | componentProps:{ |
| | | dictCode: "yuyici_status", |
| | | style: { width: '140px'} // 增加宽度,确保标签显示完整 |
| | | }, |
| | | }, |
| | | ]; |
| | |
| | | api: list, |
| | | columns, |
| | | canResize: true, |
| | | formConfig: { |
| | | labelWidth: 90, // 设置标签宽度,确保标签显示完整 |
| | | // 找到 formConfig 部分,替换为以下完整配置 |
| | | formConfig: { |
| | | //labelWidth: 80, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, // 启用紧凑模式 |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | | actionColumn: { |
| | | width: 180, |
| | | fixed: 'right', |
| | | }, |
| | | actionColumn: { |
| | | width: 180, |
| | | fixed: 'right', |
| | | }, |
| | | beforeFetch: (params) => { |
| | | if (params && fieldPickers) { |
| | | for (let key in fieldPickers) { |
| | |
| | | <style lang="less" scoped> |
| | | :deep(.ant-picker), |
| | | :deep(.ant-input-number) { |
| | | width: 100%;} |
| | | :deep(.ant-form) { |
| | | text-align: left !important; |
| | | width: 100%; |
| | | } |
| | | |
| | | // 确保搜索项左对齐 |
| | | :deep(.ant-row) { |
| | | justify-content: flex-start !important; |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | |
| | | .contract-select-modal { |
| | | .contract-file-card { |
| | |
| | | columns, |
| | | canResize: true, |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | labelWidth: 80, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单完全靠左对齐,消除所有左边空隙 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单容器的左边空隙 */ |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除行容器的左边空隙 */ |
| | | :deep(.ant-form-item-row) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单项的左边空隙 */ |
| | | :deep(.ant-form-item) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | .contract-select-modal { |
| | | .contract-file-card { |
| | | cursor: pointer; |
| | |
| | | columns, |
| | | canResize: true, |
| | | immediate: false, // 禁用立即加载,等待角色查询完成后再加载 |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | formConfig: { |
| | | labelWidth: 80, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | // 布局配置 |
| | | compact: true, // 启用紧凑模式 |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | .contract-select-modal { |
| | | .contract-file-card { |
| | | cursor: pointer; |
| | |
| | | showAdvancedButton: true, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | | actionColumn: { |
| | | width: 120, |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单完全靠左对齐,消除所有左边空隙 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单容器的左边空隙 */ |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除行容器的左边空隙 */ |
| | | :deep(.ant-form-item-row) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单项的左边空隙 */ |
| | | :deep(.ant-form-item) { |
| | | margin-left: 0 !important; |
| | | } |
| | | </style> |
| | |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 6, |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单完全靠左对齐,消除所有左边空隙 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单容器的左边空隙 */ |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除行容器的左边空隙 */ |
| | | :deep(.ant-form-item-row) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单项的左边空隙 */ |
| | | :deep(.ant-form-item) { |
| | | margin-left: 0 !important; |
| | | } |
| | | </style> |
| | |
| | | import IndexTask from './homePage/IndexTask.vue'; |
| | | |
| | | const indexStyle = ref(0); |
| | | </script> |
| | | </script> |
| New file |
| | |
| | | <template> |
| | | <div class="agent-welcome"> |
| | | <div class="welcome-content"> |
| | | <h1 class="welcome-title">您好,欢迎访问</h1> |
| | | <h2 class="welcome-system-name">艾亿欧AIEO智能业务管理系统</h2> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .agent-welcome { |
| | | min-height: calc(100vh - 120px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #f0f2f5; |
| | | } |
| | | |
| | | .welcome-content { |
| | | text-align: center; |
| | | padding: 40px; |
| | | } |
| | | |
| | | .welcome-title { |
| | | font-size: 32px; |
| | | color: #333; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .welcome-system-name { |
| | | font-size: 40px; |
| | | color: #1890ff; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| | |
| | | /* border: 1px solid #0099ff; */ |
| | | border-radius: 6px; |
| | | /* padding: 12px; */ |
| | | width: 400px; |
| | | flex: 0 0 auto; |
| | | min-width: 300px; |
| | | max-width: 400px; |
| | | } |
| | | |
| | | .card-header { |
| | |
| | | <div class="tables-container"> |
| | | <!-- 左边表格 --> |
| | | <div class="table-section"> |
| | | <div class="table-title"><img src="./警报灯警报器.png" class="title-icon"/><strong>签约快到期企业</strong><div style="width: 40px; height: 20px; background: red; border-radius: 20px; line-height: 20px; margin-left: 10px; text-align: center; color: white; font-size: 12px; font-weight: bold;">99+</div><button>更多</button></div> |
| | | <div class="table-title"><img src="./警报灯警报器.png" class="title-icon"/><strong>签约快到期企业</strong><button>更多</button></div> |
| | | <div class="table-wrapper left-table"> |
| | | <table class="data-table"> |
| | | <thead> |
| | |
| | | </div> |
| | | <div class="attention-z"> |
| | | <h2 class="section-title" style="font-size: 20px; font-weight: bold; margin-left: 60px; color: black;">签约语义词数量概览</h2> |
| | | <div ref="semanticChartContainer" style="margin: 0 20px 20px 20px; padding: 16px; border: 1px solid #e9ecef; border-radius: 20px; background: white;"> |
| | | <div ref="semanticChartContainer" style="margin: 0 20px 20px; padding: 16px; border: 1px solid #e9ecef; border-radius: 20px; background: white;"> |
| | | <SemanticWordCountChart /> |
| | | </div> |
| | | </div> |
| | |
| | | background: white; |
| | | border-radius: 20px; |
| | | border: 1px solid #e9ecef; |
| | | margin: 0 20px 20px 20px; |
| | | padding: 0 20px 20px 20px; |
| | | margin: 0 20px 20px; |
| | | padding: 0 20px 20px; |
| | | } |
| | | |
| | | .filter-tab-container { |
| | |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 20px; |
| | | margin-bottom: 20px; |
| | | margin: 0 20px 20px; |
| | | padding: 16px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | |
| | | gap: 20px; |
| | | min-height: 200px; |
| | | /* 确保加载状态有足够的空间显示 */ |
| | | margin: 0 20px; |
| | | } |
| | | |
| | | .card-wrapper :deep(.ant-spin-nested-loading) { |
| | |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: stretch; |
| | | gap: 0; |
| | | border: 2px solid #0099ff; |
| | | border-radius: 20px; |
| | | overflow: hidden; |
| | | background: #fff; |
| | | } |
| | | |
| | | .card-content > * { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .card-content .stats-card { |
| | | border-right: 1px solid #e9ecef; |
| | | } |
| | | |
| | | .card-content .chart-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | #gege { |
| | | position: relative; |
| | | left: 50px; |
| | |
| | | <template> |
| | | <PageWrapper title="客户详情页" contentBackground> |
| | | <a-card title="基本信息"> |
| | | <a-descriptions title="" :column="3"> |
| | | <a-descriptions-item label="企业名称"> |
| | | <span>{{ formData.customer?.enterpriseName || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="行业"> |
| | | <span>{{ formData.customer?.industry || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="客户地址"> |
| | | <span>{{ formData.customer?.customerAddress || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="对接人"> |
| | | <span>{{ formData.customer?.contactPerson || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="客户电话"> |
| | | <span>{{ formData.customer?.customerPhone || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="客户邮箱"> |
| | | <span>{{ formData.customer?.customerEmail || '-' }}</span> |
| | | </a-descriptions-item> |
| | | </a-descriptions> |
| | | </a-card> |
| | | <a-card title="合同信息"> |
| | | <a-descriptions title="" :column="1"> |
| | | <a-descriptions-item label="紧急状态"> |
| | | <span>{{ formData.emergencyStatus === 1 ? '紧急' : formData.emergencyStatus === 3 ? '正常' : '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="合作周期"> |
| | | <span>{{ formData.month ? `${formData.month}月` : '-' }}</span> |
| | | </a-descriptions-item> |
| | | <!-- <a-descriptions-item label="合作开始时间"> |
| | | <span>{{ formData.startDate || '-' }}</span> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="合作结束时间"> |
| | | <span>{{ formData.endDate || '-' }}</span> |
| | | </a-descriptions-item> --> |
| | | <a-descriptions-item label="合同"> |
| | | <div v-if="contractFiles.length > 0"> |
| | | <div v-for="file in contractFiles" :key="file.uid" class="file-item"> |
| | | <div class="file-header"> |
| | | <a @click="handleDownload(file)" style="display: inline-block">{{ getFileName(file) }}</a> |
| | | <a @click="toggleFilePreview(file)" class="preview-toggle"> |
| | | {{ isFilePreviewOpen(file) ? '收起' : '[预览]' }} |
| | | </a> |
| | | </div> |
| | | <div v-if="isFilePreviewOpen(file)" class="docx-preview"> |
| | | <div v-if="isReviewFile(file) && getFileUrl(file)"> |
| | | <vue-office-docx :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isDocxFile(file)"/> |
| | | <vue-office-excel :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isXlsxFile(file)"/> |
| | | <vue-office-pdf :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isPdfFile(file)"/> |
| | | <template v-if="isTxtFile(file)"> |
| | | <a-spin :spinning="isTxtLoading(file)"> |
| | | <pre class="code-area">{{ getTxtContent(file) }}</pre> |
| | | </a-spin> |
| | | </template> |
| | | <template v-if="isImageFile(file)"> |
| | | <img :src="getFileUrl(file)" style="width: 100%; height: 600px;" /> |
| | | </template> |
| | | </div> |
| | | <div v-else class="preview-not-supported"> |
| | | <a-empty description="当前文件类型的预览功能不支持!" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-else> |
| | | <span>-</span> |
| | | </div> |
| | | </a-descriptions-item> |
| | | <a-descriptions-item label="附件"> |
| | | <div v-if="attachmentFiles.length > 0"> |
| | | <div v-for="file in attachmentFiles" :key="file.uid" class="file-item"> |
| | | <div class="file-header"> |
| | | <a @click="handleDownload(file)" style="display: inline-block">{{ getFileName(file) }}</a> |
| | | <a @click="toggleFilePreview(file)" class="preview-toggle"> |
| | | {{ isFilePreviewOpen(file) ? '收起' : '[预览]' }} |
| | | </a> |
| | | </div> |
| | | <div v-if="isFilePreviewOpen(file)" class="docx-preview"> |
| | | <div v-if="isReviewFile(file) && getFileUrl(file)"> |
| | | <vue-office-docx :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isDocxFile(file)"/> |
| | | <vue-office-excel :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isXlsxFile(file)"/> |
| | | <vue-office-pdf :src="getFileUrl(file)" style="width: 100%; height: 600px;" v-if="isPdfFile(file)"/> |
| | | <template v-if="isTxtFile(file)"> |
| | | <a-spin :spinning="isTxtLoading(file)"> |
| | | <pre class="code-area">{{ getTxtContent(file) }}</pre> |
| | | </a-spin> |
| | | </template> |
| | | <template v-if="isImageFile(file)"> |
| | | <img :src="getFileUrl(file)" style="width: 100%; height: 600px;" /> |
| | | </template> |
| | | </div> |
| | | <div v-else class="preview-not-supported"> |
| | | <a-empty description="当前文件类型的预览功能不支持!" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-else> |
| | | <span>-</span> |
| | | </div> |
| | | </a-descriptions-item> |
| | | </a-descriptions> |
| | | </a-card> |
| | | <a-card title="语义词信息"> |
| | | <div v-if="semanticWords.length > 0"> |
| | | <a-descriptions title="" :column="1"> |
| | | <a-descriptions-item v-for="word in semanticWords" :key="word.id" :label="`语义词 ${semanticWords.indexOf(word) + 1}`"> |
| | | <div style="display: flex; flex-direction: column; gap: 12px; width: 100%"> |
| | | <div style="display: flex; gap: 16px; width: 100%"> |
| | | <span style="flex: 1;"><strong>类型:</strong>{{ word.category === '1' ? '品牌' : word.category === '2' ? '地区词' : '全国词' }}</span> |
| | | <span style="flex: 1;"><strong>语义词:</strong>{{ word.word || '-' }}</span> |
| | | <span style="flex: 1;"><strong>露出词:</strong>{{ word.outWord || '-' }}</span> |
| | | <span style="flex: 1;"><strong>价格:</strong>{{ word.price ? `${formatPrice(word.price)}元` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>合作周期:</strong>{{ word.month ? `${word.month}月` : '-' }}</span> |
| | | <span style="flex: 1;"><strong>签约排名:</strong>{{ formatRanking(word.ranking) }}</span> |
| | | <span style="flex: 1;"><strong>验收指标(>=):</strong>{{ word.acceptindicator ? `${word.acceptindicator}%` : '-' }}</span> |
| | | </div> |
| | | </div> |
| | | </a-descriptions-item> |
| | | </a-descriptions> |
| | | <PageWrapper title="欢迎" contentBackground> |
| | | <div class="welcome-container"> |
| | | <div class="welcome-content"> |
| | | <h1 class="welcome-title">您好,欢迎访问</h1> |
| | | <h2 class="welcome-system-name">艾亿欧AIEO智能业务管理系统!</h2> |
| | | </div> |
| | | <div v-else> |
| | | <a-empty description="暂无语义词数据" /> |
| | | </div> |
| | | </a-card> |
| | | <!-- 返回按钮 --> |
| | | <div class="action-buttons"> |
| | | <a-button type="default" @click="handleBack" class="back-btn">返回</a-button> |
| | | </div> |
| | | </PageWrapper> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, reactive, ref, onMounted } from 'vue'; |
| | | import { PageWrapper } from '/@/components/Page'; |
| | | import { message } from 'ant-design-vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | import { defHttp } from '/@/utils/http/axios'; |
| | | import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; |
| | | import VueOfficeDocx from '@vue-office/docx'; |
| | | import VueOfficeExcel from '@vue-office/excel'; |
| | | import VueOfficePdf from '@vue-office/pdf'; |
| | | import '@vue-office/docx/lib/v3/index.css'; |
| | | import '@vue-office/excel/lib/v3/index.css'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'DescDetailKehu', |
| | | components: { PageWrapper, VueOfficeDocx, VueOfficeExcel, VueOfficePdf }, |
| | | setup() { |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | |
| | | // 初始数据 |
| | | const initialData = { |
| | | id: '', |
| | | createTime: '', |
| | | updateBy: null, |
| | | updateTime: null, |
| | | agentsId: null, |
| | | agentsName: null, |
| | | contractFileList: null, |
| | | contractName: '', |
| | | createBy: '', |
| | | customer: { |
| | | id: '', |
| | | createBy: '', |
| | | createTime: '', |
| | | updateBy: null, |
| | | updateTime: null, |
| | | agentsName: null, |
| | | contactPerson: '', |
| | | contract: null, |
| | | customerAddress: '', |
| | | customerEmail: '', |
| | | customerPhone: '', |
| | | enterpriseName: '', |
| | | industry: '', |
| | | semanticWordList: null, |
| | | }, |
| | | customerName: '', |
| | | endDate: '', |
| | | rejectionReasons: null, |
| | | reviewStatus: '', |
| | | semanticWordList: null, |
| | | startDate: '', |
| | | sysOrgCode: '', |
| | | emergencyStatus: 3, // 默认正常状态 |
| | | month: null, // 合作周期(月) |
| | | }; |
| | | |
| | | // 响应式表单数据 |
| | | const formData = reactive({ ...initialData }); |
| | | |
| | | // 语义词列表 |
| | | const semanticWords = ref<any[]>([]); |
| | | |
| | | // 合同文件列表 |
| | | const contractFiles = ref<any[]>([]); |
| | | const attachmentFiles = ref<any[]>([]); |
| | | |
| | | // 文件预览展开状态(使用文件 uid 作为 key) |
| | | const filePreviewState = reactive<Record<string, boolean>>({}); |
| | | const txtContentMap = reactive<Record<string, string>>({}); |
| | | const txtLoadingMap = reactive<Record<string, boolean>>({}); |
| | | |
| | | // 加载客户数据 |
| | | const loadCustomerData = async () => { |
| | | const id = route.query.id as string; |
| | | if (id) { |
| | | try { |
| | | // 调用API根据id获取客户数据 |
| | | const result = await defHttp.get({ |
| | | url: '/contract/contract/queryById', |
| | | params: { id }, |
| | | }); |
| | | console.log('客户详情数据:', result); |
| | | if (result) { |
| | | const customerData = result; |
| | | // 填充表单数据 |
| | | Object.assign(formData, customerData); |
| | | // 确保 emergencyStatus 正确设置(1=紧急,3=正常) |
| | | const emergencyStatus = Number(customerData.emergencyStatus); |
| | | if (emergencyStatus === 1 || emergencyStatus === 3) { |
| | | formData.emergencyStatus = emergencyStatus; |
| | | } else { |
| | | formData.emergencyStatus = 3; // 默认正常状态 |
| | | } |
| | | |
| | | // 加载合同文件数据 |
| | | await loadContractFiles(id); |
| | | |
| | | // 加载语义词数据 |
| | | await loadSemanticWords(id); |
| | | } else { |
| | | message.error('加载客户详情数据失败'); |
| | | } |
| | | } catch (error) { |
| | | console.error('加载客户详情数据失败:', error); |
| | | message.error('加载客户详情数据失败'); |
| | | } |
| | | } else { |
| | | message.error('缺少客户ID参数'); |
| | | } |
| | | }; |
| | | |
| | | // 加载合同文件数据 |
| | | const loadContractFiles = async (id: string) => { |
| | | try { |
| | | const result = await defHttp.get({ |
| | | url: '/contract/contract/queryContractFileByMainId', |
| | | params: { id }, |
| | | }); |
| | | console.log('合同文件数据:', result); |
| | | // 这里可以根据返回的数据结构处理合同文件 |
| | | if (result && Array.isArray(result)) { |
| | | // 根据 fileType 区分合同文件和附件 |
| | | const contractFileList: any[] = []; |
| | | const attachmentFileList: any[] = []; |
| | | |
| | | result.forEach((file: any) => { |
| | | const fileItem = { |
| | | uid: file.id, |
| | | name: file.appendixFile || '文件', |
| | | status: 'done', |
| | | url: file.appendixFile, |
| | | }; |
| | | |
| | | if (file.fileType === '合同文件') { |
| | | contractFileList.push(fileItem); |
| | | } else if (file.fileType === '合同附件') { |
| | | attachmentFileList.push(fileItem); |
| | | } |
| | | }); |
| | | |
| | | contractFiles.value = contractFileList; |
| | | attachmentFiles.value = attachmentFileList; |
| | | } |
| | | } catch (error) { |
| | | console.error('加载合同文件失败:', error); |
| | | } |
| | | }; |
| | | // 加载语义词数据 |
| | | const loadSemanticWords = async (id: string) => { |
| | | try { |
| | | const result = await defHttp.get({ |
| | | url: '/contract/contract/querySemanticWordByMainId', |
| | | params: { id }, |
| | | }); |
| | | console.log('语义词数据:', result); |
| | | // 这里可以根据返回的数据结构处理语义词 |
| | | if (result && Array.isArray(result)) { |
| | | // 将语义词列表保存到响应式变量中 |
| | | semanticWords.value = result; |
| | | } |
| | | } catch (error) { |
| | | console.error('加载语义词失败:', error); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | loadCustomerData(); |
| | | }); |
| | | |
| | | // 工具方法:获取文件名(去掉 temp/) |
| | | const getFileName = (file: any) => { |
| | | const name = file.name || ''; |
| | | if (name.startsWith('temp/')) { |
| | | return name.substring(5); |
| | | } |
| | | return name; |
| | | }; |
| | | |
| | | // 工具方法:获取文件的完整访问 URL |
| | | const getFileUrl = (file: any) => { |
| | | if (!file.url) { |
| | | return ''; |
| | | } |
| | | return getFileAccessHttpUrl(file.url); |
| | | }; |
| | | |
| | | // 统一获取文件 key,避免重复逻辑 |
| | | const getFileKey = (file: any) => file?.uid || file?.id || file?.url || file?.name || ''; |
| | | |
| | | // 读取 TXT 文本内容用于预览 |
| | | const fetchTxtContent = async (file: any) => { |
| | | const fileKey = getFileKey(file); |
| | | if (!fileKey || txtLoadingMap[fileKey]) return; |
| | | |
| | | txtLoadingMap[fileKey] = true; |
| | | try { |
| | | const url = getFileUrl(file); |
| | | if (!url) throw new Error('TXT 文件地址不存在'); |
| | | const resp = await fetch(url); |
| | | if (!resp.ok) throw new Error(`请求失败:${resp.status}`); |
| | | const text = await resp.text(); |
| | | txtContentMap[fileKey] = text || '(文件为空)'; |
| | | } catch (error) { |
| | | console.error('TXT 文件加载失败', error); |
| | | txtContentMap[fileKey] = 'TXT 文件预览失败,请稍后重试'; |
| | | message.error('TXT 文件预览失败'); |
| | | } finally { |
| | | txtLoadingMap[fileKey] = false; |
| | | } |
| | | }; |
| | | |
| | | const isReviewFile = (file: any) => { |
| | | if (!file || !file.url) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.docx') || lowerName.endsWith('.xlsx') || lowerName.endsWith('.pdf') || lowerName.endsWith('.txt') ||lowerName.endsWith('.jpg') || lowerName.endsWith('.jpeg') || lowerName.endsWith('.png') || lowerName.endsWith('.gif') || lowerName.endsWith('.bmp') || lowerName.endsWith('.webp'); |
| | | }; |
| | | |
| | | // 判断是否为 DOCX 文件 |
| | | const isDocxFile = (file: any) => { |
| | | if (!file) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.docx'); |
| | | }; |
| | | |
| | | // 判断是否为 XLSX 文件 |
| | | const isXlsxFile = (file: any) => { |
| | | if (!file) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.xlsx'); |
| | | }; |
| | | |
| | | // 判断是否为 PDF 文件 |
| | | const isPdfFile = (file: any) => { |
| | | if (!file) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.pdf'); |
| | | }; |
| | | |
| | | // 判断是否为 TXT 文件 |
| | | const isTxtFile = (file: any) => { |
| | | if (!file) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.txt'); |
| | | }; |
| | | |
| | | // 判断是否为图片文件 |
| | | const isImageFile = (file: any) => { |
| | | if (!file) { |
| | | return false; |
| | | } |
| | | const fileName = file.name || file.url || ''; |
| | | const lowerName = fileName.toLowerCase(); |
| | | return lowerName.endsWith('.jpg') || lowerName.endsWith('.jpeg') || lowerName.endsWith('.png') || lowerName.endsWith('.gif') || lowerName.endsWith('.bmp') || lowerName.endsWith('.webp'); |
| | | }; |
| | | |
| | | const getTxtContent = (file: any) => { |
| | | const key = getFileKey(file); |
| | | return txtContentMap[key] || '加载中...'; |
| | | }; |
| | | |
| | | const isTxtLoading = (file: any) => { |
| | | const key = getFileKey(file); |
| | | return !!txtLoadingMap[key]; |
| | | }; |
| | | |
| | | // 切换文件预览状态 |
| | | const toggleFilePreview = (file: any) => { |
| | | const fileId = getFileKey(file); |
| | | if (!fileId) return; |
| | | |
| | | const nextState = !filePreviewState[fileId]; |
| | | filePreviewState[fileId] = nextState; |
| | | |
| | | // 打开 TXT 时加载内容 |
| | | if (nextState && isTxtFile(file) && !txtContentMap[fileId]) { |
| | | fetchTxtContent(file); |
| | | } |
| | | }; |
| | | |
| | | // 检查文件是否展开预览 |
| | | const isFilePreviewOpen = (file: any) => { |
| | | const fileId = getFileKey(file); |
| | | return filePreviewState[fileId] || false; |
| | | }; |
| | | |
| | | // 文件下载功能 |
| | | const handleDownload = (file: any) => { |
| | | if (!file.url) { |
| | | message.error('文件链接不存在'); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 创建一个隐藏的a标签进行下载 |
| | | const link = document.createElement('a'); |
| | | link.href = getFileAccessHttpUrl(file.url); |
| | | link.download = getFileName(file); |
| | | link.target = '_blank'; |
| | | link.style.display = 'none'; |
| | | |
| | | // 添加到DOM中 |
| | | document.body.appendChild(link); |
| | | |
| | | // 触发点击事件 |
| | | link.click(); |
| | | |
| | | // 清理DOM |
| | | document.body.removeChild(link); |
| | | } catch (error) { |
| | | console.error('文件下载失败:', error); |
| | | message.error('文件下载失败'); |
| | | } |
| | | }; |
| | | |
| | | // 返回功能 |
| | | const handleBack = () => { |
| | | router.back(); |
| | | }; |
| | | |
| | | // 格式化价格 |
| | | const formatPrice = (price: number) => { |
| | | if (!price) return '-'; |
| | | return price.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | }; |
| | | |
| | | // 格式化签约排名 |
| | | const formatRanking = (ranking: any) => { |
| | | if (ranking === 1 || ranking === 2 || ranking === 3) { |
| | | return ranking.toString(); |
| | | } else if (ranking === '保展现不保排名') { |
| | | return '保展现不保排名'; |
| | | } |
| | | return '-'; |
| | | }; |
| | | |
| | | return { |
| | | formData, |
| | | contractFiles, |
| | | attachmentFiles, |
| | | semanticWords, |
| | | handleDownload, |
| | | handleBack, |
| | | getFileName, |
| | | getFileUrl, |
| | | isReviewFile, |
| | | isDocxFile, |
| | | isXlsxFile, |
| | | isPdfFile, |
| | | isTxtFile, |
| | | isImageFile, |
| | | getTxtContent, |
| | | isTxtLoading, |
| | | toggleFilePreview, |
| | | isFilePreviewOpen, |
| | | formatPrice, |
| | | formatRanking, |
| | | }; |
| | | }, |
| | | }); |
| | | <script lang="ts" setup> |
| | | // 欢迎页面组件 |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .desc-wrap { |
| | | padding: 16px; |
| | | background-color: @component-background; |
| | | } |
| | | .welcome-container { |
| | | min-height: calc(100vh - 200px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #f0f2f5; |
| | | } |
| | | |
| | | .action-buttons { |
| | | position: fixed; |
| | | right: 24px; |
| | | bottom: 24px; |
| | | display: flex; |
| | | gap: 12px; |
| | | z-index: 1000; |
| | | .welcome-content { |
| | | text-align: center; |
| | | padding: 40px; |
| | | } |
| | | |
| | | .back-btn { |
| | | min-width: 80px; |
| | | height: 40px; |
| | | border-radius: 6px; |
| | | font-weight: 500; |
| | | background-color: #f5f5f5; |
| | | border-color: #d9d9d9; |
| | | color: #666; |
| | | .welcome-title { |
| | | font-size: 32px; |
| | | color: #333; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | &:hover { |
| | | background-color: #e6f7ff; |
| | | border-color: #40a9ff; |
| | | color: #40a9ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 文件下载链接样式 |
| | | a { |
| | | color: #1890ff; |
| | | text-decoration: none; |
| | | cursor: pointer; |
| | | transition: color 0.3s; |
| | | |
| | | &:hover { |
| | | color: #40a9ff; |
| | | text-decoration: underline; |
| | | } |
| | | |
| | | &:active { |
| | | color: #096dd9; |
| | | } |
| | | } |
| | | |
| | | .file-item { |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | } |
| | | |
| | | .file-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .preview-toggle { |
| | | margin-left: 8px; |
| | | color: #1890ff; |
| | | cursor: pointer; |
| | | font-size: 14px; |
| | | user-select: none; |
| | | |
| | | &:hover { |
| | | color: #40a9ff; |
| | | text-decoration: underline; |
| | | } |
| | | } |
| | | |
| | | .docx-preview { |
| | | margin-top: 12px; |
| | | border: 1px solid #e8e8e8; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | background: #fff; |
| | | } |
| | | |
| | | .preview-not-supported { |
| | | padding: 40px 20px; |
| | | text-align: center; |
| | | min-height: 200px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .code-area { |
| | | margin: 0; |
| | | padding: 12px; |
| | | background: #e2e8f0; |
| | | color: #0f172a; |
| | | border-radius: 4px; |
| | | font-size: 13px; |
| | | line-height: 1.6; |
| | | white-space: pre-wrap; |
| | | word-break: break-word; |
| | | min-height: 120px; |
| | | max-height: 600px; |
| | | overflow: auto; |
| | | } |
| | | .welcome-system-name { |
| | | font-size: 40px; |
| | | color: #1890ff; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| | |
| | | </a-card> |
| | | <!-- 右下角操作按钮 --> |
| | | <div class="action-buttons"> |
| | | <a-button type="danger" @click="handleStopService" class="stop-btn">停止服务</a-button> |
| | | <a-button type="default" @click="handleReset" class="reset-btn">重置</a-button> |
| | | <a-button type="primary" @click="handleSave" class="save-btn">再次提交</a-button> |
| | | </div> |
| | | |
| | | <!-- 停止服务确认弹窗 --> |
| | | <a-modal |
| | | v-model:visible="stopServiceModalVisible" |
| | | title="停止服务确认" |
| | | width="500px" |
| | | ok-text="确认停止" |
| | | cancel-text="取消" |
| | | @ok="confirmStopService" |
| | | centered |
| | | > |
| | | <div style="padding: 20px 0; text-align: center;"> |
| | | <p style="font-size: 16px; margin-bottom: 20px;">是否确认停止服务?</p> |
| | | <p style="color: #666; font-size: 14px;">停止服务后,相关服务将立即终止</p> |
| | | </div> |
| | | </a-modal> |
| | | </PageWrapper> |
| | | </template> |
| | | |
| | |
| | | |
| | | // 编辑状态控制 |
| | | const isEditing = ref(true); |
| | | |
| | | // 停止服务弹窗可见性 |
| | | const stopServiceModalVisible = ref(false); |
| | | |
| | | // 原始数据,用于重置 |
| | | const originalData = ref<any>({}); |
| | |
| | | } |
| | | }; |
| | | |
| | | // 处理停止服务按钮点击 |
| | | const handleStopService = () => { |
| | | stopServiceModalVisible.value = true; |
| | | }; |
| | | |
| | | // 确认停止服务 |
| | | const confirmStopService = async () => { |
| | | const id = route.query.id as string; |
| | | if (!id) { |
| | | message.error('缺少合同ID参数'); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 调用后端停止服务接口 |
| | | const result = await defHttp.post({ |
| | | url: '/contract/contract/dropService', |
| | | data: { |
| | | id, |
| | | isDropService: '是' |
| | | }, |
| | | }); |
| | | |
| | | if (result) { |
| | | message.success('服务已成功停止'); |
| | | // 重新加载客户数据以更新状态 |
| | | await loadCustomerData(); |
| | | } else { |
| | | message.error('停止服务失败'); |
| | | } |
| | | } catch (error) { |
| | | console.error('停止服务失败:', error); |
| | | message.error('停止服务失败'); |
| | | } finally { |
| | | stopServiceModalVisible.value = false; |
| | | } |
| | | }; |
| | | |
| | | return { |
| | | isEditing, |
| | | formData, |
| | |
| | | displayContractFiles, |
| | | displayAttachmentFiles, |
| | | semanticWords, |
| | | stopServiceModalVisible, |
| | | beforeUpload, |
| | | customUpload, |
| | | handleFileChange, |
| | | handleReset, |
| | | handleSave, |
| | | handleStopService, |
| | | confirmStopService, |
| | | getFileName, |
| | | resolveFileUrl, |
| | | downloadFile, |
| | |
| | | border-color: #40a9ff; |
| | | } |
| | | } |
| | | |
| | | .stop-btn { |
| | | min-width: 80px; |
| | | height: 40px; |
| | | border-radius: 6px; |
| | | font-weight: 500; |
| | | background-color: #fff1f0; |
| | | border-color: #ffccc7; |
| | | color: #cf1322; |
| | | |
| | | &:hover { |
| | | background-color: #fff2f0; |
| | | border-color: #ff7875; |
| | | color: #ff4d4f; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .file-row { |
| | |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 6, |
| | | md: 6, |
| | | lg: 6, |
| | | xl: 6, |
| | | xxl: 6, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | |
| | | showActionColumn: false, |
| | | canResize: true, |
| | | formConfig: { |
| | | labelWidth: 100, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: false, |
| | | showActionButtonGroup: true, |
| | | ...formLayoutConfig, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 6, |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 6, |
| | | md: 6, |
| | | lg: 6, |
| | | xl: 6, |
| | | xxl: 6, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | |
| | | showActionColumn: false, |
| | | canResize: true, |
| | | formConfig: { |
| | | labelWidth: 100, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: false, |
| | | showActionButtonGroup: true, |
| | | ...formLayoutConfig, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 6, |
| | | md: 6, |
| | | lg: 6, |
| | | xl: 6, |
| | | xxl: 6, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 6, |
| | | style: { textAlign: 'left' }, |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | </style> |
| | | |
| | | |
| | |
| | | { |
| | | label: "语义词", |
| | | field: 'word', |
| | | component: 'JInput', |
| | | componentProps: { |
| | | style: { width: '220px' } // 增加宽度,确保文字显示完整 |
| | | }, |
| | | component: 'Input', |
| | | //colProps: {span: 6}, |
| | | }, |
| | | { |
| | | label: "露出词", |
| | | field: 'outWord', |
| | | component: 'JInput', |
| | | componentProps: { |
| | | style: { width: '220px' } // 增加宽度,确保文字显示完整 |
| | | }, |
| | | component: 'Input', |
| | | //colProps: {span: 6}, |
| | | }, |
| | | { |
| | | label: "状态", |
| | | field: 'status', |
| | | component: 'JDictSelectTag', |
| | | component: 'Select', |
| | | componentProps:{ |
| | | // 仅保留以下四个状态作为查询选项 |
| | | options: [ |
| | |
| | | { label: '已驳回', value: '2' }, |
| | | { label: '已上词', value: '5' }, |
| | | ], |
| | | style: { width: '180px' } // 增加宽度,确保文字显示完整 |
| | | }, |
| | | //colProps: {span: 6}, |
| | | }, |
| | |
| | | columns, |
| | | showActionColumn: false, |
| | | canResize: true, |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, // 启用紧凑模式 |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 6, |
| | | md: 6, |
| | | lg: 6, |
| | | xl: 6, |
| | | xxl: 6, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 8, |
| | | style: { textAlign: 'left', marginLeft: '0px' }, |
| | | }, |
| | | }, |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | | beforeFetch: (params) => { |
| | | if (params && fieldPickers) { |
| | | for (let key in fieldPickers) { |
| | |
| | | gap: 20px; |
| | | } |
| | | |
| | | /* 更具体的样式选择器,确保搜索表单更靠左 */ |
| | | :deep(.basic-table) { |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.ant-row) { |
| | | margin-left: 0 !important; |
| | | width: 100%; |
| | | justify-content: flex-start !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item) { |
| | | margin-right: 16px !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item-label) { |
| | | padding-right: 8px !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item-control) { |
| | | min-width: 180px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | { |
| | | label: "语义词", |
| | | field: 'word', |
| | | component: 'JInput', |
| | | componentProps: { |
| | | style: { width: '200px' } // 适当宽度,确保文字显示完整 |
| | | }, |
| | | component: 'Input', |
| | | //colProps: {span: 6}, |
| | | }, |
| | | { |
| | | label: "状态", |
| | | field: 'status', |
| | | component: 'JDictSelectTag', |
| | | component: 'Select', |
| | | componentProps:{ |
| | | options: [ |
| | | { label: '待分配', value: '1' }, |
| | | { label: '已分配', value: '7' }, |
| | | ], |
| | | style: { width: '200px' } // 适当宽度,确保文字显示完整 |
| | | }, |
| | | }, |
| | | // { |
| | |
| | | api: list, |
| | | columns, |
| | | canResize: true, |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, // 启用紧凑模式 |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 6, |
| | | md: 6, |
| | | lg: 6, |
| | | xl: 6, |
| | | xxl: 6, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | | formConfig: { |
| | | //labelWidth: 120, |
| | | schemas: searchFormSchema, |
| | | autoSubmitOnEnter: true, |
| | | showAdvancedButton: true, |
| | | autoAdvancedCol: 4, |
| | | fieldMapToNumber: [], |
| | | fieldMapToTime: [], |
| | | compact: true, |
| | | baseColProps: { |
| | | xs: 24, |
| | | sm: 4, |
| | | md: 4, |
| | | lg: 4, |
| | | xl: 4, |
| | | xxl: 4, |
| | | }, |
| | | rowProps: { |
| | | gutter: 8, |
| | | }, |
| | | actionColOptions: { |
| | | span: 8, |
| | | style: { textAlign: 'left' }, |
| | | }, |
| | | }, |
| | | actionColumn: { |
| | | width: 150, |
| | | fixed: 'right', |
| | |
| | | } |
| | | } |
| | | |
| | | /* 更具体的样式选择器,确保搜索表单更靠左 */ |
| | | :deep(.basic-table) { |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 确保搜索表单靠左对齐 */ |
| | | :deep(.basic-table-search) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.ant-row) { |
| | | margin-left: 0 !important; |
| | | width: 100%; |
| | | justify-content: flex-start !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item) { |
| | | margin-right: 16px !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item-label) { |
| | | padding-right: 8px !important; |
| | | } |
| | | |
| | | :deep(.ant-form-item-control) { |
| | | min-width: 180px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | :deep(.ant-input-number) { |
| | | width: 100%; |
| | | } |
| | | |
| | | /* 确保搜索表单完全靠左对齐,消除所有左边空隙 */ |
| | | :deep(.basic-table-search) { |
| | | padding-left: 0 !important; |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单容器的左边空隙 */ |
| | | :deep(.ant-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除行容器的左边空隙 */ |
| | | :deep(.ant-form-item-row) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除表单项的左边空隙 */ |
| | | :deep(.ant-form-item) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除BasicTable容器的左边空隙 */ |
| | | :deep(.ant-card) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除BasicTable头部的左边空隙 */ |
| | | :deep(.ant-card-head) { |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除BasicTable内容的左边空隙 */ |
| | | :deep(.ant-card-body) { |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除搜索表单外层容器的左边空隙 */ |
| | | :deep(.ant-row) { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | /* 消除搜索表单列容器的左边空隙 */ |
| | | :deep(.ant-col) { |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 直接针对搜索表单区域的容器 */ |
| | | :deep(.arco-form) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 直接针对搜索表单区域 */ |
| | | .semantic-word-search { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 针对整个表格容器的最外层 */ |
| | | :deep(.arco-table-container) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 针对搜索表单的直接父容器 */ |
| | | :deep(.arco-space) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 针对搜索表单的直接容器 */ |
| | | :deep(.arco-form-container) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 直接针对整个页面容器 */ |
| | | .table-container { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | |
| | | /* 针对页面根容器 */ |
| | | :deep(.app-container) { |
| | | margin-left: 0 !important; |
| | | padding-left: 0 !important; |
| | | } |
| | | </style> |