1
src/assets/review-tools/back-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M0.721558 9.05713L9.90522 4.03418L9.89694 13.9675L0.721558 9.05713Z" fill="#EB903D" ></path><path d="M9.91747 13.9997L9.88676 13.9833L0.680664 9.05657L9.92577 4L9.92577 4.03345L9.91747 13.9997ZM0.762483 9.05619L9.87645 13.9337L9.88472 4.06683L0.762483 9.05619Z" fill="#EB903D" ></path><path d="M18.6807 12.7514C18.6807 12.7514 18.484 11.9212 17.8363 11.4146C17.3395 11.026 17.0303 10.8069 16.3011 10.6854C14.4034 10.3691 5.74231 10.6864 5.74231 10.6864L5.74231 7.8C5.74231 7.8 11.4957 7.72844 13.3842 8.04199C13.9446 8.13501 14.9362 8.25487 15.6103 8.5281C16.337 8.82274 17.0461 9.24959 17.4141 9.59154C18.6682 10.7567 18.6807 12.7514 18.6807 12.7514Z" fill="#EB903D" ></path></svg>
|
||||
|
After Width: | Height: | Size: 835 B |
1
src/assets/review-tools/back-default.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M0.721558 9.05713L9.90522 4.03418L9.89694 13.9675L0.721558 9.05713Z" fill="#C4C6C8" ></path><path d="M9.91747 13.9997L9.88676 13.9833L0.680664 9.05657L9.92577 4L9.92577 4.03345L9.91747 13.9997ZM0.762483 9.05619L9.87645 13.9337L9.88472 4.06683L0.762483 9.05619Z" fill="#C4C6C8" ></path><path d="M18.6807 12.7514C18.6807 12.7514 18.484 11.9212 17.8363 11.4146C17.3395 11.026 17.0303 10.8069 16.3011 10.6854C14.4034 10.3691 5.74231 10.6864 5.74231 10.6864L5.74231 7.8C5.74231 7.8 11.4957 7.72844 13.3842 8.04199C13.9446 8.13501 14.9362 8.25487 15.6103 8.5281C16.337 8.82274 17.0461 9.24959 17.4141 9.59154C18.6682 10.7567 18.6807 12.7514 18.6807 12.7514Z" fill="#C4C6C8" ></path></svg>
|
||||
|
After Width: | Height: | Size: 835 B |
1
src/assets/review-tools/pattern-active.svg
Normal file
|
After Width: | Height: | Size: 38 KiB |
1
src/assets/review-tools/pattern-default.svg
Normal file
|
After Width: | Height: | Size: 38 KiB |
1
src/assets/review-tools/response-active.svg
Normal file
|
After Width: | Height: | Size: 42 KiB |
1
src/assets/review-tools/response-default.svg
Normal file
|
After Width: | Height: | Size: 42 KiB |
1
src/assets/review-tools/reupload-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M3 16.7985C3 16.4214 3.36056 16.1462 3.7611 16.1462L16.2389 16.1462C16.6395 16.1462 17 16.4214 17 16.7985L17 18.2254C17 18.6025 16.6394 19 16.2389 19L3.7611 19C3.36052 19 3 18.6025 3 18.2254L3 16.7985ZM3 9.012L9.98996 2L17 9.012L3 9.012ZM7.18592 13.2925L7.18592 6.26031C7.18592 5.82197 7.32622 5.48571 7.60657 5.23089C7.8869 4.97601 8.21755 4.8537 8.58793 4.8537L11.4121 4.8537C11.7824 4.8537 12.113 4.97601 12.3934 5.23089C12.6738 5.48571 12.814 5.82197 12.814 6.26031L12.814 13.2925C12.814 13.6697 12.6738 14.006 12.3934 14.2913C12.113 14.5766 11.7825 14.7194 11.4121 14.7194L8.58793 14.7194C8.21755 14.7194 7.88692 14.5766 7.60657 14.2913C7.3262 14.006 7.18592 13.6697 7.18592 13.2925Z" fill="#EB903D" ></path></svg>
|
||||
|
After Width: | Height: | Size: 868 B |
1
src/assets/review-tools/reupload-default.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M3 16.7985C3 16.4214 3.36056 16.1462 3.7611 16.1462L16.2389 16.1462C16.6395 16.1462 17 16.4214 17 16.7985L17 18.2254C17 18.6025 16.6394 19 16.2389 19L3.7611 19C3.36052 19 3 18.6025 3 18.2254L3 16.7985ZM3 9.012L9.98996 2L17 9.012L3 9.012ZM7.18592 13.2925L7.18592 6.26031C7.18592 5.82197 7.32622 5.48571 7.60657 5.23089C7.8869 4.97601 8.21755 4.8537 8.58793 4.8537L11.4121 4.8537C11.7824 4.8537 12.113 4.97601 12.3934 5.23089C12.6738 5.48571 12.814 5.82197 12.814 6.26031L12.814 13.2925C12.814 13.6697 12.6738 14.006 12.3934 14.2913C12.113 14.5766 11.7825 14.7194 11.4121 14.7194L8.58793 14.7194C8.21755 14.7194 7.88692 14.5766 7.60657 14.2913C7.3262 14.006 7.18592 13.6697 7.18592 13.2925Z" fill="#C4C6C8" ></path></svg>
|
||||
|
After Width: | Height: | Size: 868 B |
1
src/assets/review-tools/style-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M14.0176 12.9204C12.3702 12.9204 11.0352 14.2811 11.0352 15.9602C11.0352 17.6393 12.3702 19 14.0176 19C15.665 19 17 17.6393 17 15.9602C17 14.2811 15.665 12.9204 14.0176 12.9204ZM15.4847 15.2143C15.0171 15.8565 14.5495 16.5005 14.0818 17.1426C13.9837 17.2773 13.757 17.3737 13.5964 17.2609C13.2591 17.0244 12.9235 16.7879 12.588 16.5514C12.4345 16.4423 12.3542 16.2694 12.4612 16.1021C12.5505 15.962 12.7932 15.8747 12.9467 15.9838C13.2823 16.2203 13.691 16.5078 13.691 16.5078C14.0854 15.9656 14.4781 15.4254 14.8725 14.8833C15.126 14.534 15.7417 14.8632 15.4847 15.2143ZM14.0176 12.0727C14.4192 12.0727 14.8065 12.1345 15.1724 12.2491L15.1724 2L3 2L3 16.606L10.1571 16.606C10.1321 16.4241 10.1178 16.2367 10.1178 16.0475C10.1178 13.8518 11.8633 12.0727 14.0176 12.0727ZM5.78251 5.00524L12.3881 5.00524C12.6522 5.00524 12.8682 5.22354 12.8682 5.4946C12.8682 5.76383 12.654 5.98395 12.3881 5.98395L5.78251 5.98395C5.51836 5.98395 5.3024 5.76565 5.3024 5.4946C5.3024 5.22354 5.51836 5.00524 5.78251 5.00524ZM5.78251 8.11054L12.3881 8.11054C12.6522 8.11054 12.8682 8.32884 12.8682 8.59989C12.8682 8.87095 12.654 9.08925 12.3881 9.08925L5.78251 9.08925C5.51836 9.08925 5.3024 8.87095 5.3024 8.59989C5.3024 8.32884 5.51836 8.11054 5.78251 8.11054ZM9.00586 12.1945L5.78251 12.1945C5.51836 12.1945 5.3024 11.9762 5.3024 11.7052C5.3024 11.4341 5.51657 11.2158 5.78251 11.2158L9.00408 11.2158C9.26823 11.2158 9.48419 11.4341 9.48419 11.7052C9.48419 11.9762 9.27002 12.1945 9.00586 12.1945Z" fill="#EB903D" ></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
src/assets/review-tools/style-default.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M14.0176 12.9204C12.3702 12.9204 11.0352 14.2811 11.0352 15.9602C11.0352 17.6393 12.3702 19 14.0176 19C15.665 19 17 17.6393 17 15.9602C17 14.2811 15.665 12.9204 14.0176 12.9204ZM15.4847 15.2143C15.0171 15.8565 14.5495 16.5005 14.0818 17.1426C13.9837 17.2773 13.757 17.3737 13.5964 17.2609C13.2591 17.0244 12.9235 16.7879 12.588 16.5514C12.4345 16.4423 12.3542 16.2694 12.4612 16.1021C12.5505 15.962 12.7932 15.8747 12.9467 15.9838C13.2823 16.2203 13.691 16.5078 13.691 16.5078C14.0854 15.9656 14.4781 15.4254 14.8725 14.8833C15.126 14.534 15.7417 14.8632 15.4847 15.2143ZM14.0176 12.0727C14.4192 12.0727 14.8065 12.1345 15.1724 12.2491L15.1724 2L3 2L3 16.606L10.1571 16.606C10.1321 16.4241 10.1178 16.2367 10.1178 16.0475C10.1178 13.8518 11.8633 12.0727 14.0176 12.0727ZM5.78251 5.00524L12.3881 5.00524C12.6522 5.00524 12.8682 5.22354 12.8682 5.4946C12.8682 5.76383 12.654 5.98395 12.3881 5.98395L5.78251 5.98395C5.51836 5.98395 5.3024 5.76565 5.3024 5.4946C5.3024 5.22354 5.51836 5.00524 5.78251 5.00524ZM5.78251 8.11054L12.3881 8.11054C12.6522 8.11054 12.8682 8.32884 12.8682 8.59989C12.8682 8.87095 12.654 9.08925 12.3881 9.08925L5.78251 9.08925C5.51836 9.08925 5.3024 8.87095 5.3024 8.59989C5.3024 8.32884 5.51836 8.11054 5.78251 8.11054ZM9.00586 12.1945L5.78251 12.1945C5.51836 12.1945 5.3024 11.9762 5.3024 11.7052C5.3024 11.4341 5.51657 11.2158 5.78251 11.2158L9.00408 11.2158C9.26823 11.2158 9.48419 11.4341 9.48419 11.7052C9.48419 11.9762 9.27002 12.1945 9.00586 12.1945Z" fill="#C4C6C8" ></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -2,14 +2,16 @@
|
||||
import { computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
|
||||
import type { LayoutMode } from '@sa/materials';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { initWebSocket } from '@/utils/websocket';
|
||||
import { initSSE } from '@/utils/sse';
|
||||
import GlobalHeader from '../modules/global-header/index.vue';
|
||||
import GlobalSider from '../modules/global-sider/index.vue';
|
||||
import GlobalTab from '../modules/global-tab/index.vue';
|
||||
import GlobalContent from '../modules/global-content/index.vue';
|
||||
import MessageButton from '../modules/global-header/components/message-button.vue';
|
||||
import UserAvatar from '../modules/global-header/components/user-avatar.vue';
|
||||
// import GlobalFooter from '../modules/global-footer/index.vue';
|
||||
import ThemeDrawer from '../modules/theme-drawer/index.vue';
|
||||
import { setupMixMenuContext } from '../context';
|
||||
@@ -20,6 +22,7 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const route = useRoute();
|
||||
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
|
||||
|
||||
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
|
||||
@@ -30,41 +33,6 @@ const layoutMode = computed(() => {
|
||||
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
|
||||
});
|
||||
|
||||
const headerProps = computed(() => {
|
||||
// const { mode, reverseHorizontalMix } = themeStore.layout;
|
||||
const { mode } = themeStore.layout;
|
||||
|
||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||
vertical: {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: true
|
||||
},
|
||||
'vertical-mix': {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: false
|
||||
},
|
||||
horizontal: {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
},
|
||||
'horizontal-mix': {
|
||||
showLogo: false,
|
||||
showMenu: true,
|
||||
showMenuToggler: true
|
||||
}
|
||||
// 'horizontal-mix': {
|
||||
// showLogo: true,
|
||||
// showMenu: true,
|
||||
// showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
|
||||
// }
|
||||
};
|
||||
|
||||
return headerPropsConfig[mode];
|
||||
});
|
||||
|
||||
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
@@ -75,6 +43,8 @@ const siderWidth = computed(() => getSiderWidth());
|
||||
|
||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
||||
|
||||
const floatingActionsVisible = computed(() => !route.path.startsWith('/smart-proposal/document-edit'));
|
||||
|
||||
function getSiderWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
// const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
@@ -129,7 +99,7 @@ onMounted(() => {
|
||||
:is-mobile="appStore.isMobile"
|
||||
:full-content="appStore.fullContent"
|
||||
:fixed-top="themeStore.fixedHeaderAndTab"
|
||||
:header-height="themeStore.header.height"
|
||||
:header-height="0"
|
||||
:tab-visible="themeStore.tab.visible"
|
||||
:tab-height="themeStore.tab.height"
|
||||
:content-class="appStore.contentXScrollable ? 'overflow-x-hidden' : ''"
|
||||
@@ -141,9 +111,6 @@ onMounted(() => {
|
||||
:fixed-footer="themeStore.footer.fixed"
|
||||
:right-footer="themeStore.footer.right"
|
||||
>
|
||||
<template #header>
|
||||
<GlobalHeader v-bind="headerProps" />
|
||||
</template>
|
||||
<template #tab>
|
||||
<GlobalTab />
|
||||
</template>
|
||||
@@ -152,6 +119,10 @@ onMounted(() => {
|
||||
</template>
|
||||
<GlobalMenu />
|
||||
<GlobalContent />
|
||||
<div v-if="floatingActionsVisible" class="global-floating-actions">
|
||||
<MessageButton />
|
||||
<UserAvatar />
|
||||
</div>
|
||||
<ThemeDrawer />
|
||||
<!--
|
||||
<template #footer>
|
||||
@@ -165,4 +136,27 @@ onMounted(() => {
|
||||
#__SCROLL_EL_ID__ {
|
||||
@include scrollbar();
|
||||
}
|
||||
|
||||
.global-floating-actions {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 28px;
|
||||
z-index: 120;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
color: #20222a;
|
||||
}
|
||||
|
||||
.global-floating-actions .n-button {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.global-floating-actions {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -251,6 +251,7 @@ const local: App.I18n.Schema = {
|
||||
exception_500: '500',
|
||||
'ai-assistant': 'AI Assistant',
|
||||
'ai-assistant_ai-read': 'AI Read',
|
||||
'ai-credit': 'AI Credit',
|
||||
'smart-proposal': 'Smart Proposal',
|
||||
'smart-proposal_tech-proposal': 'Smart Proposal',
|
||||
'smart-proposal_tech-proposal_document-edit': '',
|
||||
|
||||
@@ -251,6 +251,7 @@ const local: App.I18n.Schema = {
|
||||
exception_500: '500',
|
||||
'ai-assistant': 'AI助手',
|
||||
'ai-assistant_ai-read': 'AI读标',
|
||||
'ai-credit': 'AI资信',
|
||||
'smart-proposal': '智慧标书',
|
||||
'smart-proposal_tech-proposal': '智慧标书',
|
||||
'smart-proposal_tech-proposal_document-edit': '',
|
||||
|
||||
@@ -25,6 +25,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
"social-callback": () => import("@/views/_builtin/social-callback/index.vue"),
|
||||
"user-center": () => import("@/views/_builtin/user-center/index.vue"),
|
||||
"ai-assistant_ai-read": () => import("@/views/ai-assistant/ai-read/index.vue"),
|
||||
"ai-credit": () => import("@/views/ai-credit/index.vue"),
|
||||
"ai-review_tech-proposal-redesign_content-check": () => import("@/views/ai-review/tech-proposal-redesign/content-check/index.vue"),
|
||||
"ai-review_tech-proposal-redesign": () => import("@/views/ai-review/tech-proposal-redesign/index.vue"),
|
||||
"ai-review_tech-proposal-redesign_regular-analysis": () => import("@/views/ai-review/tech-proposal-redesign/regular-analysis/index.vue"),
|
||||
|
||||
@@ -61,6 +61,15 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'ai-credit',
|
||||
path: '/ai-credit',
|
||||
component: 'layout.base$view.ai-credit',
|
||||
meta: {
|
||||
title: 'ai-credit',
|
||||
i18nKey: 'route.ai-credit'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ai-review',
|
||||
path: '/ai-review',
|
||||
|
||||
@@ -172,6 +172,7 @@ const routeMap: RouteMap = {
|
||||
"500": "/500",
|
||||
"ai-assistant": "/ai-assistant",
|
||||
"ai-assistant_ai-read": "/ai-assistant/ai-read",
|
||||
"ai-credit": "/ai-credit",
|
||||
"ai-review": "/ai-review",
|
||||
"ai-review_tech-proposal": "/ai-review/tech-proposal",
|
||||
"ai-review_tech-proposal_modules": "/ai-review/tech-proposal/modules",
|
||||
|
||||
3
src/typings/elegant-router.d.ts
vendored
@@ -26,6 +26,7 @@ declare module "@elegant-router/types" {
|
||||
"500": "/500";
|
||||
"ai-assistant": "/ai-assistant";
|
||||
"ai-assistant_ai-read": "/ai-assistant/ai-read";
|
||||
"ai-credit": "/ai-credit";
|
||||
"ai-review": "/ai-review";
|
||||
"ai-review_tech-proposal": "/ai-review/tech-proposal";
|
||||
"ai-review_tech-proposal_modules": "/ai-review/tech-proposal/modules";
|
||||
@@ -141,6 +142,7 @@ declare module "@elegant-router/types" {
|
||||
| "404"
|
||||
| "500"
|
||||
| "ai-assistant"
|
||||
| "ai-credit"
|
||||
| "ai-review"
|
||||
| "demo"
|
||||
| "home"
|
||||
@@ -184,6 +186,7 @@ declare module "@elegant-router/types" {
|
||||
| "social-callback"
|
||||
| "user-center"
|
||||
| "ai-assistant_ai-read"
|
||||
| "ai-credit"
|
||||
| "ai-review_tech-proposal-redesign_content-check"
|
||||
| "ai-review_tech-proposal-redesign"
|
||||
| "ai-review_tech-proposal-redesign_regular-analysis"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
import { deleteConversation, getConversationList, updateConversation } from '@/service/api/conversation/index';
|
||||
import { fetchBatchDeleteOss } from '@/service/api/system/oss';
|
||||
@@ -10,6 +10,7 @@ import AnalysisResult from './module/analysis-result.vue';
|
||||
// import AnalysisSummary from './module/analysis-summary.vue';
|
||||
|
||||
const analysisVisible = ref(false);
|
||||
const isAiReadButtonVisible = ref(false);
|
||||
const statusVisible = ref(false);
|
||||
const analysisStatus = ref(true);
|
||||
const resultVisible = ref(false);
|
||||
@@ -162,16 +163,31 @@ watch(fileList, value => {
|
||||
selectedFile.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
isAiReadButtonVisible.value = true;
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
isAiReadButtonVisible.value = true;
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
isAiReadButtonVisible.value = false;
|
||||
historyVisible.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical :size="[0, 0]" class="contain pos-relative overflow-hidden">
|
||||
<div class="watermark" aria-hidden="true"></div>
|
||||
<div class="contain-wrapper">
|
||||
<button class="history-button" type="button" @click="openHistoryList">
|
||||
<span class="history-clock" aria-hidden="true">◷</span>
|
||||
<span class="history-text">历史记录</span>
|
||||
</button>
|
||||
<Teleport to="body">
|
||||
<button v-if="isAiReadButtonVisible" class="ai-read-history-button" type="button" @click="openHistoryList">
|
||||
<span class="history-clock" aria-hidden="true">◷</span>
|
||||
<span class="history-text">历史记录</span>
|
||||
</button>
|
||||
</Teleport>
|
||||
|
||||
<section class="hero-panel">
|
||||
<div class="hero-copy">
|
||||
@@ -296,10 +312,10 @@ watch(fileList, value => {
|
||||
}
|
||||
|
||||
.history-button {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 26px;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
left: calc(var(--soy-sider-width, 258px) + 20px);
|
||||
top: calc((var(--soy-header-height, 70px) - 38px) / 2);
|
||||
z-index: 102;
|
||||
width: 108px;
|
||||
height: 38px;
|
||||
border: 1px solid #d6dce8;
|
||||
@@ -567,6 +583,50 @@ watch(fileList, value => {
|
||||
}
|
||||
}
|
||||
|
||||
:global(.ai-read-history-button) {
|
||||
position: fixed;
|
||||
left: calc(var(--soy-sider-width, 258px) + 20px);
|
||||
top: calc((var(--soy-header-height, 70px) - 38px) / 2);
|
||||
z-index: 102;
|
||||
width: 108px;
|
||||
height: 38px;
|
||||
border: 1px solid #d6dce8;
|
||||
border-radius: 7px;
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 20px rgba(31, 41, 55, 0.06);
|
||||
color: #20242d;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:global(.ai-read-history-button::before),
|
||||
:global(.ai-read-history-button::after) {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
:global(.ai-read-history-button svg),
|
||||
:global(.ai-read-history-button .svg-icon),
|
||||
:global(.ai-read-history-button .iconify) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:global(.ai-read-history-button .history-clock) {
|
||||
color: #20242d;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:global(.ai-read-history-button .history-text) {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:global(.history-overlay) {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -758,6 +818,10 @@ watch(fileList, value => {
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
:global(.ai-read-history-button) {
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.contain {
|
||||
.watermark {
|
||||
left: 0;
|
||||
@@ -779,6 +843,10 @@ watch(fileList, value => {
|
||||
.history-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.history-button {
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,6 +42,7 @@ const currentTab = ref('progress');
|
||||
const currentPage = ref(1);
|
||||
const totalPage = ref(10);
|
||||
const docScale = ref(1);
|
||||
const isPanelExpanded = ref(false);
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
@@ -74,6 +75,10 @@ const updateTab = (value: string) => {
|
||||
currentTab.value = value;
|
||||
};
|
||||
|
||||
const togglePanelExpanded = () => {
|
||||
isPanelExpanded.value = !isPanelExpanded.value;
|
||||
};
|
||||
|
||||
const zoomOut = () => {
|
||||
docScale.value = Math.max(0.5, Number((docScale.value - 0.1).toFixed(1)));
|
||||
};
|
||||
@@ -407,7 +412,16 @@ const handleSearch = async (payload: any) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="analysis-panel">
|
||||
<aside class="analysis-panel" :class="{ 'is-expanded': isPanelExpanded }">
|
||||
<button
|
||||
class="panel-expand-handle"
|
||||
type="button"
|
||||
:aria-label="isPanelExpanded ? '收起解读面板' : '向左展开解读面板'"
|
||||
@click="togglePanelExpanded"
|
||||
>
|
||||
<span>{{ isPanelExpanded ? '收起' : '展开' }}</span>
|
||||
<i>{{ isPanelExpanded ? '›' : '‹' }}</i>
|
||||
</button>
|
||||
<nav class="analysis-tabs" aria-label="解析面板">
|
||||
<button
|
||||
class="analysis-tab"
|
||||
@@ -684,51 +698,131 @@ const handleSearch = async (payload: any) => {
|
||||
margin: 18px 28px 18px 16px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
box-shadow: 0 12px 32px rgba(17, 24, 39, 0.06);
|
||||
transition: width 0.24s ease, box-shadow 0.24s ease, transform 0.24s ease;
|
||||
|
||||
&.is-expanded {
|
||||
position: fixed;
|
||||
top: 18px;
|
||||
right: 28px;
|
||||
bottom: 18px;
|
||||
z-index: 8;
|
||||
width: min(980px, calc(100vw - 124px));
|
||||
height: auto;
|
||||
margin: 0;
|
||||
box-shadow: 0 18px 48px rgba(17, 24, 39, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
.panel-expand-handle {
|
||||
position: absolute;
|
||||
left: -38px;
|
||||
top: 96px;
|
||||
z-index: 4;
|
||||
width: 38px;
|
||||
min-height: 112px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 1px solid #e6ebf2;
|
||||
border-right: 0;
|
||||
border-radius: 12px 0 0 12px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
color: #ff7b19;
|
||||
box-shadow: -8px 10px 22px rgba(17, 24, 39, 0.08);
|
||||
cursor: pointer;
|
||||
transition: background 0.16s ease, color 0.16s ease, transform 0.16s ease;
|
||||
|
||||
span {
|
||||
writing-mode: vertical-rl;
|
||||
letter-spacing: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: linear-gradient(180deg, #ff9a34 0%, #ff7b19 100%);
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
height: 62px;
|
||||
padding: 14px 14px 0;
|
||||
border-bottom: 1px solid #ececec;
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 50px;
|
||||
margin: 12px 22px 0;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #e3e8f1;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(180deg, #fbfdff 0%, #f5f8fc 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.88), 0 8px 18px rgba(31, 45, 61, 0.06);
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.analysis-tab {
|
||||
position: relative;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: #111;
|
||||
font-size: 14px;
|
||||
color: #344155;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
padding: 0 4px;
|
||||
padding: 0 10px;
|
||||
transition: color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
background: #b8c2d1;
|
||||
transition: background 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #ff7b19;
|
||||
color: #182235;
|
||||
background: #fff;
|
||||
box-shadow: 0 6px 16px rgba(30, 41, 59, 0.12), inset 0 0 0 1px rgba(255, 255, 255, 0.86);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
right: 14px;
|
||||
bottom: -1px;
|
||||
height: 2px;
|
||||
&::before {
|
||||
background: #ff8a1f;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
color: #1d293d;
|
||||
background: rgba(255, 255, 255, 0.52);
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-panel-content {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.analysis-tab-panel {
|
||||
@@ -737,7 +831,7 @@ const handleSearch = async (payload: any) => {
|
||||
min-height: 0;
|
||||
padding: 13px 18px;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-x: auto;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
@@ -753,12 +847,30 @@ const handleSearch = async (payload: any) => {
|
||||
}
|
||||
|
||||
:deep(.mc-markdown-render) {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
:deep(.mc-markdown-render::-webkit-scrollbar) {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
:deep(.mc-markdown-render::-webkit-scrollbar-thumb) {
|
||||
border-radius: 999px;
|
||||
background: #b8c0cc;
|
||||
}
|
||||
|
||||
:deep(.mc-markdown-render::-webkit-scrollbar-track) {
|
||||
border-radius: 999px;
|
||||
background: #f0f3f7;
|
||||
}
|
||||
|
||||
:deep(.mc-markdown-render table) {
|
||||
width: 100%;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
max-width: none;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
@@ -775,7 +887,7 @@ const handleSearch = async (payload: any) => {
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.analysis-view {
|
||||
grid-template-columns: 64px minmax(0, 1fr) 390px;
|
||||
grid-template-columns: 64px minmax(0, 1fr) 480px;
|
||||
}
|
||||
|
||||
.analysis-back {
|
||||
@@ -783,7 +895,7 @@ const handleSearch = async (payload: any) => {
|
||||
}
|
||||
|
||||
.document-page {
|
||||
width: min(720px, calc(100vw - 520px));
|
||||
width: min(720px, calc(100vw - 610px));
|
||||
}
|
||||
|
||||
.analysis-tab {
|
||||
@@ -793,6 +905,11 @@ const handleSearch = async (payload: any) => {
|
||||
.analysis-panel {
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
|
||||
&.is-expanded {
|
||||
right: 12px;
|
||||
width: min(900px, calc(100vw - 88px));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,9 +923,26 @@ const handleSearch = async (payload: any) => {
|
||||
right: 12px;
|
||||
top: 76px;
|
||||
bottom: 12px;
|
||||
width: min(420px, calc(100vw - 84px));
|
||||
width: min(560px, calc(100vw - 84px));
|
||||
height: auto;
|
||||
margin: 0;
|
||||
|
||||
&.is-expanded {
|
||||
top: 76px;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
width: min(760px, calc(100vw - 72px));
|
||||
}
|
||||
}
|
||||
|
||||
.panel-expand-handle {
|
||||
left: -30px;
|
||||
width: 30px;
|
||||
min-height: 96px;
|
||||
|
||||
span {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.document-page {
|
||||
|
||||
@@ -41,6 +41,7 @@ const locateStatusText = ref('正在定位文档位置...');
|
||||
let docConvertPollTimer: number | null = null;
|
||||
let activeHighlightEl: HTMLElement | null = null;
|
||||
const loadingPartIndexes = new Set<number>();
|
||||
const scrollContainers = new Set<HTMLElement>();
|
||||
let currentLocateToken = 0;
|
||||
let suspendAutoLoadUntil = 0;
|
||||
|
||||
@@ -205,6 +206,7 @@ const loadPartByIndex = async (partIndex: number) => {
|
||||
updateCurrentPartIndex();
|
||||
await nextTick();
|
||||
injectPageNumbers();
|
||||
window.setTimeout(() => handleScroll(), 0);
|
||||
} finally {
|
||||
loadingPartIndexes.delete(partIndex);
|
||||
}
|
||||
@@ -263,27 +265,55 @@ const getRenderedPageElements = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
const container = previewContainerRef.value;
|
||||
if (!container || isLoadingMore.value || loadedPages.value >= totalPages.value) return;
|
||||
const getScrollParent = (element: HTMLElement | null) => {
|
||||
let parent = element?.parentElement || null;
|
||||
while (parent) {
|
||||
const { overflowY, overflow } = getComputedStyle(parent);
|
||||
if (/auto|scroll|overlay/.test(`${overflowY} ${overflow}`)) return parent;
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const clearScrollListeners = () => {
|
||||
scrollContainers.forEach(container => {
|
||||
container.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
scrollContainers.clear();
|
||||
};
|
||||
|
||||
const isNearScrollBottom = (scrollContainer: HTMLElement) => {
|
||||
const remaining = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight;
|
||||
if (remaining <= 640) return true;
|
||||
|
||||
const content = previewContainerRef.value;
|
||||
if (!content) return false;
|
||||
|
||||
const contentRect = content.getBoundingClientRect();
|
||||
const containerRect = scrollContainer.getBoundingClientRect();
|
||||
return contentRect.bottom - containerRect.bottom <= 640;
|
||||
};
|
||||
|
||||
const handleScroll = (event?: Event) => {
|
||||
const scrollContainer = (event?.currentTarget as HTMLElement | null) || getScrollParent(previewShellRef.value) || previewContainerRef.value;
|
||||
if (!scrollContainer || isLoadingMore.value || loadedPages.value >= totalPages.value) return;
|
||||
if (shouldSuspendAutoLoad()) return;
|
||||
|
||||
const pageElements = getRenderedPageElements();
|
||||
if (!pageElements.length) return;
|
||||
|
||||
const viewportBottom = container.scrollTop + container.clientHeight;
|
||||
const lastPage = pageElements[pageElements.length - 1];
|
||||
if (lastPage.offsetTop - viewportBottom < 240) {
|
||||
if (isNearScrollBottom(scrollContainer)) {
|
||||
loadMorePages();
|
||||
}
|
||||
};
|
||||
|
||||
const setupScrollListener = () => {
|
||||
const container = previewContainerRef.value;
|
||||
if (!container) return;
|
||||
clearScrollListeners();
|
||||
|
||||
container.removeEventListener('scroll', handleScroll);
|
||||
container.addEventListener('scroll', handleScroll);
|
||||
const containers = [previewContainerRef.value, getScrollParent(previewShellRef.value)].filter(Boolean) as HTMLElement[];
|
||||
containers.forEach(container => {
|
||||
scrollContainers.add(container);
|
||||
container.addEventListener('scroll', handleScroll, { passive: true });
|
||||
});
|
||||
|
||||
window.setTimeout(() => handleScroll(), 0);
|
||||
};
|
||||
|
||||
const prepareDocConvertPreview = async (silent = false): Promise<'ready' | 'pending' | 'error'> => {
|
||||
@@ -741,10 +771,7 @@ watch(
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
const container = previewContainerRef.value;
|
||||
if (container) {
|
||||
container.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
clearScrollListeners();
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
if (document.fullscreenElement === previewShellRef.value) {
|
||||
document.exitFullscreen().catch(() => {});
|
||||
|
||||
@@ -300,6 +300,18 @@ function hideParagraphIdColumns(root?: Element | null) {
|
||||
});
|
||||
}
|
||||
|
||||
function wrapMarkdownTables(root?: Element | null) {
|
||||
const tables = root?.querySelectorAll('table') || [];
|
||||
tables.forEach(table => {
|
||||
if (table.parentElement?.classList.contains('part-table-scroll')) return;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'part-table-scroll';
|
||||
table.parentNode?.insertBefore(wrapper, table);
|
||||
wrapper.appendChild(table);
|
||||
});
|
||||
}
|
||||
|
||||
function bindParagraphLocateRows(root?: Element | null) {
|
||||
const rowList = root?.querySelectorAll('tbody tr') || [];
|
||||
rowList.forEach(row => {
|
||||
@@ -328,6 +340,7 @@ async function processMarkdownTables() {
|
||||
?.querySelector<HTMLElement>('.mc-markdown-render');
|
||||
if (!markdownRoot) return;
|
||||
|
||||
wrapMarkdownTables(markdownRoot);
|
||||
hideParagraphIdColumns(markdownRoot);
|
||||
bindParagraphLocateRows(markdownRoot);
|
||||
}
|
||||
@@ -555,12 +568,51 @@ defineExpose({
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
#docx-container-inner-slice {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.el-row) {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render) {
|
||||
display: block;
|
||||
color: #242832;
|
||||
font-size: 14px;
|
||||
line-height: 1.72;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.part-table-scroll) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin: 12px 0 18px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.part-table-scroll::-webkit-scrollbar) {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.part-table-scroll::-webkit-scrollbar-thumb) {
|
||||
border-radius: 999px;
|
||||
background: #b8c0cc;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.part-table-scroll::-webkit-scrollbar-track) {
|
||||
border-radius: 999px;
|
||||
background: #f0f3f7;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render h1),
|
||||
@@ -582,19 +634,20 @@ defineExpose({
|
||||
.part-reading-scroll :deep(.mc-markdown-render table) {
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
max-width: none;
|
||||
table-layout: auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td) {
|
||||
min-width: 96px;
|
||||
max-width: 220px;
|
||||
min-width: 132px;
|
||||
max-width: 360px;
|
||||
padding: 12px 12px;
|
||||
border: 1px solid #eceff3;
|
||||
vertical-align: middle;
|
||||
word-break: normal;
|
||||
overflow-wrap: break-word;
|
||||
vertical-align: top;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@@ -608,33 +661,26 @@ defineExpose({
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(1)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(1)) {
|
||||
min-width: 86px;
|
||||
max-width: 110px;
|
||||
max-width: 126px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(2)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(2)) {
|
||||
min-width: 116px;
|
||||
max-width: 150px;
|
||||
min-width: 130px;
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(3)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(3)) {
|
||||
min-width: 180px;
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(4)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(4)) {
|
||||
min-width: 74px;
|
||||
max-width: 90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(5)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(5)) {
|
||||
min-width: 220px;
|
||||
max-width: 320px;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.part-reading-scroll :deep(.mc-markdown-render table td:nth-child(n + 4)),
|
||||
.part-reading-scroll :deep(.mc-markdown-render table th:nth-child(n + 4)) {
|
||||
min-width: 220px;
|
||||
max-width: 420px;
|
||||
}
|
||||
/deep/.tr-bg:hover {
|
||||
color: rgb(1, 255, 64);
|
||||
@@ -714,6 +760,6 @@ defineExpose({
|
||||
|
||||
<style>
|
||||
.mc-markdown-render td {
|
||||
min-width: 120px;
|
||||
min-width: 132px;
|
||||
}
|
||||
</style>
|
||||
|
||||
2068
src/views/ai-credit/index.vue
Normal file
@@ -8,6 +8,8 @@ const {
|
||||
changeSystemRuleTemplate,
|
||||
checkResultStat,
|
||||
checkRuleRows,
|
||||
closeContentRuleEditor,
|
||||
contentRuleEditing,
|
||||
detailActiveTreeId,
|
||||
detailCorrectReq,
|
||||
detailDocName,
|
||||
@@ -109,17 +111,24 @@ const {
|
||||
|
||||
<section class="review-check-panel">
|
||||
<header class="review-panel-tools content-result-tools">
|
||||
<div>
|
||||
<button type="button" @click="handleEditRule()">编辑</button>
|
||||
<div class="review-style-actions">
|
||||
<button v-if="contentRuleEditing" type="button" @click="closeContentRuleEditor">返回</button>
|
||||
<button v-else type="button" @click="handleEditRule()">编辑</button>
|
||||
<button type="button" @click="toggleSelectedRuleLock">
|
||||
{{ selectedRule?.lockStatus === 1 ? '解锁' : '锁定' }}
|
||||
</button>
|
||||
</div>
|
||||
<button class="review-start-check" type="button" :disabled="btnLoading" @click="startCheck">
|
||||
<button
|
||||
v-if="!contentRuleEditing"
|
||||
class="review-start-check"
|
||||
type="button"
|
||||
:disabled="btnLoading"
|
||||
@click="startCheck"
|
||||
>
|
||||
{{ btnLoading ? '检查中' : '开始检查' }}
|
||||
</button>
|
||||
</header>
|
||||
<div class="review-check-list">
|
||||
<div class="review-check-list view-mode" :class="{ 'is-editing-rule': contentRuleEditing }">
|
||||
<table class="review-check-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -167,7 +176,12 @@ const {
|
||||
</div>
|
||||
</header>
|
||||
<div class="review-result-list">
|
||||
<article v-for="item in reviewResultCards" :key="item.id" class="review-result-card">
|
||||
<article
|
||||
v-for="item in reviewResultCards"
|
||||
:key="item.id"
|
||||
class="review-result-card"
|
||||
@click="openResultDetail(item, 1)"
|
||||
>
|
||||
<div>
|
||||
<h4>
|
||||
{{ item.proposalName || item.dtlName || item.levelName || '-' }}
|
||||
|
||||
@@ -88,8 +88,8 @@ const getAnalysisStatusClass = (status?: string) => {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="regular-rule-grid">
|
||||
<section class="regular-setting-card design-card featured">
|
||||
<div class="regular-rule-grid regular-prototype-rules">
|
||||
<section class="regular-setting-card design-card featured regular-prototype-card">
|
||||
<header>
|
||||
<strong>文本</strong>
|
||||
<span>TEXT</span>
|
||||
@@ -99,12 +99,14 @@ const getAnalysisStatusClass = (status?: string) => {
|
||||
<span>>=</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.checkDuplicateContent"
|
||||
class="regular-percent-select"
|
||||
:options="analysisPercentOptions"
|
||||
size="small"
|
||||
/>
|
||||
<span>的</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.splitParagraphs"
|
||||
class="regular-scope-select"
|
||||
:options="analysisScopeOptions"
|
||||
size="small"
|
||||
/>
|
||||
@@ -120,7 +122,7 @@ const getAnalysisStatusClass = (status?: string) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="regular-setting-card design-card">
|
||||
<section class="regular-setting-card design-card regular-prototype-card">
|
||||
<header>
|
||||
<strong>图片</strong>
|
||||
<span>IMAGE</span>
|
||||
@@ -132,7 +134,7 @@ const getAnalysisStatusClass = (status?: string) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="regular-setting-card design-card compact-card">
|
||||
<section class="regular-setting-card design-card compact-card regular-prototype-card">
|
||||
<header>
|
||||
<strong>文件属性</strong>
|
||||
<span>METADATA</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import { useTechProposalRedesignContext } from '../context';
|
||||
|
||||
const {
|
||||
@@ -31,6 +32,7 @@ const {
|
||||
saveResponseRuleData,
|
||||
selectedResponseDocName,
|
||||
toggleResponseCheckedKey,
|
||||
toggleResponseResultExpand,
|
||||
toggleResponseRuleExpand
|
||||
} = useTechProposalRedesignContext();
|
||||
</script>
|
||||
@@ -154,7 +156,7 @@ const {
|
||||
type="button"
|
||||
@click="toggleResponseRuleExpand(row.id)"
|
||||
>
|
||||
{{ row.expanded ? '⌄' : '〉' }}
|
||||
<span class="response-tree-toggle-icon" :class="{ expanded: row.expanded }">▶</span>
|
||||
</button>
|
||||
<label>
|
||||
<input
|
||||
@@ -169,11 +171,27 @@ const {
|
||||
</p>
|
||||
</td>
|
||||
<td class="response-rule-actions-cell">
|
||||
<button v-if="row.isGroup" type="button" @click="openResponseModuleDialog('add', 'leaf', row)">
|
||||
+下级
|
||||
</button>
|
||||
<button type="button" @click="openResponseModuleDialog('edit', 'leaf', row)">编辑</button>
|
||||
<button type="button" @click="deleteResponseModule(row.id)">删除</button>
|
||||
<ButtonIcon
|
||||
v-if="row.isGroup"
|
||||
class="response-action-link response-action-child"
|
||||
icon="material-symbols:add-rounded"
|
||||
tooltip-content="添加下级"
|
||||
@click="openResponseModuleDialog('add', 'leaf', row)"
|
||||
>
|
||||
<span>+ 下级</span>
|
||||
</ButtonIcon>
|
||||
<ButtonIcon
|
||||
class="response-action-icon"
|
||||
icon="material-symbols:edit-outline"
|
||||
tooltip-content="编辑"
|
||||
@click="openResponseModuleDialog('edit', 'leaf', row)"
|
||||
/>
|
||||
<ButtonIcon
|
||||
class="response-action-icon danger"
|
||||
icon="material-symbols:delete-outline"
|
||||
tooltip-content="删除"
|
||||
@click="deleteResponseModule(row.id)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -216,7 +234,14 @@ const {
|
||||
<tbody>
|
||||
<tr v-for="row in responseResultTableRows" :key="row.tempKey || row.index || row.id">
|
||||
<td>
|
||||
<span v-if="row.isGroup" class="response-expand">⌄</span>
|
||||
<button
|
||||
v-if="row.isGroup"
|
||||
class="response-result-toggle"
|
||||
type="button"
|
||||
@click="toggleResponseResultExpand(row.resultTreeKey)"
|
||||
>
|
||||
<span class="response-tree-toggle-icon" :class="{ expanded: row.expanded }">▶</span>
|
||||
</button>
|
||||
{{ row.depth === 0 ? row.index : '' }}
|
||||
</td>
|
||||
<td :class="{ 'response-result-child-name': row.depth > 0 }">{{ row.name || '-' }}</td>
|
||||
@@ -306,6 +331,98 @@ const {
|
||||
color: #5f6777;
|
||||
}
|
||||
|
||||
.response-tree-toggle,
|
||||
.response-result-toggle {
|
||||
display: inline-grid;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
place-items: center;
|
||||
border: 1px solid #cfd8e6;
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
color: #315b9d;
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.response-tree-toggle:hover,
|
||||
.response-result-toggle:hover {
|
||||
border-color: #9db8e8;
|
||||
background: #eef5ff;
|
||||
color: #1f62ff;
|
||||
}
|
||||
|
||||
.response-tree-toggle-icon {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
transition: transform 0.16s ease;
|
||||
}
|
||||
|
||||
.response-tree-toggle-icon.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.response-rule-actions-cell {
|
||||
text-align: center !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.response-action-link {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
background: transparent !important;
|
||||
color: #1f62ff !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.response-action-link:hover {
|
||||
color: #004ee8 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.response-action-child {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.response-action-icon {
|
||||
width: 18px !important;
|
||||
min-width: 18px !important;
|
||||
height: 18px !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
color: #1f62ff !important;
|
||||
transition:
|
||||
color 0.16s ease,
|
||||
opacity 0.16s ease;
|
||||
}
|
||||
|
||||
.response-action-icon + .response-action-icon {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.response-action-icon:hover {
|
||||
color: #1f62ff !important;
|
||||
background: transparent !important;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.response-action-icon.danger {
|
||||
color: #e9364c !important;
|
||||
}
|
||||
|
||||
.response-action-icon.danger:hover {
|
||||
color: #d5223d !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.response-pass-dot,
|
||||
.response-fail-dot {
|
||||
display: inline-grid;
|
||||
|
||||
@@ -12,9 +12,17 @@ defineOptions({
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
drawerClass?: string;
|
||||
drawerShowMask?: boolean;
|
||||
drawerTo?: string;
|
||||
drawerWidth?: number | string;
|
||||
}>(),
|
||||
{
|
||||
readonly: false
|
||||
readonly: false,
|
||||
drawerClass: '',
|
||||
drawerShowMask: true,
|
||||
drawerTo: undefined,
|
||||
drawerWidth: '50%'
|
||||
}
|
||||
);
|
||||
|
||||
@@ -780,10 +788,13 @@ defineExpose({
|
||||
<template>
|
||||
<NDrawer
|
||||
v-model:show="visible"
|
||||
:class="props.drawerClass"
|
||||
display-directive="show"
|
||||
width="50%"
|
||||
:to="props.drawerTo"
|
||||
:width="props.drawerWidth"
|
||||
placement="right"
|
||||
:mask-closable="false"
|
||||
:show-mask="props.drawerShowMask"
|
||||
:z-index="2600"
|
||||
>
|
||||
<NDrawerContent title="设置检查项">
|
||||
|
||||
@@ -39,9 +39,9 @@ import riskDefaultIcon from '@/assets/svg-icon/library-tabs/risk-default.svg?url
|
||||
import riskActiveIcon from '@/assets/svg-icon/library-tabs/risk-active.svg?url';
|
||||
import qualificationDefaultIcon from '@/assets/svg-icon/library-tabs/qualification-default.svg?url';
|
||||
import qualificationActiveIcon from '@/assets/svg-icon/library-tabs/qualification-active.svg?url';
|
||||
import defaultSetting from '@/assets/json/defaultSetting.json';
|
||||
import FileUploadFolder from '@/components/custom/file-upload-folder.vue';
|
||||
import DocPreview from '@/components/custom/doc-preview.vue';
|
||||
import SettingDrawer from '@/views/ai-review/tech-proposal/modules/review-proposal/content-check/modules/setting-drawer.vue';
|
||||
|
||||
defineOptions({ name: 'ResourceLibrary' });
|
||||
|
||||
@@ -108,17 +108,16 @@ const editingId = ref<number | null>(null);
|
||||
const previewVisible = ref(false);
|
||||
const previewOssId = ref('');
|
||||
const previewTitle = ref('');
|
||||
const settingDrawerVisible = ref(false);
|
||||
const settingDrawerReadonly = ref(false);
|
||||
const visualRuleConfig = ref<any>(undefined);
|
||||
const ruleConfigExpanded = ref(false);
|
||||
const uploadFile = ref<File | null>(null);
|
||||
const uploadFileList = ref<UploadFileInfo[]>([]);
|
||||
const detailFormRef = ref<FormInst | null>(null);
|
||||
const categoryFormRef = ref<FormInst | null>(null);
|
||||
const ruleFormRef = ref<FormInst | null>(null);
|
||||
|
||||
const searchParams = ref({ keyword: '', page: 1, pageSize: 20, total: 0 });
|
||||
const rulePager = ref({ page: 1, pageSize: 20, total: 0 });
|
||||
const searchParams = ref({ keyword: '', page: 1, pageSize: 18, total: 0 });
|
||||
const rulePager = ref({ page: 1, pageSize: 18, total: 0 });
|
||||
const activeTemplateType = ref<TemplateType>(CUSTOM_TEMPLATE_TYPE);
|
||||
const systemRuleType = ref<RuleScopeType | null>(null);
|
||||
const systemSector = ref<string | null>(null);
|
||||
@@ -155,6 +154,19 @@ const canManageCurrentRuleType = computed(() => activeTemplateType.value === CUS
|
||||
const isSystemRuleSource = computed(() => activeTemplateType.value === SYSTEM_TEMPLATE_TYPE);
|
||||
const isSystemRuleForm = computed(() => ruleForm.value.templateType === SYSTEM_TEMPLATE_TYPE);
|
||||
const currentRuleCount = computed(() => getRuleCount(ruleForm.value.checkConfig));
|
||||
const detailDrawerWidth = computed(() => (isRuleLibrary.value ? 420 : 640));
|
||||
const detailDrawerTitle = computed(() => {
|
||||
if (!isRuleLibrary.value) return isTemplateLibrary.value ? '解析结果' : '详情';
|
||||
|
||||
const actionMap: Record<OperateType, string> = {
|
||||
add: '新增',
|
||||
edit: '编辑',
|
||||
view: '查看'
|
||||
};
|
||||
const ruleType = isSystemRuleForm.value ? '系统模板' : '用户自定义模板';
|
||||
|
||||
return `${actionMap[operateType.value]}${ruleType}`;
|
||||
});
|
||||
const ruleSourceTabs = computed(() => {
|
||||
const result = [
|
||||
{ label: '用户自定义模版', value: CUSTOM_TEMPLATE_TYPE as TemplateType, desc: '维护当前账号可用的规则模版' }
|
||||
@@ -179,6 +191,89 @@ const tabIconMap: Record<LibraryKey, { default: string; active: string }> = {
|
||||
risk: { default: riskDefaultIcon, active: riskActiveIcon },
|
||||
qualification: { default: qualificationDefaultIcon, active: qualificationActiveIcon }
|
||||
};
|
||||
const ruleConfigItems = [
|
||||
{ title: '敏感内容检查', type: 'sensitiveContentCheck', fallback: '敏感词、文件属性、图片检查' },
|
||||
{ title: '纸张要求', type: 'paperRequirements', fallback: '纸张大小、纸张方向' },
|
||||
{ title: '颜色要求', type: 'colorRequirements', fallback: '白色页面、正文及图片颜色' },
|
||||
{ title: '页面要求', type: 'pageRequirements', fallback: '页边距、页眉页脚、页面边框' },
|
||||
{ title: '正文要求', type: 'contentRequirements', fallback: '正文、标题、段落样式' },
|
||||
{ title: '表格要求', type: 'tableRequirements', fallback: '表格属性、字体样式' },
|
||||
{ title: '图片要求', type: 'imageRequirements', fallback: '图片环绕、段落样式' },
|
||||
{ title: '页码要求', type: 'pageNumberRequirements', fallback: '页码字体、编号格式' },
|
||||
{ title: '目录要求', type: 'tocRequirements', fallback: '目录字体、段落样式' },
|
||||
{ title: '页数要求', type: 'pageCountRequirements', fallback: '最大页数限制' },
|
||||
{ title: '默认检查', type: 'defaultChecks', fallback: '超链接、形状、水印、字体' },
|
||||
{ title: '不允许出现', type: 'prohibitedItems', fallback: '空行、空格、空白页' }
|
||||
];
|
||||
const ruleConfigLabelMap: Record<string, string> = {
|
||||
sensitiveWordsEnable: '敏感词检查',
|
||||
checkFileProperties: '文件属性检查',
|
||||
palaceCheck: '暗标/宫格检查',
|
||||
imageCheck: '图片检查',
|
||||
paperSizeEnable: '纸张大小',
|
||||
paperOrientationEnable: '纸张方向',
|
||||
whiteColorPage: '白色页面',
|
||||
noColorContent: '禁止彩色正文',
|
||||
noColorImages: '禁止彩色图片',
|
||||
noColorGraphics: '禁止彩色图形',
|
||||
noTableShading: '禁止表格底纹',
|
||||
noTableColorBorders: '禁止表格彩色边框',
|
||||
topMarginEnable: '上边距',
|
||||
bottomMarginEnable: '下边距',
|
||||
leftMarginEnable: '左边距',
|
||||
rightMarginEnable: '右边距',
|
||||
headerDistanceEnable: '页眉距离',
|
||||
footerDistanceEnable: '页脚距离',
|
||||
noHeader: '禁止页眉',
|
||||
noFooter: '禁止页脚',
|
||||
noPageBorders: '禁止页面边框',
|
||||
chineseFontEnable: '中文字体',
|
||||
westernFontEnable: '西文字体',
|
||||
fontSizeEnable: '字号',
|
||||
lineSpacingEnable: '行距',
|
||||
spacingBeforeEnable: '段前间距',
|
||||
spacingAfterEnable: '段后间距',
|
||||
leftIndentEnable: '左缩进',
|
||||
rightIndentEnable: '右缩进',
|
||||
firstLineIndentEnable: '首行缩进',
|
||||
alignmentEnable: '对齐方式',
|
||||
outlineLevelBodyText: '正文大纲级别',
|
||||
pageCountEnable: '页数限制',
|
||||
disallowTables: '禁止表格',
|
||||
noTableRequirements: '无表格要求',
|
||||
tableAlignmentEnable: '表格对齐',
|
||||
cellVerticalAlignmentEnable: '单元格垂直对齐',
|
||||
borderWidthEnable: '边框宽度',
|
||||
noTextEnvironment: '禁止文字环绕',
|
||||
borderStyleSolid: '实线边框',
|
||||
disallowImages: '禁止图片',
|
||||
noImageRequirements: '无图片要求',
|
||||
wrapTextEnable: '图片环绕方式',
|
||||
disallowPageNumbers: '禁止页码',
|
||||
noPageNumberRequirements: '无页码要求',
|
||||
numberingFormatEnable: '编号格式',
|
||||
rangeEnable: '页码范围',
|
||||
continuous: '连续编号',
|
||||
disallowToc: '禁止目录',
|
||||
tocHidePageNumbers: '目录隐藏页码',
|
||||
disallowHyperlinks: '禁止超链接',
|
||||
disallowShapes: '禁止形状',
|
||||
disallowWatermark: '禁止水印',
|
||||
disallowBold: '禁止加粗',
|
||||
disallowUnderline: '禁止下划线',
|
||||
disallowItalic: '禁止斜体',
|
||||
disallowStrikethrough: '禁止删除线',
|
||||
disallowEmphasisMark: '禁止着重号',
|
||||
allowedColorEnable: '允许颜色',
|
||||
requireDocumentGrid: '文档网格',
|
||||
requireStandardScale: '标准缩放',
|
||||
requireStandardSpacing: '标准间距',
|
||||
requireStandardPosition: '标准位置',
|
||||
disallowBlankLines: '禁止空行',
|
||||
disallowSpaces: '禁止空格',
|
||||
disallowBlankPages: '禁止空白页',
|
||||
disallowHalfwidthPunctuation: '禁止半角标点'
|
||||
};
|
||||
const heroTitle = computed(() => {
|
||||
if (isRuleLibrary.value) return '维护审查规则,沉淀可复用的投标检查模板';
|
||||
if (isTemplateLibrary.value) return '上传模版内容,定制你的专属模版';
|
||||
@@ -240,6 +335,7 @@ function resetRuleForm() {
|
||||
sector: null
|
||||
};
|
||||
visualRuleConfig.value = undefined;
|
||||
ruleConfigExpanded.value = false;
|
||||
ruleFormRef.value?.restoreValidation();
|
||||
}
|
||||
|
||||
@@ -252,13 +348,9 @@ function getRuleCount(checkConfig: string) {
|
||||
const parsed = JSON.parse(checkConfig);
|
||||
if (Array.isArray(parsed)) return parsed.length;
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
const values = Object.values(parsed as Record<string, unknown>);
|
||||
const enableItems = values.filter(item => {
|
||||
if (!item || typeof item !== 'object' || Array.isArray(item)) return false;
|
||||
return 'enable' in item && Boolean((item as Record<string, unknown>).enable);
|
||||
});
|
||||
const rules = parsed as Record<string, Record<string, unknown>>;
|
||||
|
||||
return enableItems.length > 0 ? enableItems.length : Object.keys(parsed).length;
|
||||
return ruleConfigItems.filter(item => isTopRuleConfigChecked(rules[item.type])).length;
|
||||
}
|
||||
} catch {
|
||||
return 0;
|
||||
@@ -267,6 +359,15 @@ function getRuleCount(checkConfig: string) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isTopRuleConfigChecked(config: unknown) {
|
||||
if (!config || typeof config !== 'object') return false;
|
||||
const subItems = getRuleConfigSubItems(config);
|
||||
|
||||
if (subItems.length) return subItems.every(item => item.checked);
|
||||
|
||||
return Boolean((config as Record<string, unknown>).enable);
|
||||
}
|
||||
|
||||
function getRuleScopeLabel(type?: RuleScopeType | null) {
|
||||
if (type === REGION_RULE_TYPE) return '地区通用规则';
|
||||
if (type === COMPANY_RULE_TYPE) return '企业通用规则';
|
||||
@@ -285,15 +386,112 @@ function syncVisualRuleConfig(checkConfig: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function openRuleSetting() {
|
||||
settingDrawerReadonly.value = detailReadonly.value;
|
||||
if (!visualRuleConfig.value) syncVisualRuleConfig(ruleForm.value.checkConfig);
|
||||
settingDrawerVisible.value = true;
|
||||
function ensureVisualRuleConfig() {
|
||||
if (!visualRuleConfig.value || typeof visualRuleConfig.value !== 'object') {
|
||||
visualRuleConfig.value = JSON.parse(JSON.stringify(defaultSetting));
|
||||
syncRuleConfigToForm();
|
||||
}
|
||||
|
||||
return visualRuleConfig.value;
|
||||
}
|
||||
|
||||
function handleRuleSettingSubmitted(value: string) {
|
||||
ruleForm.value.checkConfig = value;
|
||||
syncVisualRuleConfig(value);
|
||||
function syncRuleConfigToForm() {
|
||||
ruleForm.value.checkConfig = JSON.stringify(visualRuleConfig.value || {});
|
||||
}
|
||||
|
||||
function toggleRuleConfigExpanded() {
|
||||
ensureVisualRuleConfig();
|
||||
ruleConfigExpanded.value = !ruleConfigExpanded.value;
|
||||
}
|
||||
|
||||
function isRuleConfigEnabled(type: string) {
|
||||
const config = ensureVisualRuleConfig()[type];
|
||||
|
||||
return isTopRuleConfigChecked(config);
|
||||
}
|
||||
|
||||
function setAllRuleConfigBooleans(value: unknown, checked: boolean) {
|
||||
if (!value || typeof value !== 'object') return;
|
||||
|
||||
Object.entries(value as Record<string, unknown>).forEach(([key, item]) => {
|
||||
if (key === 'enable') return;
|
||||
if (typeof item === 'boolean') {
|
||||
(value as Record<string, boolean>)[key] = checked;
|
||||
return;
|
||||
}
|
||||
setAllRuleConfigBooleans(item, checked);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleRuleConfigItem(type: string, checked: boolean) {
|
||||
if (detailReadonly.value) return;
|
||||
const config = ensureVisualRuleConfig();
|
||||
config[type] ||= {};
|
||||
config[type].enable = checked;
|
||||
setAllRuleConfigBooleans(config[type], checked);
|
||||
syncRuleConfigToForm();
|
||||
}
|
||||
|
||||
function getRuleConfigSubItems(value: unknown, prefix = ''): Array<{ path: string; label: string; checked: boolean }> {
|
||||
if (!value || typeof value !== 'object') return [];
|
||||
|
||||
return Object.entries(value as Record<string, unknown>).flatMap(([key, item]) => {
|
||||
if (key === 'enable') return [];
|
||||
const path = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
if (typeof item === 'boolean') {
|
||||
return [{ path, label: ruleConfigLabelMap[key] || key, checked: item }];
|
||||
}
|
||||
|
||||
if (item && typeof item === 'object') {
|
||||
return getRuleConfigSubItems(item, path);
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
function getRuleConfigItemSubs(type: string) {
|
||||
return getRuleConfigSubItems(ensureVisualRuleConfig()[type]).slice(0, 12);
|
||||
}
|
||||
|
||||
function setRuleConfigPathValue(target: Record<string, any>, path: string, checked: boolean) {
|
||||
const keys = path.split('.');
|
||||
let current = target;
|
||||
|
||||
keys.slice(0, -1).forEach(key => {
|
||||
current[key] ||= {};
|
||||
current = current[key];
|
||||
});
|
||||
|
||||
current[keys[keys.length - 1]] = checked;
|
||||
}
|
||||
|
||||
function toggleRuleConfigSubItem(type: string, path: string, checked: boolean) {
|
||||
if (detailReadonly.value) return;
|
||||
const config = ensureVisualRuleConfig();
|
||||
config[type] ||= {};
|
||||
setRuleConfigPathValue(config[type], path, checked);
|
||||
config[type].enable = getRuleConfigSubItems(config[type]).every(item => item.checked);
|
||||
syncRuleConfigToForm();
|
||||
}
|
||||
|
||||
function getEnabledRuleSubCount(value: unknown): number {
|
||||
if (!value || typeof value !== 'object') return 0;
|
||||
|
||||
return Object.entries(value as Record<string, unknown>).reduce((count, [key, item]) => {
|
||||
if (key === 'enable') return count;
|
||||
if (typeof item === 'boolean') return count + (item ? 1 : 0);
|
||||
if (item && typeof item === 'object') return count + getEnabledRuleSubCount(item);
|
||||
return count;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function getRuleConfigSummary(item: (typeof ruleConfigItems)[number]) {
|
||||
const config = ensureVisualRuleConfig()[item.type];
|
||||
const count = getEnabledRuleSubCount(config);
|
||||
|
||||
return count ? `${count} 项子规则已启用` : item.fallback;
|
||||
}
|
||||
|
||||
function addKeysToTree(nodes: TreeNode[], level = 1) {
|
||||
@@ -693,8 +891,6 @@ watch(
|
||||
if (!visible) {
|
||||
operateType.value = 'add';
|
||||
detailReadonly.value = false;
|
||||
settingDrawerVisible.value = false;
|
||||
settingDrawerReadonly.value = false;
|
||||
resetDetailForm();
|
||||
resetRuleForm();
|
||||
return;
|
||||
@@ -975,11 +1171,11 @@ onMounted(async () => {
|
||||
|
||||
<NDrawer
|
||||
v-model:show="detailVisible"
|
||||
:width="640"
|
||||
:width="detailDrawerWidth"
|
||||
placement="right"
|
||||
:class="{ 'resource-result-drawer': isTemplateLibrary, 'rule-detail-drawer': isRuleLibrary }"
|
||||
>
|
||||
<NDrawerContent :title="isRuleLibrary ? '规则详情' : isTemplateLibrary ? '解析结果' : '详情'" closable>
|
||||
<NDrawerContent :title="detailDrawerTitle" closable>
|
||||
<NForm
|
||||
v-if="isRuleLibrary"
|
||||
ref="ruleFormRef"
|
||||
@@ -989,16 +1185,16 @@ onMounted(async () => {
|
||||
label-placement="top"
|
||||
:disabled="detailReadonly"
|
||||
>
|
||||
<NFormItem label="规则类型" class="rule-detail-kind-row">
|
||||
<NFormItem label="模板类型" class="rule-detail-kind-row">
|
||||
<div class="rule-detail-kind">
|
||||
<em class="rule-detail-kind-tag">
|
||||
{{ ruleForm.templateType === SYSTEM_TEMPLATE_TYPE ? '系统模版' : '用户自定义模版' }}
|
||||
{{ ruleForm.templateType === SYSTEM_TEMPLATE_TYPE ? '系统模板' : '用户自定义模板' }}
|
||||
</em>
|
||||
</div>
|
||||
</NFormItem>
|
||||
<NFormItem label="规则名称" path="configName" class="rule-detail-name-row required-mark">
|
||||
<NFormItem label="模板名称" path="configName" class="rule-detail-name-row required-mark">
|
||||
<div class="rule-detail-name-shell">
|
||||
<NInput v-model:value="ruleForm.configName" maxlength="50" placeholder="请输入模版名称" />
|
||||
<NInput v-model:value="ruleForm.configName" maxlength="50" placeholder="请输入模板名称" />
|
||||
<em class="rule-detail-name-count">{{ ruleForm.configName.length }} / 50</em>
|
||||
</div>
|
||||
</NFormItem>
|
||||
@@ -1021,11 +1217,57 @@ onMounted(async () => {
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="规则配置" path="checkConfig" class="rule-detail-config-block">
|
||||
<div class="rule-detail-config-box">
|
||||
<strong class="rule-detail-config-status">已配置 {{ currentRuleCount }} 项规则</strong>
|
||||
<button class="rule-detail-view-rules" type="button" @click="openRuleSetting">
|
||||
{{ detailReadonly ? '查看规则' : '设置规则' }}
|
||||
</button>
|
||||
<div class="rule-detail-config-wrap">
|
||||
<div class="rule-detail-config-box">
|
||||
<strong class="rule-detail-config-status">已配置 {{ currentRuleCount }} 项规则</strong>
|
||||
<button class="rule-detail-view-rules" type="button" @click="toggleRuleConfigExpanded">
|
||||
{{ ruleConfigExpanded ? '收起' : detailReadonly ? '查看规则' : '编辑规则' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section v-if="ruleConfigExpanded" class="rule-inline-config">
|
||||
<header>
|
||||
<strong>检查项勾选</strong>
|
||||
</header>
|
||||
<div class="rule-inline-list">
|
||||
<article
|
||||
v-for="item in ruleConfigItems"
|
||||
:key="item.type"
|
||||
class="rule-inline-item"
|
||||
:class="{ disabled: detailReadonly }"
|
||||
>
|
||||
<label class="rule-inline-main-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isRuleConfigEnabled(item.type)"
|
||||
:disabled="detailReadonly"
|
||||
@change="toggleRuleConfigItem(item.type, ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
<span>
|
||||
<strong>{{ item.title }}</strong>
|
||||
<em>[{{ getRuleConfigSummary(item) }}]</em>
|
||||
</span>
|
||||
</label>
|
||||
<div class="rule-inline-sub-list">
|
||||
<label
|
||||
v-for="sub in getRuleConfigItemSubs(item.type)"
|
||||
:key="sub.path"
|
||||
class="rule-inline-sub-check"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="sub.checked"
|
||||
:disabled="detailReadonly"
|
||||
@change="
|
||||
toggleRuleConfigSubItem(item.type, sub.path, ($event.target as HTMLInputElement).checked)
|
||||
"
|
||||
/>
|
||||
<span>{{ sub.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
@@ -1103,30 +1345,28 @@ onMounted(async () => {
|
||||
</NModal>
|
||||
|
||||
<DocPreview v-model:visible="previewVisible" :oss-id="previewOssId" :title="previewTitle" />
|
||||
<SettingDrawer
|
||||
v-model:visible="settingDrawerVisible"
|
||||
v-model:value="visualRuleConfig"
|
||||
:readonly="settingDrawerReadonly"
|
||||
@submitted="handleRuleSettingSubmitted"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.resource-page {
|
||||
position: relative;
|
||||
min-height: calc(100vh - 64px);
|
||||
padding: 28px 20px 48px;
|
||||
padding: 18px 20px 44px;
|
||||
background: #fff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.resource-tabs {
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 50px;
|
||||
margin: 0 auto 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0 auto 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(244, 245, 247, 0.96);
|
||||
border: 1px solid #e7e9ee;
|
||||
@@ -1137,6 +1377,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.resource-tab {
|
||||
flex: 0 0 auto;
|
||||
min-width: 86px;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
@@ -1168,7 +1409,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.resource-hero {
|
||||
margin: 0 0 22px;
|
||||
margin: 0 0 18px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
@@ -1182,7 +1423,7 @@ onMounted(async () => {
|
||||
.resource-upload-card {
|
||||
position: relative;
|
||||
width: min(744px, 100%);
|
||||
min-height: 252px;
|
||||
min-height: 236px;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #1f2026;
|
||||
border-radius: 20px;
|
||||
@@ -1429,7 +1670,7 @@ onMounted(async () => {
|
||||
width: min(744px, 100%);
|
||||
margin: 16px auto 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
@@ -1778,63 +2019,70 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer) {
|
||||
width: min(640px, 100vw) !important;
|
||||
border-left: 1px solid #dde2ec;
|
||||
box-shadow: -8px 0 22px rgba(17, 24, 39, 0.1);
|
||||
width: min(390px, 100vw) !important;
|
||||
border-left: 1px solid #e5e9f0;
|
||||
background: #fff;
|
||||
box-shadow: -10px 0 26px rgba(17, 24, 39, 0.12);
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-header) {
|
||||
height: 50px;
|
||||
padding: 0 24px !important;
|
||||
border-bottom: 1px solid #edf0f5 !important;
|
||||
height: 40px;
|
||||
padding: 0 14px !important;
|
||||
border-bottom: 1px solid #ece2d6 !important;
|
||||
background: #fffaf4;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-header__main) {
|
||||
color: #111827;
|
||||
font-size: 18px;
|
||||
color: #172033;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-header__close) {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #e6e7ea;
|
||||
color: #777d86;
|
||||
background: #eef2f8;
|
||||
color: #647084;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-body-content-wrapper) {
|
||||
padding: 16px 24px !important;
|
||||
padding: 10px 12px !important;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-footer) {
|
||||
height: 70px;
|
||||
padding: 0 24px !important;
|
||||
height: 50px;
|
||||
padding: 0 12px !important;
|
||||
border-top: 1px solid #edf0f5;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-footer .n-button:first-child) {
|
||||
min-width: 56px;
|
||||
height: 34px;
|
||||
border-color: #d8dde8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #4b5563;
|
||||
min-width: 54px;
|
||||
height: 30px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: #fff1de;
|
||||
color: #c86f1a;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global(.rule-detail-drawer .n-drawer-footer .n-button:not(:first-child)) {
|
||||
min-width: 72px;
|
||||
height: 34px;
|
||||
min-width: 64px;
|
||||
height: 30px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(180deg, #ff9f22 0%, #ff8614 100%);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global(.rule-detail-panel) {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-panel .n-form-item) {
|
||||
@@ -1843,46 +2091,55 @@ onMounted(async () => {
|
||||
|
||||
:global(.rule-detail-panel .n-form-item-label) {
|
||||
min-height: auto;
|
||||
padding: 0 0 6px !important;
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
padding: 0 0 4px !important;
|
||||
color: #2d3748;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global(.rule-detail-panel .n-form-item-feedback-wrapper) {
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-panel .required-mark .n-form-item-label::after) {
|
||||
content: ' *';
|
||||
color: #ff7d1f;
|
||||
color: #ff5d4f;
|
||||
}
|
||||
|
||||
:global(.rule-detail-panel .n-input),
|
||||
:global(.rule-detail-panel .n-base-selection) {
|
||||
--n-height: 36px !important;
|
||||
--n-border: 1px solid #d8dde8 !important;
|
||||
--n-border-hover: 1px solid #d8dde8 !important;
|
||||
--n-border-focus: 1px solid #ffcc92 !important;
|
||||
--n-border-radius: 8px !important;
|
||||
--n-height: 32px !important;
|
||||
--n-border: 1px solid #d9e1ee !important;
|
||||
--n-border-hover: 1px solid #cfd8e8 !important;
|
||||
--n-border-focus: 1px solid #ffbf72 !important;
|
||||
--n-border-radius: 6px !important;
|
||||
--n-box-shadow-focus: 0 0 0 2px rgba(255, 138, 31, 0.12) !important;
|
||||
--n-color: #f8fafc !important;
|
||||
--n-color-disabled: #f3f6fa !important;
|
||||
--n-text-color-disabled: #2d3748 !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-kind) {
|
||||
height: 34px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e2d4c2;
|
||||
border-radius: 8px;
|
||||
background: #fffaf2;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #f0bf80;
|
||||
border-radius: 6px;
|
||||
background: #fffaf4;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-kind-tag) {
|
||||
height: 24px;
|
||||
height: 22px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 7px;
|
||||
border-radius: 6px;
|
||||
background: #fff0df;
|
||||
color: #c56d1b;
|
||||
padding: 0 10px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -1892,14 +2149,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
:global(.rule-detail-name-shell .n-input__input-el) {
|
||||
padding-right: 72px;
|
||||
padding-right: 62px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-name-count) {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
color: #8e96a3;
|
||||
color: #8893a5;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
transform: translateY(-50%);
|
||||
@@ -1910,32 +2167,157 @@ onMounted(async () => {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:global(.rule-detail-config-wrap) {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:global(.rule-detail-config-box) {
|
||||
min-height: 46px;
|
||||
min-height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border: 1px solid #dbe4f2;
|
||||
border-radius: 9px;
|
||||
border: 1px solid #cfdcf0;
|
||||
border-radius: 6px;
|
||||
background: #f9fbff;
|
||||
padding: 0 10px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-config) {
|
||||
overflow: hidden;
|
||||
border: 1px solid #d9e3f2;
|
||||
border-radius: 8px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
:global(.rule-inline-config > header) {
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #edf2f8;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-config > header strong) {
|
||||
color: #2d3748;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
:global(.rule-inline-list) {
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-list::-webkit-scrollbar) {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-list::-webkit-scrollbar-thumb) {
|
||||
border-radius: 999px;
|
||||
background: #9aa3b2;
|
||||
}
|
||||
|
||||
:global(.rule-inline-item) {
|
||||
display: block;
|
||||
border: 1px solid #edf0f5;
|
||||
border-radius: 7px;
|
||||
background: #fffdf9;
|
||||
padding: 6px 8px 7px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-item + .rule-inline-item) {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-item.disabled) {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:global(.rule-inline-main-check) {
|
||||
display: grid;
|
||||
grid-template-columns: 18px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global(.rule-inline-item input) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
accent-color: #ff941f;
|
||||
}
|
||||
|
||||
:global(.rule-inline-main-check span) {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-main-check strong) {
|
||||
color: #273244;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
:global(.rule-inline-main-check em) {
|
||||
overflow: hidden;
|
||||
color: #667894;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:global(.rule-inline-sub-list) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px 8px;
|
||||
margin-top: 7px;
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-sub-check) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
max-width: 100%;
|
||||
color: #65738b;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global(.rule-inline-sub-check input) {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
:global(.rule-inline-sub-check span) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:global(.rule-detail-config-status) {
|
||||
color: #52607a;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #526079;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global(.rule-detail-view-rules) {
|
||||
height: 30px;
|
||||
border: 1px solid #7da2f2;
|
||||
border-radius: 8px;
|
||||
background: #f3f7ff;
|
||||
color: #285fd8;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
height: 28px;
|
||||
border: 1px solid #8eb3ff;
|
||||
border-radius: 6px;
|
||||
background: #eef5ff;
|
||||
color: #2368d8;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1944,13 +2326,33 @@ onMounted(async () => {
|
||||
background: #eaf1ff;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
@media (max-width: 980px) {
|
||||
.resource-upload-card,
|
||||
.resource-sticky-block,
|
||||
.resource-rule-library,
|
||||
.template-category-grid,
|
||||
.resource-rule-library ~ .n-spin .template-category-grid {
|
||||
width: min(560px, 100%);
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.pager-row {
|
||||
width: min(560px, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.resource-tabs {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.resource-tab {
|
||||
flex: 0 0 auto;
|
||||
min-width: 106px;
|
||||
}
|
||||
|
||||
.resource-fields,
|
||||
.rule-fields,
|
||||
.resource-rule-source-switch,
|
||||
@@ -1959,6 +2361,15 @@ onMounted(async () => {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.template-category-grid,
|
||||
.resource-rule-library,
|
||||
.resource-upload-card,
|
||||
.resource-sticky-block,
|
||||
.resource-rule-library ~ .n-spin .template-category-grid,
|
||||
.pager-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resource-sticky-block {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ const importModalVisible = ref(false);
|
||||
const catalogueModalVisible = ref(false);
|
||||
const currentRightModule = ref('');
|
||||
const isRightCollapsed = ref(false);
|
||||
const isLeftCollapsed = ref(false);
|
||||
const catalogueData = ref<TreeNode[]>([]);
|
||||
const currentMajorType = ref<string>(''); // 当前方案的专业类型
|
||||
const currentPlanName = ref('');
|
||||
@@ -271,10 +270,6 @@ const toggleRightCollapsed = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleLeftCollapsed = () => {
|
||||
isLeftCollapsed.value = !isLeftCollapsed.value;
|
||||
};
|
||||
|
||||
// 开始生成文档
|
||||
const handleGenerateDocument = async () => {
|
||||
if (!proposalId.value) {
|
||||
@@ -877,14 +872,14 @@ const normalizeSerialNumber = (text: string) => {
|
||||
|
||||
const extractSerialPrefix = (text: string) => {
|
||||
const normalized = normalizeCatalogueText(text);
|
||||
const match = normalized.match(/^([0-9]+(?:\.[0-9]+)*)/);
|
||||
const match = normalized.match(/^([0-9]+(?:\.[0-9]+)*)(?=$|[\s..、::\-—()()])/);
|
||||
return match?.[1] ? normalizeSerialNumber(match[1]) : '';
|
||||
};
|
||||
|
||||
const stripCatalogueSerial = (text: string) => {
|
||||
const normalized = normalizeCatalogueText(text);
|
||||
return normalized
|
||||
.replace(/^[0-9]+(?:\.[0-9]+)*[\s..、::\-—()()]*/i, '')
|
||||
.replace(/^[0-9]+(?:\.[0-9]+)*(?:[\s..、::\-—()()]+|$)/i, '')
|
||||
.replace(/^[一二三四五六七八九十百千万]+[、..\s]*/, '')
|
||||
.trim();
|
||||
};
|
||||
@@ -926,6 +921,19 @@ const getAnchorMatchInfo = (text: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
const isCatalogueAnchorMatched = (nodeInfo: ReturnType<typeof getNodeMatchInfo>, headingText: string, strictSerial = true) => {
|
||||
const headingInfo = getAnchorMatchInfo(headingText);
|
||||
const titleMatched = nodeInfo.title ? isCatalogueTitleMatch(nodeInfo.title, headingInfo.title) : true;
|
||||
|
||||
if (nodeInfo.serial && strictSerial) {
|
||||
if (headingInfo.serial && headingInfo.serial !== nodeInfo.serial) return false;
|
||||
if (headingInfo.title && !titleMatched) return false;
|
||||
return Boolean(headingInfo.serial || headingInfo.title);
|
||||
}
|
||||
|
||||
return titleMatched;
|
||||
};
|
||||
|
||||
const getCatalogueNodeLevel = (nodes: TreeNode[], key: string, level = 1): number => {
|
||||
for (const item of nodes) {
|
||||
if (item.key === key) return level;
|
||||
@@ -937,6 +945,35 @@ const getCatalogueNodeLevel = (nodes: TreeNode[], key: string, level = 1): numbe
|
||||
return 0;
|
||||
};
|
||||
|
||||
const findCatalogueParentNode = (nodes: TreeNode[], key: string, parent: TreeNode | null = null): TreeNode | null => {
|
||||
for (const item of nodes) {
|
||||
if (item.key === key) return parent;
|
||||
if (item.children?.length) {
|
||||
const found = findCatalogueParentNode(item.children, key, item);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getHeadingLevel = (heading: HTMLElement) => {
|
||||
const level = Number(heading.tagName.replace(/^H/i, ''));
|
||||
return Number.isFinite(level) ? level : 0;
|
||||
};
|
||||
|
||||
const isHeadingInsideParentSection = (root: Element, heading: HTMLElement, parentHeading: HTMLElement | null) => {
|
||||
if (!parentHeading || heading === parentHeading) return true;
|
||||
|
||||
const headings = Array.from(root.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6'));
|
||||
const parentIndex = headings.indexOf(parentHeading);
|
||||
const headingIndex = headings.indexOf(heading);
|
||||
if (parentIndex < 0 || headingIndex <= parentIndex) return false;
|
||||
|
||||
const parentLevel = getHeadingLevel(parentHeading);
|
||||
const nextPeerIndex = headings.findIndex((item, index) => index > parentIndex && getHeadingLevel(item) <= parentLevel);
|
||||
return nextPeerIndex < 0 || headingIndex < nextPeerIndex;
|
||||
};
|
||||
|
||||
const resolveCatalogueHeadingSelector = (node: TreeNode) => {
|
||||
const levelFromTree = getCatalogueNodeLevel(catalogueData.value, node.key);
|
||||
if (levelFromTree === 2) return 'h2';
|
||||
@@ -956,6 +993,10 @@ const findCatalogueTargetElement = (node: TreeNode): HTMLElement | null => {
|
||||
const nodeInfo = getNodeMatchInfo(node);
|
||||
if (!nodeInfo.title && !nodeInfo.serial) return null;
|
||||
|
||||
const levelFromTree = getCatalogueNodeLevel(catalogueData.value, node.key);
|
||||
const parentNode = levelFromTree > 1 ? findCatalogueParentNode(catalogueData.value, node.key) : null;
|
||||
const parentHeading = parentNode ? findCatalogueTargetElement(parentNode) : null;
|
||||
|
||||
const headingSelector = resolveCatalogueHeadingSelector(node);
|
||||
const headings = Array.from(root.querySelectorAll<HTMLElement>(headingSelector));
|
||||
|
||||
@@ -963,18 +1004,19 @@ const findCatalogueTargetElement = (node: TreeNode): HTMLElement | null => {
|
||||
const headingText = normalizeCatalogueText(heading.textContent || '');
|
||||
if (!headingText) continue;
|
||||
|
||||
const headingInfo = getAnchorMatchInfo(headingText);
|
||||
const titleMatched = nodeInfo.title ? isCatalogueTitleMatch(nodeInfo.title, headingInfo.title) : true;
|
||||
|
||||
if (nodeInfo.serial) {
|
||||
if (headingInfo.serial && headingInfo.serial !== nodeInfo.serial) continue;
|
||||
if (headingInfo.title && !titleMatched) continue;
|
||||
if (!headingInfo.serial && !headingInfo.title) continue;
|
||||
} else if (!titleMatched) {
|
||||
continue;
|
||||
if (isCatalogueAnchorMatched(nodeInfo, headingText) && isHeadingInsideParentSection(root, heading, parentHeading)) {
|
||||
return heading;
|
||||
}
|
||||
}
|
||||
|
||||
return heading;
|
||||
const fallbackHeadings = Array.from(root.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6'));
|
||||
for (const heading of fallbackHeadings) {
|
||||
const headingText = normalizeCatalogueText(heading.textContent || '');
|
||||
if (!headingText) continue;
|
||||
|
||||
if (isCatalogueAnchorMatched(nodeInfo, headingText, false) && isHeadingInsideParentSection(root, heading, parentHeading)) {
|
||||
return heading;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1990,14 +2032,14 @@ onUnmounted(() => {
|
||||
<NFlex vertical :size="[0, 0]" class="contain compose-page-shell h-full w-full" :class="{ 'is-fullscreen': isPreviewFullscreen }">
|
||||
<section class="compose-view" aria-label="智慧标书编制内页">
|
||||
<aside class="compose-left-tools">
|
||||
<button class="compose-tool-button" type="button" @click="back"><span class="compose-tool-icon is-back"></span>返回</button>
|
||||
<button v-if="!isViewMode" class="compose-tool-button" type="button" @click="openCatalogueDialog">
|
||||
<button class="compose-tool-button is-back" type="button" @click="back"><span class="compose-tool-icon"></span>返回</button>
|
||||
<button v-if="!isViewMode" class="compose-tool-button is-template" type="button" @click="openCatalogueDialog">
|
||||
<span class="compose-tool-icon is-template"></span>选择模版
|
||||
</button>
|
||||
<button v-if="!isViewMode" class="compose-tool-button" type="button" :disabled="isGenerating" @click="handleGenerateDocument">
|
||||
<button v-if="!isViewMode" class="compose-tool-button is-generate" type="button" :disabled="isGenerating" @click="handleGenerateDocument">
|
||||
<span class="compose-tool-icon is-generate"></span>{{ isGenerating ? `生成中${generateProgress}%` : '生成文档' }}
|
||||
</button>
|
||||
<button class="compose-tool-button" type="button" :disabled="!canDownload" @click="handleDownloadDocument">
|
||||
<button class="compose-tool-button is-download" type="button" :disabled="!canDownload" @click="handleDownloadDocument">
|
||||
<span class="compose-tool-icon is-download"></span>下载文档
|
||||
</button>
|
||||
</aside>
|
||||
@@ -2047,11 +2089,10 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="compose-document-area">
|
||||
<aside v-show="!isLeftCollapsed" class="compose-toc">
|
||||
<aside class="compose-toc">
|
||||
<div class="compose-toc-content">
|
||||
<div class="compose-toc-title-row">
|
||||
<h3>目录</h3>
|
||||
<button type="button" @click="toggleLeftCollapsed">收起</button>
|
||||
</div>
|
||||
<div v-if="isJumpingToCatalogue" class="compose-toc-locating">
|
||||
<NSpin size="small" />
|
||||
@@ -2073,8 +2114,6 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<button v-show="isLeftCollapsed" class="compose-toc-expand" type="button" @click="toggleLeftCollapsed">展开目录</button>
|
||||
|
||||
<div ref="previewContainerRef" class="contain-main-wrapper-middle compose-doc-scroll">
|
||||
<Transition name="jump-indicator">
|
||||
<div v-if="isAwayFromActiveRewrite && activeRewriteTask" class="rewrite-away-tip">
|
||||
@@ -3246,28 +3285,28 @@ onUnmounted(() => {
|
||||
height: 24px;
|
||||
margin: 0 auto 4px;
|
||||
background-color: #b7bdc6;
|
||||
transition: color 0.16s ease;
|
||||
transition: background-color 0.16s ease;
|
||||
}
|
||||
|
||||
.compose-tool-icon {
|
||||
-webkit-mask: var(--tool-icon) center / 24px 24px no-repeat;
|
||||
mask: var(--tool-icon) center / 24px 24px no-repeat;
|
||||
-webkit-mask: var(--tool-icon) center / 20px 20px no-repeat;
|
||||
mask: var(--tool-icon) center / 20px 20px no-repeat;
|
||||
}
|
||||
|
||||
&.is-back {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 6 5 11.5l5.5 5.5 1.4-1.4-3.1-3.1H18a5 5 0 0 0 0-10h-1v2h1a3 3 0 0 1 0 6H8.8l3.1-3.1L10.5 6Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
&.is-back .compose-tool-icon {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.721558 9.05713L9.90522 4.03418L9.89694 13.9675L0.721558 9.05713Z'/%3E%3Cpath d='M9.91747 13.9997L9.88676 13.9833L0.680664 9.05657L9.92577 4L9.92577 4.03345L9.91747 13.9997ZM0.762483 9.05619L9.87645 13.9337L9.88472 4.06683L0.762483 9.05619Z'/%3E%3Cpath d='M18.6807 12.7514C18.6807 12.7514 18.484 11.9212 17.8363 11.4146C17.3395 11.026 17.0303 10.8069 16.3011 10.6854C14.4034 10.3691 5.74231 10.6864 5.74231 10.6864L5.74231 7.8C5.74231 7.8 11.4957 7.72844 13.3842 8.04199C13.9446 8.13501 14.9362 8.25487 15.6103 8.5281C16.337 8.82274 17.0461 9.24959 17.4141 9.59154C18.6682 10.7567 18.6807 12.7514 18.6807 12.7514Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&.is-template {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 4h7v7H4V4Zm2 2v3h3V6H6Zm7-2h7v7h-7V4Zm2 2v3h3V6h-3ZM4 13h7v7H4v-7Zm2 2v3h3v-3H6Zm7-2h7v7h-7v-7Zm2 2v3h3v-3h-3Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
&.is-template .compose-tool-icon {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.87456 3L16.1237 3C16.6078 3 16.999 3.34842 16.999 3.77826L16.999 6.11305C16.999 6.54201 16.606 6.89044 16.1237 6.89044L3.87544 6.89044C3.39044 6.89044 3 6.54201 3 6.11305L3 3.77826C3 3.3493 3.3922 3 3.87456 3ZM3.82729 8.44171L6.3074 8.44171C6.76437 8.44171 7.13468 8.82428 7.13468 9.29789L7.13468 16.1438C7.13468 16.6165 6.76437 17 6.3074 17L3.82729 17C3.37031 17 3 16.6183 3 16.1438L3 9.29789C3 8.82603 3.37031 8.44171 3.82729 8.44171ZM9.72773 8.44171L16.1919 8.44171C16.6384 8.44171 17 8.82428 17 9.29789L17 16.1438C17 16.6165 16.6384 17 16.1919 17L9.72773 17C9.28125 17 8.91882 16.6183 8.91882 16.1438L8.91882 9.29789C8.91882 8.82603 9.28125 8.44171 9.72773 8.44171Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&.is-generate {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 2 1.7 5.1L19 9l-5.3 1.9L12 16l-1.7-5.1L5 9l5.3-1.9L12 2Zm6.5 10 1 3 3 1-3 1-1 3-1-3-3-1 3-1 1-3ZM5.5 13l.8 2.2L8.5 16l-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8.8-2.2Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
&.is-generate .compose-tool-icon {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.3841 8.28056C12.2616 8.5165 12.3448 8.81025 12.5755 8.94931L12.5771 8.95087C12.6587 8.99931 12.7529 9.02431 12.8471 9.02431C12.891 9.02431 12.9365 9.01806 12.9805 9.00712C13.1155 8.97275 13.2316 8.88369 13.3007 8.76494L16.256 3.79462L16.2576 3.79306C16.4004 3.54931 16.3157 3.23212 16.0693 3.08837L16.0614 3.08368C15.8087 2.94462 15.4886 3.03212 15.3457 3.279L12.3919 8.26806L12.3841 8.28056Z'/%3E%3Cpath d='M16.0818 5.33125C16.0049 5.3 15.9155 5.32969 15.8731 5.40156L13.8327 8.83281C13.7291 9.00625 13.6585 9.14375 13.5549 9.25C13.4231 9.38437 13.2771 9.45312 13.0935 9.5C13.0087 9.52188 12.9224 9.53281 12.8361 9.53281C12.654 9.53281 12.472 9.48438 12.3135 9.39062L12.3087 9.3875C11.863 9.11875 11.6998 8.54844 11.9368 8.09062L11.9493 8.06719L13.4796 5.48281C13.5471 5.37031 13.4655 5.22656 13.3336 5.22656L11.0045 5.22656C10.6702 5.22656 10.1836 5.1625 10.0314 4.84531L9.33139 3.54063C9.17915 3.22187 8.94372 3 8.3583 3L4.45964 3C3.51166 3 3 3.69688 3 4.50156L3 15.4984C3 16.2812 3.56659 17 4.45964 17L15.7224 17C16.6155 17 17 16.4172 17 15.4984L17 6.72969C17 6.12031 16.6406 5.55625 16.0818 5.33125ZM10.2543 13.2203L5.18946 13.2203C4.78296 13.2203 4.51144 12.9531 4.51144 12.5516C4.51144 12.15 4.78296 11.8828 5.18946 11.8828L10.2558 11.8828C10.6623 11.8828 10.9339 12.15 10.9339 12.5516C10.9323 12.9531 10.6623 13.2203 10.2543 13.2203ZM14.8105 11.0328L5.18946 11.0328C4.78296 11.0328 4.51144 10.7656 4.51144 10.3641C4.51144 9.9625 4.78296 9.69531 5.18946 9.69531L14.8121 9.69531C15.2186 9.69531 15.4901 9.9625 15.4901 10.3641C15.4901 10.7656 15.2186 11.0328 14.8105 11.0328Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&.is-download {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 4h2v8.2l3.1-3.1 1.4 1.4L12 16l-5.5-5.5 1.4-1.4 3.1 3.1V4Zm-6 14h14v2H5v-2Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
&.is-download .compose-tool-icon {
|
||||
--tool-icon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.6667 1.66699C11.9167 1.66699 12.0834 1.75033 12.25 1.91699L16.4167 6.08367C16.5834 6.25033 16.6667 6.41699 16.6667 6.66699L16.6667 16.667C16.6667 17.5837 15.9167 18.3337 15 18.3337L5.00003 18.3337C4.08337 18.3337 3.33337 17.5837 3.33337 16.667L3.33337 3.33367C3.33337 2.41699 4.08337 1.66699 5.00003 1.66699L11.6667 1.66699ZM10 9.58367C9.50003 9.58367 9.16669 9.91699 9.16669 10.417L9.16669 12.5837L8.91669 12.3337C8.58337 12.0003 8.08337 12.0003 7.75003 12.3337C7.41669 12.667 7.41669 13.167 7.75003 13.5003L9.41669 15.167C9.75003 15.5003 10.25 15.5003 10.5834 15.167L12.25 13.5003C12.5834 13.167 12.5834 12.667 12.25 12.3337C11.9167 12.0003 11.4167 12.0003 11.0834 12.3337L10.8334 12.5837L10.8334 10.417C10.8334 9.91699 10.5 9.58367 10 9.58367ZM11.6667 3.66699L11.6667 6.66699L14.6667 6.66699L11.6667 3.66699Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
@@ -3419,15 +3458,15 @@ onUnmounted(() => {
|
||||
.compose-document-area {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
background: #ededed;
|
||||
padding-left: 210px;
|
||||
}
|
||||
|
||||
.compose-toc {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
left: 80px;
|
||||
top: 48px;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
width: 210px;
|
||||
@@ -3445,25 +3484,47 @@ onUnmounted(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree.n-tree) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree .n-tree-node) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree .n-tree-node-content) {
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree .n-tree-node-content:hover) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree .n-tree-node-switcher) {
|
||||
width: 16px;
|
||||
height: 24px;
|
||||
color: #8d949f;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-content__text) {
|
||||
color: #4f5668;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
:deep(.catalogue-tree .n-tree-node-children) {
|
||||
margin-left: 8px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid #dde2ec;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-toc-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #8a909b;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-toc-locating {
|
||||
@@ -3482,21 +3543,11 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.compose-toc-expand {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 18px;
|
||||
z-index: 22;
|
||||
height: 32px;
|
||||
border: 1px solid #d7dce8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: #4d5668;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.compose-doc-scroll {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: auto;
|
||||
background: #ededed;
|
||||
padding: 12px 0 70px;
|
||||
@@ -3551,6 +3602,7 @@ onUnmounted(() => {
|
||||
|
||||
.compose-toc {
|
||||
display: none;
|
||||
left: 64px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import FileUploadFolder from '@/components/custom/file-upload-folder.vue';
|
||||
@@ -33,6 +34,10 @@ const formData = reactive({
|
||||
});
|
||||
|
||||
const { routerPush } = useRouterPush();
|
||||
const route = useRoute();
|
||||
const isSmartProposalRoute = computed(() => {
|
||||
return ['/tech-proposal', '/smart-proposal/tech-proposal'].includes(route.path);
|
||||
});
|
||||
const isRecordPanelVisible = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
@@ -259,9 +264,17 @@ onMounted(() => {
|
||||
<div class="smart-bid-page" :class="{ 'compose-ready': formData.originalFileName, 'compose-processing': isSubmitting }">
|
||||
<div class="smart-bid-watermark" aria-hidden="true"></div>
|
||||
<header class="smart-bid-topbar">
|
||||
<button class="record-entry" :class="{ active: isRecordPanelVisible }" type="button" @click="openRecordDrawer">
|
||||
我的编制
|
||||
</button>
|
||||
<Teleport to="body">
|
||||
<button
|
||||
v-if="isSmartProposalRoute"
|
||||
class="record-entry"
|
||||
:class="{ active: isRecordPanelVisible }"
|
||||
type="button"
|
||||
@click="openRecordDrawer"
|
||||
>
|
||||
我的编制
|
||||
</button>
|
||||
</Teleport>
|
||||
</header>
|
||||
|
||||
<section class="compose-stage">
|
||||
@@ -420,6 +433,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.record-entry {
|
||||
position: fixed;
|
||||
left: calc(var(--soy-sider-width, 258px) + 20px);
|
||||
top: calc((var(--soy-header-height, 70px) - 36px) / 2);
|
||||
z-index: 102;
|
||||
display: inline-flex;
|
||||
width: 108px;
|
||||
height: 36px;
|
||||
@@ -462,6 +479,12 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.record-entry {
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.compose-stage {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
@@ -575,7 +575,7 @@ onMounted(async () => {
|
||||
<template>
|
||||
<div class="catalogue-config-panel">
|
||||
<div class="catalogue-config-body">
|
||||
<NForm label-placement="left" :label-width="86" :show-feedback="false" class="catalogue-config-form">
|
||||
<NForm label-placement="left" :label-width="104" :show-feedback="false" class="catalogue-config-form">
|
||||
<NFormItem label="目录模板">
|
||||
<NSelect
|
||||
v-model:value="selectedCatalogueId"
|
||||
@@ -684,11 +684,14 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
:deep(.n-form-item-label) {
|
||||
justify-content: flex-start;
|
||||
min-height: 34px;
|
||||
padding-right: 14px;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
line-height: 34px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.n-base-selection) {
|
||||
@@ -754,8 +757,11 @@ onMounted(async () => {
|
||||
.catalogue-tree-box {
|
||||
margin-top: 15px;
|
||||
padding-left: 2px;
|
||||
overflow-x: auto;
|
||||
|
||||
:deep(.n-tree) {
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -768,6 +774,7 @@ onMounted(async () => {
|
||||
min-height: 31px;
|
||||
padding: 0 6px;
|
||||
border-radius: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-switcher) {
|
||||
@@ -778,6 +785,8 @@ onMounted(async () => {
|
||||
:deep(.n-tree-node-content__text) {
|
||||
color: #1f2d3d;
|
||||
line-height: 31px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1652,8 +1652,9 @@ onMounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.system2-page {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
padding: 0 20px 40px;
|
||||
padding: 20px 20px 40px;
|
||||
color: #253047;
|
||||
background: #f5f7fb;
|
||||
--system2-border: #e3e8f1;
|
||||
@@ -1665,6 +1666,11 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.system2-hero {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* keep legacy rules below from affecting layout */
|
||||
.system2-hero--unused {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@@ -1730,6 +1736,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.system2-group-switch {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
@@ -1737,12 +1745,14 @@ onMounted(() => {
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-height: 50px;
|
||||
margin: -36px auto 10px;
|
||||
margin: 0 auto 24px;
|
||||
padding: 6px;
|
||||
border: 1px solid #e7e9ee;
|
||||
border-radius: 999px;
|
||||
background: rgba(244, 245, 247, 0.96);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 10px 24px rgba(22, 28, 42, 0.06);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9),
|
||||
0 10px 24px rgba(22, 28, 42, 0.06);
|
||||
}
|
||||
|
||||
.system2-group-switch button,
|
||||
@@ -2442,7 +2452,7 @@ onMounted(() => {
|
||||
|
||||
.system2-page--log {
|
||||
min-height: 100vh;
|
||||
padding: 0 20px 40px;
|
||||
padding: 20px 20px 40px;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
@@ -2451,8 +2461,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.system2-page--log .system2-group-switch {
|
||||
margin-top: -42px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.system2-page--log .system2-content-grid {
|
||||
|
||||