This commit is contained in:
@@ -2,6 +2,16 @@ import { useRouter } from 'vue-router';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { router as globalRouter } from '@/router';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
|
||||
function findMenuByKey(menus: App.Global.Menu[], key: string): App.Global.Menu | null {
|
||||
for (const menu of menus) {
|
||||
if (menu.key === key) return menu;
|
||||
const child = findMenuByKey(menu.children || [], key);
|
||||
if (child) return child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Router push
|
||||
@@ -38,7 +48,24 @@ export function useRouterPush(inSetup = true) {
|
||||
|
||||
function routerPushByKeyWithMetaQuery(key: RouteKey) {
|
||||
const allRoutes = router.getRoutes();
|
||||
const meta = allRoutes.find(item => item.name === key)?.meta || null;
|
||||
let targetRoute = allRoutes.find(item => item.name === key);
|
||||
|
||||
if (!targetRoute) {
|
||||
const routeStore = useRouteStore();
|
||||
const menu = findMenuByKey(routeStore.menus, key);
|
||||
targetRoute = allRoutes.find(item => item.name === menu?.routeKey);
|
||||
|
||||
if (!targetRoute && menu?.routePath) {
|
||||
return routerPush(menu.routePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetRoute) {
|
||||
console.warn(`[router] route key "${key}" not found`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const meta = targetRoute?.meta || null;
|
||||
|
||||
const query: Record<string, string> = {};
|
||||
|
||||
@@ -46,7 +73,7 @@ export function useRouterPush(inSetup = true) {
|
||||
query[item.key] = item.value;
|
||||
});
|
||||
|
||||
return routerPushByKey(key, { query });
|
||||
return routerPushByKey(targetRoute.name as RouteKey, { query });
|
||||
}
|
||||
|
||||
async function toHome() {
|
||||
|
||||
@@ -206,6 +206,18 @@ const local: App.I18n.Schema = {
|
||||
'iframe-page': 'Iframe',
|
||||
home: 'Home',
|
||||
system: 'System Management',
|
||||
system2: 'System Management 2',
|
||||
system2_user: 'User Management 2',
|
||||
system2_role: 'Role Management 2',
|
||||
system2_menu: 'Menu Management 2',
|
||||
system2_dept: 'Dept Management 2',
|
||||
system2_post: 'Post Management 2',
|
||||
system2_dict: 'Dict Management 2',
|
||||
system2_config: 'Config Management 2',
|
||||
system2_client: 'Client Management 2',
|
||||
system2_notice: 'Notice Management 2',
|
||||
system2_oss: 'File Management 2',
|
||||
system2_log: 'Log Center 2',
|
||||
system_menu: 'Menu Management',
|
||||
tool: 'System Tools',
|
||||
tool_gen: 'Code Generation',
|
||||
|
||||
@@ -206,6 +206,18 @@ const local: App.I18n.Schema = {
|
||||
'iframe-page': '外链页面',
|
||||
home: '首页',
|
||||
system: '系统管理',
|
||||
system2: '系统管理2',
|
||||
system2_user: '用户管理2',
|
||||
system2_role: '角色管理2',
|
||||
system2_menu: '菜单管理2',
|
||||
system2_dept: '部门管理2',
|
||||
system2_post: '岗位管理2',
|
||||
system2_dict: '字典管理2',
|
||||
system2_config: '参数设置2',
|
||||
system2_client: '客户端管理2',
|
||||
system2_notice: '通知公告2',
|
||||
system2_oss: '文件管理2',
|
||||
system2_log: '日志中心2',
|
||||
system_menu: '菜单管理',
|
||||
tool: '系统工具',
|
||||
tool_gen: '代码生成',
|
||||
|
||||
@@ -74,5 +74,16 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
"system_tenant-package": () => import("@/views/system/tenant-package/index.vue"),
|
||||
system_tenant: () => import("@/views/system/tenant/index.vue"),
|
||||
system_user: () => import("@/views/system/user/index.vue"),
|
||||
system2_client: () => import("@/views/system2/client/index.vue"),
|
||||
system2_config: () => import("@/views/system2/config/index.vue"),
|
||||
system2_dept: () => import("@/views/system2/dept/index.vue"),
|
||||
system2_dict: () => import("@/views/system2/dict/index.vue"),
|
||||
system2_log: () => import("@/views/system2/log/index.vue"),
|
||||
system2_menu: () => import("@/views/system2/menu/index.vue"),
|
||||
system2_notice: () => import("@/views/system2/notice/index.vue"),
|
||||
system2_oss: () => import("@/views/system2/oss/index.vue"),
|
||||
system2_post: () => import("@/views/system2/post/index.vue"),
|
||||
system2_role: () => import("@/views/system2/role/index.vue"),
|
||||
system2_user: () => import("@/views/system2/user/index.vue"),
|
||||
tool_gen: () => import("@/views/tool/gen/index.vue"),
|
||||
};
|
||||
|
||||
@@ -695,6 +695,121 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'system2',
|
||||
path: '/system2',
|
||||
component: 'layout.base',
|
||||
redirect: {
|
||||
name: 'system2_user'
|
||||
},
|
||||
meta: {
|
||||
title: 'system2',
|
||||
i18nKey: 'route.system2',
|
||||
localIcon: 'menu-system',
|
||||
order: 2
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'system2_client',
|
||||
path: '/system2/client',
|
||||
component: 'view.system2_client',
|
||||
meta: {
|
||||
title: 'system2_client',
|
||||
i18nKey: 'route.system2_client'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_config',
|
||||
path: '/system2/config',
|
||||
component: 'view.system2_config',
|
||||
meta: {
|
||||
title: 'system2_config',
|
||||
i18nKey: 'route.system2_config'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_dept',
|
||||
path: '/system2/dept',
|
||||
component: 'view.system2_dept',
|
||||
meta: {
|
||||
title: 'system2_dept',
|
||||
i18nKey: 'route.system2_dept'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_dict',
|
||||
path: '/system2/dict',
|
||||
component: 'view.system2_dict',
|
||||
meta: {
|
||||
title: 'system2_dict',
|
||||
i18nKey: 'route.system2_dict'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_log',
|
||||
path: '/system2/log',
|
||||
component: 'view.system2_log',
|
||||
meta: {
|
||||
title: 'system2_log',
|
||||
i18nKey: 'route.system2_log'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_menu',
|
||||
path: '/system2/menu',
|
||||
component: 'view.system2_menu',
|
||||
meta: {
|
||||
title: 'system2_menu',
|
||||
i18nKey: 'route.system2_menu'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_notice',
|
||||
path: '/system2/notice',
|
||||
component: 'view.system2_notice',
|
||||
meta: {
|
||||
title: 'system2_notice',
|
||||
i18nKey: 'route.system2_notice'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_oss',
|
||||
path: '/system2/oss',
|
||||
component: 'view.system2_oss',
|
||||
meta: {
|
||||
title: 'system2_oss',
|
||||
i18nKey: 'route.system2_oss'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_post',
|
||||
path: '/system2/post',
|
||||
component: 'view.system2_post',
|
||||
meta: {
|
||||
title: 'system2_post',
|
||||
i18nKey: 'route.system2_post'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_role',
|
||||
path: '/system2/role',
|
||||
component: 'view.system2_role',
|
||||
meta: {
|
||||
title: 'system2_role',
|
||||
i18nKey: 'route.system2_role'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2_user',
|
||||
path: '/system2/user',
|
||||
component: 'view.system2_user',
|
||||
meta: {
|
||||
title: 'system2_user',
|
||||
i18nKey: 'route.system2_user'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'tool',
|
||||
path: '/tool',
|
||||
|
||||
@@ -233,6 +233,18 @@ const routeMap: RouteMap = {
|
||||
"system_tenant": "/system/tenant",
|
||||
"system_tenant-package": "/system/tenant-package",
|
||||
"system_user": "/system/user",
|
||||
"system2": "/system2",
|
||||
"system2_client": "/system2/client",
|
||||
"system2_config": "/system2/config",
|
||||
"system2_dept": "/system2/dept",
|
||||
"system2_dict": "/system2/dict",
|
||||
"system2_log": "/system2/log",
|
||||
"system2_menu": "/system2/menu",
|
||||
"system2_notice": "/system2/notice",
|
||||
"system2_oss": "/system2/oss",
|
||||
"system2_post": "/system2/post",
|
||||
"system2_role": "/system2/role",
|
||||
"system2_user": "/system2/user",
|
||||
"tool": "/tool",
|
||||
"tool_gen": "/tool/gen",
|
||||
"user-center": "/user-center"
|
||||
|
||||
@@ -139,6 +139,85 @@ const dynamicSupplementAuthRoutes: ElegantConstRoute[] = [
|
||||
hideInMenu: true,
|
||||
activeMenu: 'smart-proposal_tech-proposal'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'system2',
|
||||
path: '/system2',
|
||||
component: 'layout.base',
|
||||
redirect: { name: 'system2_user' },
|
||||
meta: {
|
||||
title: 'system2',
|
||||
i18nKey: 'route.system2',
|
||||
hideInMenu: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'system2_user',
|
||||
path: '/system2/user',
|
||||
component: 'view.system2_user',
|
||||
meta: { title: 'system2_user', i18nKey: 'route.system2_user', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_role',
|
||||
path: '/system2/role',
|
||||
component: 'view.system2_role',
|
||||
meta: { title: 'system2_role', i18nKey: 'route.system2_role', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_menu',
|
||||
path: '/system2/menu',
|
||||
component: 'view.system2_menu',
|
||||
meta: { title: 'system2_menu', i18nKey: 'route.system2_menu', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_dept',
|
||||
path: '/system2/dept',
|
||||
component: 'view.system2_dept',
|
||||
meta: { title: 'system2_dept', i18nKey: 'route.system2_dept', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_post',
|
||||
path: '/system2/post',
|
||||
component: 'view.system2_post',
|
||||
meta: { title: 'system2_post', i18nKey: 'route.system2_post', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_dict',
|
||||
path: '/system2/dict',
|
||||
component: 'view.system2_dict',
|
||||
meta: { title: 'system2_dict', i18nKey: 'route.system2_dict', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_config',
|
||||
path: '/system2/config',
|
||||
component: 'view.system2_config',
|
||||
meta: { title: 'system2_config', i18nKey: 'route.system2_config', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_client',
|
||||
path: '/system2/client',
|
||||
component: 'view.system2_client',
|
||||
meta: { title: 'system2_client', i18nKey: 'route.system2_client', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_notice',
|
||||
path: '/system2/notice',
|
||||
component: 'view.system2_notice',
|
||||
meta: { title: 'system2_notice', i18nKey: 'route.system2_notice', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_oss',
|
||||
path: '/system2/oss',
|
||||
component: 'view.system2_oss',
|
||||
meta: { title: 'system2_oss', i18nKey: 'route.system2_oss', hideInMenu: true }
|
||||
},
|
||||
{
|
||||
name: 'system2_log',
|
||||
path: '/system2/log',
|
||||
component: 'view.system2_log',
|
||||
meta: { title: 'system2_log', i18nKey: 'route.system2_log', hideInMenu: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ export function fetchBatchDeleteDept(deptIds: CommonType.IdType[]) {
|
||||
}
|
||||
|
||||
/** 获取部门选择框列表 */
|
||||
export function fetchGetDeptSelect() {
|
||||
export function fetchGetDeptSelect(deptIds?: CommonType.IdType[]) {
|
||||
return request<Api.System.Dept[]>({
|
||||
url: '/system/dept/optionselect',
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params: { deptIds }
|
||||
});
|
||||
}
|
||||
|
||||
24
src/typings/elegant-router.d.ts
vendored
24
src/typings/elegant-router.d.ts
vendored
@@ -87,6 +87,18 @@ declare module "@elegant-router/types" {
|
||||
"system_tenant": "/system/tenant";
|
||||
"system_tenant-package": "/system/tenant-package";
|
||||
"system_user": "/system/user";
|
||||
"system2": "/system2";
|
||||
"system2_client": "/system2/client";
|
||||
"system2_config": "/system2/config";
|
||||
"system2_dept": "/system2/dept";
|
||||
"system2_dict": "/system2/dict";
|
||||
"system2_log": "/system2/log";
|
||||
"system2_menu": "/system2/menu";
|
||||
"system2_notice": "/system2/notice";
|
||||
"system2_oss": "/system2/oss";
|
||||
"system2_post": "/system2/post";
|
||||
"system2_role": "/system2/role";
|
||||
"system2_user": "/system2/user";
|
||||
"tool": "/tool";
|
||||
"tool_gen": "/tool/gen";
|
||||
"user-center": "/user-center";
|
||||
@@ -142,6 +154,7 @@ declare module "@elegant-router/types" {
|
||||
| "social-callback"
|
||||
| "statistical-analysis"
|
||||
| "system"
|
||||
| "system2"
|
||||
| "tool"
|
||||
| "user-center"
|
||||
>;
|
||||
@@ -220,6 +233,17 @@ declare module "@elegant-router/types" {
|
||||
| "system_tenant-package"
|
||||
| "system_tenant"
|
||||
| "system_user"
|
||||
| "system2_client"
|
||||
| "system2_config"
|
||||
| "system2_dept"
|
||||
| "system2_dict"
|
||||
| "system2_log"
|
||||
| "system2_menu"
|
||||
| "system2_notice"
|
||||
| "system2_oss"
|
||||
| "system2_post"
|
||||
| "system2_role"
|
||||
| "system2_user"
|
||||
| "tool_gen"
|
||||
>;
|
||||
|
||||
|
||||
@@ -169,8 +169,8 @@ watch(fileList, value => {
|
||||
<div class="watermark" aria-hidden="true"></div>
|
||||
<div class="contain-wrapper">
|
||||
<button class="history-button" type="button" @click="openHistoryList">
|
||||
<SvgIcon icon="material-symbols:history-rounded" />
|
||||
历史记录
|
||||
<span class="history-clock" aria-hidden="true">◷</span>
|
||||
<span class="history-text">历史记录</span>
|
||||
</button>
|
||||
|
||||
<section class="hero-panel">
|
||||
@@ -317,10 +317,26 @@ watch(fileList, value => {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.history-button :deep(.svg-icon) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.history-button::before,
|
||||
.history-button::after {
|
||||
display: none !important;
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
.history-button :deep(svg),
|
||||
.history-button :deep(.svg-icon),
|
||||
.history-button :deep(.iconify) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.history-clock {
|
||||
color: #20242d;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.history-text {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
|
||||
@@ -14,7 +14,6 @@ const {
|
||||
detailFixedCount,
|
||||
detailIssueCount,
|
||||
detailLoading,
|
||||
detailPageCount,
|
||||
detailPagination,
|
||||
detailRowProps,
|
||||
detailSelectedRowId,
|
||||
@@ -276,7 +275,6 @@ const {
|
||||
v-model:page-size="detailPagination.pageSize"
|
||||
class="detail-pagination"
|
||||
:item-count="detailTotal"
|
||||
:page-count="detailPageCount"
|
||||
:page-sizes="[20, 50]"
|
||||
show-size-picker
|
||||
show-quick-jumper
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,25 +7,50 @@ const {
|
||||
analysisProposalFileIds,
|
||||
analysisProposalOptions,
|
||||
analysisReferenceData,
|
||||
analysisResultColumns,
|
||||
analysisResultData,
|
||||
analysisResultLoading,
|
||||
analysisRuleJsonSetting,
|
||||
analysisScopeOptions,
|
||||
analysisStat,
|
||||
btnLoading,
|
||||
deleteAnalysisFile,
|
||||
exportAnalysisResult,
|
||||
openAnalysisInfo,
|
||||
openAnalysisSmartFilter,
|
||||
reAnalysisRegularAnalysis,
|
||||
restoreAnalysisDocument,
|
||||
rewriteRegularAnalysis,
|
||||
startRegularAnalysis
|
||||
} = useTechProposalRedesignContext();
|
||||
|
||||
const toggleAnalysisCheckedRow = (row: any, checked: boolean) => {
|
||||
const key = row.id;
|
||||
analysisCheckedRowKeys.value = checked
|
||||
? Array.from(new Set([...analysisCheckedRowKeys.value, key]))
|
||||
: analysisCheckedRowKeys.value.filter((item: any) => item !== key);
|
||||
};
|
||||
|
||||
const isAnalysisChecked = (row: any) => analysisCheckedRowKeys.value.includes(row.id);
|
||||
|
||||
const getAnalysisStatusClass = (status?: string) => {
|
||||
if (status?.includes('完成')) return 'success';
|
||||
if (status?.includes('正在')) return 'pending';
|
||||
if (status?.includes('失败')) return 'failed';
|
||||
return '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="regular-analysis-workbench">
|
||||
<section class="regular-config-panel">
|
||||
<header class="regular-config-toolbar">
|
||||
<div class="regular-panel-title">
|
||||
<span class="regular-title-marker"></span>
|
||||
<div>
|
||||
<strong>纪律性分析规则</strong>
|
||||
<p>选择参照与目标文件,按文本、图片、文件属性维度识别雷同风险</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="review-start-check" type="button" :disabled="btnLoading" @click="startRegularAnalysis">
|
||||
{{ btnLoading ? '分析中' : '开始分析' }}
|
||||
</button>
|
||||
@@ -43,6 +68,7 @@ const {
|
||||
label-field="name"
|
||||
value-field="id"
|
||||
children-field="dtlList"
|
||||
to="body"
|
||||
:options="analysisProposalOptions"
|
||||
/>
|
||||
</label>
|
||||
@@ -56,63 +82,66 @@ const {
|
||||
label-field="name"
|
||||
value-field="id"
|
||||
children-field="dtlList"
|
||||
to="body"
|
||||
:options="analysisProposalOptions"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<section class="regular-setting-card design-card">
|
||||
<header>
|
||||
<strong>文本</strong>
|
||||
<span>TEXT</span>
|
||||
</header>
|
||||
<div class="regular-inline-rule design-inline-rule">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.checkDuplicateContentEnable">重复内容</NCheckbox>
|
||||
<span>>=</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.checkDuplicateContent"
|
||||
:options="analysisPercentOptions"
|
||||
size="small"
|
||||
/>
|
||||
<span>的</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.splitParagraphs"
|
||||
:options="analysisScopeOptions"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.keyInformation">重点信息</NCheckbox>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.smartFilterEnable">智能过滤</NCheckbox>
|
||||
<button type="button" @click="openAnalysisSmartFilter">设置</button>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
</section>
|
||||
<div class="regular-rule-grid">
|
||||
<section class="regular-setting-card design-card featured">
|
||||
<header>
|
||||
<strong>文本</strong>
|
||||
<span>TEXT</span>
|
||||
</header>
|
||||
<div class="regular-inline-rule design-inline-rule">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.checkDuplicateContentEnable">重复内容</NCheckbox>
|
||||
<span>>=</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.checkDuplicateContent"
|
||||
:options="analysisPercentOptions"
|
||||
size="small"
|
||||
/>
|
||||
<span>的</span>
|
||||
<NSelect
|
||||
v-model:value="analysisRuleJsonSetting.splitParagraphs"
|
||||
:options="analysisScopeOptions"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.keyInformation">重点信息</NCheckbox>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.smartFilterEnable">智能过滤</NCheckbox>
|
||||
<button type="button" @click="openAnalysisSmartFilter">设置</button>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="regular-setting-card design-card">
|
||||
<header>
|
||||
<strong>图片</strong>
|
||||
<span>IMAGE</span>
|
||||
</header>
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.image.similar">相似图片</NCheckbox>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.image.sameText">图片中相同文字及重点信息</NCheckbox>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
</section>
|
||||
<section class="regular-setting-card design-card">
|
||||
<header>
|
||||
<strong>图片</strong>
|
||||
<span>IMAGE</span>
|
||||
</header>
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.image.similar">相似图片</NCheckbox>
|
||||
<div class="regular-ai-line">
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.image.sameText">图片中相同文字及重点信息</NCheckbox>
|
||||
<em>AI</em>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="regular-setting-card design-card compact-card">
|
||||
<header>
|
||||
<strong>文件属性</strong>
|
||||
<span>METADATA</span>
|
||||
</header>
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.checkFileProperties">
|
||||
相同作者、最后一次保存者及公司
|
||||
</NCheckbox>
|
||||
</section>
|
||||
<section class="regular-setting-card design-card compact-card">
|
||||
<header>
|
||||
<strong>文件属性</strong>
|
||||
<span>METADATA</span>
|
||||
</header>
|
||||
<NCheckbox v-model:checked="analysisRuleJsonSetting.checkFileProperties">
|
||||
相同作者、最后一次保存者及公司
|
||||
</NCheckbox>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -130,30 +159,106 @@ const {
|
||||
已修复
|
||||
<strong>{{ analysisStat.rewriteDocCount }}</strong>
|
||||
</div>
|
||||
<div class="regular-design-actions">
|
||||
<button type="button" @click="rewriteRegularAnalysis">一键改写</button>
|
||||
<button type="button" @click="restoreAnalysisDocument">还原文档</button>
|
||||
<button type="button" @click="exportAnalysisResult">导出文档</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="regular-result-area">
|
||||
<div v-if="!analysisResultLoading && analysisResultData.length === 0" class="regular-empty-result">
|
||||
<strong>暂无检查结果</strong>
|
||||
<p>点击上方 “开始分析” 后生成结果</p>
|
||||
</div>
|
||||
<div v-else class="regular-result-scroll">
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="analysisCheckedRowKeys"
|
||||
:row-key="(row: any) => row.id"
|
||||
:columns="analysisResultColumns"
|
||||
:data="analysisResultData"
|
||||
:bordered="false"
|
||||
:loading="analysisResultLoading"
|
||||
:single-line="false"
|
||||
:scroll-x="1124"
|
||||
class="regular-data-table regular-result-detail-table"
|
||||
/>
|
||||
</div>
|
||||
<div class="regular-result-area design-result-stack">
|
||||
<section class="design-table-block regular-reference-block">
|
||||
<header>
|
||||
<strong>参照投标文件</strong>
|
||||
<button
|
||||
class="regular-reanalyze-button"
|
||||
type="button"
|
||||
:disabled="btnLoading || analysisResultData.length <= 0"
|
||||
@click="reAnalysisRegularAnalysis"
|
||||
>
|
||||
{{ btnLoading ? '分析中' : '重新分析' }}
|
||||
</button>
|
||||
</header>
|
||||
<div class="design-table-scroll">
|
||||
<table class="design-reference-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>参照单位</th>
|
||||
<th>参照文件名称</th>
|
||||
<th>更新时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in analysisReferenceData" :key="`${item.proposalDtlId1 || item.id}-${index}`">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ item.proposal1Name || '-' }}</td>
|
||||
<td>{{ item.proposalDtl1Name || '-' }}</td>
|
||||
<td>{{ item.updateTime || '-' }}</td>
|
||||
</tr>
|
||||
<tr v-if="!analysisReferenceData.length">
|
||||
<td colspan="4" class="design-table-empty">暂无参照文件</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="design-table-block regular-analysis-result-block">
|
||||
<header>
|
||||
<strong>分析结果详情</strong>
|
||||
<div class="regular-design-actions">
|
||||
<button type="button" @click="rewriteRegularAnalysis">一键改写</button>
|
||||
<button type="button" @click="restoreAnalysisDocument">还原文档</button>
|
||||
<button type="button" @click="exportAnalysisResult">导出文档</button>
|
||||
</div>
|
||||
</header>
|
||||
<div v-if="!analysisResultLoading && analysisResultData.length === 0" class="regular-empty-result">
|
||||
<strong>暂无检查结果</strong>
|
||||
<p>点击左侧 “开始分析” 后生成结果</p>
|
||||
</div>
|
||||
<div v-else class="regular-result-scroll">
|
||||
<table class="design-analysis-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>序号</th>
|
||||
<th>目标单位</th>
|
||||
<th>风险度</th>
|
||||
<th>文本相似度(%)</th>
|
||||
<th>图片相似度(%)</th>
|
||||
<th>文件属性雷同</th>
|
||||
<th>重点信息雷同(项)</th>
|
||||
<th>检查状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in analysisResultData" :key="item.id">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="isAnalysisChecked(item)"
|
||||
@change="toggleAnalysisCheckedRow(item, ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ item.proposal2Name || '-' }}</td>
|
||||
<td>
|
||||
<strong class="analysis-risk-value">{{ item.riskDegree ?? 0 }}</strong>
|
||||
</td>
|
||||
<td>{{ item.textSimilarity ?? 0 }}</td>
|
||||
<td>{{ item.imgSimilarity ?? 0 }}</td>
|
||||
<td>{{ item.docSimilarity ?? 0 }}</td>
|
||||
<td>{{ item.informationSimilarity ?? 0 }}</td>
|
||||
<td>
|
||||
<span class="analysis-status" :class="getAnalysisStatusClass(item.taskStatus)">
|
||||
{{ item.taskStatus || '未审查' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="design-analysis-actions-cell">
|
||||
<button type="button" @click.stop="openAnalysisInfo(item, true)">查看详情</button>
|
||||
<button class="danger" type="button" @click.stop="deleteAnalysisFile(item.proposalDtlId2)">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useTechProposalRedesignContext } from '../context';
|
||||
|
||||
const {
|
||||
appStore,
|
||||
batchDeleteResponseModule,
|
||||
btnLoading,
|
||||
collapseAllResponseRules,
|
||||
@@ -19,18 +18,20 @@ const {
|
||||
responseGenerateLoading,
|
||||
responseHistoryBidding,
|
||||
responseHistoryCards,
|
||||
responseCheckedKeys,
|
||||
responseResultCompanyColumns,
|
||||
responseResultLoading,
|
||||
responseResultScrollX,
|
||||
responseResultTableRows,
|
||||
responseReviewStatus,
|
||||
responseRuleTableRows,
|
||||
responseSourceTab,
|
||||
responseStartCheck,
|
||||
responseStatisticData,
|
||||
saveResponseRuleData,
|
||||
selectedResponseDocName,
|
||||
toggleResponseRuleExpand,
|
||||
updateResponseCheckedKeys
|
||||
toggleResponseCheckedKey,
|
||||
toggleResponseRuleExpand
|
||||
} = useTechProposalRedesignContext();
|
||||
</script>
|
||||
|
||||
@@ -156,7 +157,11 @@ const {
|
||||
{{ row.expanded ? '⌄' : '〉' }}
|
||||
</button>
|
||||
<label>
|
||||
<input type="checkbox" checked @change="updateResponseCheckedKeys([row.id])" />
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="responseCheckedKeys.includes(row.id)"
|
||||
@change="toggleResponseCheckedKey(row.id, ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
{{ row.name }}
|
||||
</label>
|
||||
<p v-if="row.scoringStandard" class="response-rule-desc">
|
||||
@@ -195,7 +200,10 @@ const {
|
||||
</div>
|
||||
</header>
|
||||
<div class="response-result-table">
|
||||
<div v-if="responseResultTableRows.length > 0" class="response-result-native-scroll">
|
||||
<div
|
||||
v-if="responseResultTableRows.length > 0 && !responseReviewStatus.includes('未审查')"
|
||||
class="response-result-native-scroll"
|
||||
>
|
||||
<table class="review-response-result-table" :style="{ minWidth: `${responseResultScrollX}px` }">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -216,12 +224,18 @@ const {
|
||||
<span v-if="row[column.key] === true" class="response-pass-dot">✓</span>
|
||||
<span v-else-if="row[column.key] === false" class="response-fail-dot">×</span>
|
||||
</td>
|
||||
<td><span class="response-fail-pill">{{ row.taskStatus || '审查失败' }}</span></td>
|
||||
<td><span class="response-fail-pill">{{ row.taskStatus || responseReviewStatus }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<NEmpty v-if="!responseResultLoading && responseResultTableRows.length === 0" description="暂无检查结果" />
|
||||
<div
|
||||
v-if="!responseResultLoading && (responseResultTableRows.length === 0 || responseReviewStatus.includes('未审查'))"
|
||||
class="response-result-empty-state"
|
||||
>
|
||||
<strong>暂无检查结果</strong>
|
||||
<p>点击上方“开始检查”后生成结果</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -234,6 +248,29 @@ const {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.response-result-empty-state {
|
||||
display: grid;
|
||||
min-height: 72px;
|
||||
place-items: center;
|
||||
border: 1px dashed #d7dfec;
|
||||
border-radius: 8px;
|
||||
color: #6b7485;
|
||||
margin: 8px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.response-result-empty-state strong {
|
||||
color: #2e3543;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.response-result-empty-state p {
|
||||
margin: 8px 0 0;
|
||||
color: #7a8394;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.review-response-result-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
@@ -55,17 +55,9 @@ const back = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical :size="[0, 20]" class="contain h-full w-full bg-#F1F2F6">
|
||||
<div class="contain-header flex items-center flex-justify-start bg-white">
|
||||
<div class="flex cursor-pointer items-center px-25px py-12px" @click="back">
|
||||
<NButton text>
|
||||
<NIcon class="text-21px">
|
||||
<icon-local-back-icon class="block text-21px" />
|
||||
</NIcon>
|
||||
</NButton>
|
||||
<NText class="ml-2 text-3.5 color-#333333">返回上一步</NText>
|
||||
</div>
|
||||
<div class="contain-header-tab ml-40px h-100% flex">
|
||||
<NFlex vertical :size="[0, 0]" class="contain h-full w-full bg-#f6f8fc">
|
||||
<div class="contain-header flex items-center justify-between bg-white">
|
||||
<div class="contain-header-tab h-100% flex">
|
||||
<div
|
||||
v-for="item in tabList"
|
||||
:key="item.value"
|
||||
@@ -76,6 +68,9 @@ const back = () => {
|
||||
<NText :class="item.value === currentTab ? 'text-#1e5ef9' : 'text-#333'">{{ item.label }}</NText>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contain-header-actions">
|
||||
<button class="cancel" type="button" @click="back">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contain-main flex-1">
|
||||
<div class="contain-main-wrapper h-100% w-100%">
|
||||
@@ -106,19 +101,233 @@ const back = () => {
|
||||
<style lang="scss" scoped>
|
||||
.contain {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
box-shadow: 0rem 0.13rem 0.25rem rgba(122, 122, 122, 0.25);
|
||||
height: 50px;
|
||||
flex: 0 0 50px;
|
||||
border-bottom: 1px solid #dfe6f2;
|
||||
background: #fffaf4 !important;
|
||||
box-shadow: none;
|
||||
padding: 0 18px 0 40px;
|
||||
|
||||
&-tab {
|
||||
.active-item {
|
||||
background-color: rgba($color: #1e5ef9, $alpha: 0.1);
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
font-weight: 700;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: #1e5ef9;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-tab-item {
|
||||
min-width: 64px;
|
||||
justify-content: center;
|
||||
padding: 0 18px !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
min-width: 64px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
border: 1px solid #ffd9a9;
|
||||
background: #fff5e8;
|
||||
color: #bd6a18;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-main {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 14px 10px 16px;
|
||||
background: #f6f8fc;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.module) {
|
||||
height: 100% !important;
|
||||
padding-bottom: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper) {
|
||||
box-sizing: border-box;
|
||||
height: 100% !important;
|
||||
gap: 14px !important;
|
||||
padding: 0 14px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider) {
|
||||
width: 230px !important;
|
||||
flex: 0 0 230px !important;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 4px !important;
|
||||
background: #fff !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-search) {
|
||||
padding: 14px 14px 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-search .n-input) {
|
||||
--n-height: 32px !important;
|
||||
--n-border-radius: 6px !important;
|
||||
--n-border: 1px solid #dbe3ef !important;
|
||||
--n-border-hover: 1px solid #b8c8e6 !important;
|
||||
--n-border-focus: 1px solid #2d66ff !important;
|
||||
--n-box-shadow-focus: none !important;
|
||||
background: #fff !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-header) {
|
||||
height: 104px !important;
|
||||
border-bottom: 1px solid #e8edf5;
|
||||
border-radius: 0 !important;
|
||||
background: #eef5ff !important;
|
||||
background-image: none !important;
|
||||
padding: 14px 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content) {
|
||||
margin-top: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card) {
|
||||
border-radius: 0 !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card-header) {
|
||||
min-height: 42px;
|
||||
border-bottom: 1px solid #e8edf5;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card-header__main) {
|
||||
color: #243044 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card__content) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-data-table-th) {
|
||||
height: 36px;
|
||||
background: #f7f8fb !important;
|
||||
border-bottom: 1px solid #e8edf5 !important;
|
||||
color: #0f1e36 !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-data-table-td) {
|
||||
height: 34px;
|
||||
border-bottom: 1px solid #edf1f6 !important;
|
||||
color: #0f1e36 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .selected-row),
|
||||
:deep(.module-wrapper-sider-content .selected-row td) {
|
||||
background: #eaf2ff !important;
|
||||
color: #1f62ff !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content) {
|
||||
min-width: 0;
|
||||
flex: 1 1 0 !important;
|
||||
width: auto !important;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 0 !important;
|
||||
background: #fff !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-header) {
|
||||
height: 34px !important;
|
||||
border-bottom: 1px solid #e8edf5 !important;
|
||||
background: #fff !important;
|
||||
padding-left: 76px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-header .n-text) {
|
||||
color: #0f1e36 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card .gradient-1::before) {
|
||||
width: auto !important;
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
top: 5px !important;
|
||||
left: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
background: linear-gradient(180deg, #5f90ff 0%, #3064df 100%) !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card .gradient-2::before) {
|
||||
width: auto !important;
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
top: 5px !important;
|
||||
left: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
background: linear-gradient(180deg, #ffbe62 0%, #ff8d1f 100%) !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content-title) {
|
||||
height: 34px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
background: #fff !important;
|
||||
padding: 0 10px !important;
|
||||
color: #5e6777;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content-body) {
|
||||
border-radius: 0 !important;
|
||||
background: #737373;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
:deep(.module-footer) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { DocConvertTask } from '@/service/api/tech-proposal';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import ImportantInfo from './modules/important-info copy.vue';
|
||||
import TextContent from './modules/text-content copy.vue';
|
||||
import ImageContent from './modules/image-content.vue';
|
||||
import type { DocConvertTask } from '@/service/api/tech-proposal';
|
||||
defineOptions({
|
||||
name: 'ReviewProposal'
|
||||
});
|
||||
@@ -54,6 +54,7 @@ watch(
|
||||
);
|
||||
const { routerBack } = useRouterPush();
|
||||
const currentTab = ref('ImportantInfo');
|
||||
const textContentRef = ref<InstanceType<typeof TextContent>>();
|
||||
const tabList = ref([
|
||||
{
|
||||
label: '重点信息',
|
||||
@@ -81,20 +82,16 @@ const back = () => {
|
||||
window.sessionStorage.setItem('review-proposal-tab', 'RegularAnalysis');
|
||||
routerBack();
|
||||
};
|
||||
|
||||
const saveTextContent = () => {
|
||||
textContentRef.value?.handleConfirm?.();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical :size="[0, 20]" class="contain h-full w-full bg-#F1F2F6">
|
||||
<div class="contain-header flex items-center flex-justify-start bg-white">
|
||||
<div class="flex items-center px-25px py-12px">
|
||||
<NButton text>
|
||||
<NIcon class="text-21px" @click="back">
|
||||
<icon-local-back-icon class="block text-21px" />
|
||||
</NIcon>
|
||||
</NButton>
|
||||
<NText class="ml-2 text-3.5 color-#333333">返回上一步</NText>
|
||||
</div>
|
||||
<div class="contain-header-tab ml-40px h-100% flex">
|
||||
<NFlex vertical :size="[0, 0]" class="contain h-full w-full bg-#f6f8fc">
|
||||
<div class="contain-header flex items-center justify-between bg-white">
|
||||
<div class="contain-header-tab h-100% flex">
|
||||
<div
|
||||
v-for="item in tabList"
|
||||
:key="item.value"
|
||||
@@ -105,6 +102,12 @@ const back = () => {
|
||||
<NText :class="item.value === currentTab ? 'text-#1e5ef9' : 'text-#333'">{{ item.label }}</NText>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contain-header-actions">
|
||||
<button v-if="currentTab === 'TextContent'" class="save" type="button" @click="saveTextContent">
|
||||
确定保存
|
||||
</button>
|
||||
<button class="cancel" type="button" @click="back">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contain-main flex-1">
|
||||
<div class="contain-main-wrapper h-100% w-100%">
|
||||
@@ -121,6 +124,7 @@ const back = () => {
|
||||
<FileAttrs v-else-if="currentTab === 'FileAttrs'" :parent-id="parentId" />
|
||||
<TextContent
|
||||
v-else-if="currentTab === 'TextContent'"
|
||||
ref="textContentRef"
|
||||
:parent-id="parentId"
|
||||
:info-id="infoId"
|
||||
:target-company="targetCompany"
|
||||
@@ -148,20 +152,239 @@ const back = () => {
|
||||
<style lang="scss" scoped>
|
||||
.contain {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
box-shadow: 0rem 0.13rem 0.25rem rgba(122, 122, 122, 0.25);
|
||||
height: 50px;
|
||||
flex: 0 0 50px;
|
||||
border-bottom: 1px solid #dfe6f2;
|
||||
background: #fffaf4 !important;
|
||||
box-shadow: none;
|
||||
padding: 0 18px 0 40px;
|
||||
|
||||
&-tab {
|
||||
.active-item {
|
||||
background-color: rgba($color: #1e5ef9, $alpha: 0.1);
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
font-weight: 700;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: #1e5ef9;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-tab-item {
|
||||
min-width: 64px;
|
||||
justify-content: center;
|
||||
padding: 0 18px !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
button {
|
||||
min-width: 64px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.save {
|
||||
border: 1px solid #ff9f22;
|
||||
background: linear-gradient(180deg, #ff9f22 0%, #ff8614 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
border: 1px solid #ffd9a9;
|
||||
background: #fff5e8;
|
||||
color: #bd6a18;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-main {
|
||||
min-height: 0;
|
||||
overflow-y: hidden;
|
||||
padding: 14px 10px 16px;
|
||||
background: #f6f8fc;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.module) {
|
||||
height: 100% !important;
|
||||
padding-bottom: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper) {
|
||||
box-sizing: border-box;
|
||||
height: 100% !important;
|
||||
gap: 14px !important;
|
||||
padding: 0 14px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider) {
|
||||
width: 230px !important;
|
||||
flex: 0 0 230px !important;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 4px !important;
|
||||
background: #fff !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-search) {
|
||||
padding: 14px 14px 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-search .n-input) {
|
||||
--n-height: 32px !important;
|
||||
--n-border-radius: 6px !important;
|
||||
--n-border: 1px solid #dbe3ef !important;
|
||||
--n-border-hover: 1px solid #b8c8e6 !important;
|
||||
--n-border-focus: 1px solid #2d66ff !important;
|
||||
--n-box-shadow-focus: none !important;
|
||||
background: #fff !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-header) {
|
||||
height: 104px !important;
|
||||
border-bottom: 1px solid #e8edf5;
|
||||
border-radius: 0 !important;
|
||||
background: #eef5ff !important;
|
||||
background-image: none !important;
|
||||
padding: 14px 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content) {
|
||||
margin-top: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card) {
|
||||
border-radius: 0 !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card-header) {
|
||||
min-height: 42px;
|
||||
border-bottom: 1px solid #e8edf5;
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card-header__main) {
|
||||
color: #243044 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-card__content) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-data-table-th) {
|
||||
height: 36px;
|
||||
background: #f7f8fb !important;
|
||||
border-bottom: 1px solid #e8edf5 !important;
|
||||
color: #0f1e36 !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .n-data-table-td) {
|
||||
height: 34px;
|
||||
border-bottom: 1px solid #edf1f6 !important;
|
||||
color: #0f1e36 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-sider-content .selected-row),
|
||||
:deep(.module-wrapper-sider-content .selected-row td) {
|
||||
background: #eaf2ff !important;
|
||||
color: #1f62ff !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content) {
|
||||
min-width: 0;
|
||||
flex: 1 1 0 !important;
|
||||
width: auto !important;
|
||||
border: 1px solid #dbe3ef;
|
||||
border-radius: 0 !important;
|
||||
background: #fff !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-header) {
|
||||
height: 34px !important;
|
||||
border-bottom: 1px solid #e8edf5 !important;
|
||||
background: #fff !important;
|
||||
padding-left: 76px !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-header .n-text) {
|
||||
color: #0f1e36 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 800 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card .gradient-1::before) {
|
||||
width: auto !important;
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
top: 5px !important;
|
||||
left: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
background: linear-gradient(180deg, #5f90ff 0%, #3064df 100%) !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card .gradient-2::before) {
|
||||
width: auto !important;
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
top: 5px !important;
|
||||
left: 10px !important;
|
||||
border-radius: 4px !important;
|
||||
background: linear-gradient(180deg, #ffbe62 0%, #ff8d1f 100%) !important;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content-title) {
|
||||
height: 34px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
background: #fff !important;
|
||||
padding: 0 10px !important;
|
||||
color: #5e6777;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.module-wrapper-content-card-content-body) {
|
||||
border-radius: 0 !important;
|
||||
background: #737373;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
:deep(.module-footer) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -579,6 +579,10 @@ const handleConfirm = () => {
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
handleConfirm
|
||||
});
|
||||
|
||||
/** 清空数据 */
|
||||
const clearData = () => {
|
||||
htmlParams.value = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, ref } from 'vue';
|
||||
import { NButton, NDivider } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchBatchDeletePost, fetchGetPostDeptSelect, fetchGetPostList } from '@/service/api/system/post';
|
||||
import { ref } from 'vue';
|
||||
import { NDivider } from 'naive-ui';
|
||||
import { fetchGetDeptSelect } from '@/service/api/system/dept';
|
||||
import { fetchBatchDeletePost, fetchGetPostList } from '@/service/api/system/post';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useAuth } from '@/hooks/business/auth';
|
||||
import { useDownload } from '@/hooks/business/download';
|
||||
@@ -11,6 +11,7 @@ import { useDict } from '@/hooks/business/dict';
|
||||
import DictTag from '@/components/custom/dict-tag.vue';
|
||||
import { $t } from '@/locales';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import { handleTree } from '@/utils/common';
|
||||
import PostOperateDrawer from './modules/post-operate-drawer.vue';
|
||||
import PostSearch from './modules/post-search.vue';
|
||||
|
||||
@@ -42,8 +43,7 @@ const {
|
||||
// the value can not be undefined, otherwise the property in Form will not be reactive
|
||||
postCode: null,
|
||||
postName: null,
|
||||
status: null,
|
||||
belongDeptId: null
|
||||
status: null
|
||||
},
|
||||
columns: () => [
|
||||
{
|
||||
@@ -180,163 +180,81 @@ async function handleExport() {
|
||||
download('/system/post/export', searchParams, `岗位信息_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
const expandedKeys = ref<CommonType.IdType[]>([100]);
|
||||
|
||||
const selectable = computed(() => {
|
||||
return !loading.value;
|
||||
});
|
||||
|
||||
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
|
||||
const deptPattern = ref<string>();
|
||||
const deptData = ref<Api.Common.CommonTreeRecord>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
function toCommonDeptTree(list: Record<string, any>[]) {
|
||||
const tree = handleTree(
|
||||
list.map(item => ({ ...item })),
|
||||
{ idField: 'deptId' }
|
||||
);
|
||||
const normalize = (items: Record<string, any>[]): Record<string, any>[] =>
|
||||
items.map(item => ({
|
||||
...item,
|
||||
id: item.deptId,
|
||||
parentId: item.parentId,
|
||||
label: item.deptName,
|
||||
weight: item.orderNum ?? 0,
|
||||
children: item.children?.length ? normalize(item.children) : []
|
||||
}));
|
||||
return normalize(tree) as Api.Common.CommonTreeRecord;
|
||||
}
|
||||
|
||||
async function getDeptOptions() {
|
||||
// 加载
|
||||
startTreeLoading();
|
||||
const { data: tree, error } = await fetchGetPostDeptSelect();
|
||||
const { data: tree, error } = await fetchGetDeptSelect();
|
||||
if (!error) {
|
||||
deptData.value = tree;
|
||||
deptData.value = toCommonDeptTree(tree || []);
|
||||
}
|
||||
endTreeLoading();
|
||||
}
|
||||
getDeptOptions();
|
||||
|
||||
function handleClickTree(keys: string[]) {
|
||||
searchParams.belongDeptId = keys.length ? keys[0] : null;
|
||||
checkedRowKeys.value = [];
|
||||
getDataByPage();
|
||||
}
|
||||
|
||||
function handleResetTreeData() {
|
||||
deptPattern.value = undefined;
|
||||
getDeptOptions();
|
||||
}
|
||||
|
||||
function handleResetSearch() {
|
||||
resetSearchParams();
|
||||
selectedKeys.value = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableSiderLayout sider-title="部门列表">
|
||||
<template #header-extra>
|
||||
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ic:round-refresh" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
<template #sider>
|
||||
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
|
||||
<NSpin class="dept-tree" :show="treeLoading">
|
||||
<NTree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
block-node
|
||||
show-line
|
||||
:data="deptData as []"
|
||||
:show-irrelevant-nodes="false"
|
||||
:pattern="deptPattern"
|
||||
block-line
|
||||
class="infinite-scroll h-full min-h-200px py-3"
|
||||
key-field="id"
|
||||
label-field="label"
|
||||
virtual-scroll
|
||||
:selectable="selectable"
|
||||
@update:selected-keys="handleClickTree"
|
||||
>
|
||||
<template #empty>
|
||||
<NEmpty description="暂无部门信息" class="h-full min-h-200px justify-center" />
|
||||
</template>
|
||||
</NTree>
|
||||
</NSpin>
|
||||
</template>
|
||||
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
|
||||
<PostSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
|
||||
<NCard title="岗位信息列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
:show-add="hasAuth('system:post:add')"
|
||||
:show-delete="hasAuth('system:post:remove')"
|
||||
:show-export="hasAuth('system:post:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="962"
|
||||
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
|
||||
<PostSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
|
||||
<NCard title="岗位信息列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.postId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
:show-add="hasAuth('system:post:add')"
|
||||
:show-delete="hasAuth('system:post:remove')"
|
||||
:show-export="hasAuth('system:post:export')"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@export="handleExport"
|
||||
@refresh="getData"
|
||||
/>
|
||||
<PostOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:dept-data="deptData"
|
||||
@submitted="getData"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</TableSiderLayout>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="962"
|
||||
:loading="loading"
|
||||
remote
|
||||
:row-key="row => row.postId"
|
||||
:pagination="mobilePagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<PostOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:dept-data="deptData"
|
||||
@submitted="getData"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dept-tree {
|
||||
.n-button {
|
||||
--n-padding: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.n-tree__empty) {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.n-spin-content) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.infinite-scroll) {
|
||||
height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
|
||||
max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
:deep(.infinite-scroll) {
|
||||
height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
|
||||
max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-tree-node) {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-switcher) {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
:deep(.n-tree-node-switcher__icon) {
|
||||
font-size: 16px !important;
|
||||
height: 16px !important;
|
||||
width: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-data-table-wrapper),
|
||||
:deep(.n-data-table-base-table),
|
||||
:deep(.n-data-table-base-table-body) {
|
||||
|
||||
9
src/views/system2/client/index.vue
Normal file
9
src/views/system2/client/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Client' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="client" />
|
||||
</template>
|
||||
9
src/views/system2/config/index.vue
Normal file
9
src/views/system2/config/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Config' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="config" />
|
||||
</template>
|
||||
9
src/views/system2/dept/index.vue
Normal file
9
src/views/system2/dept/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Dept' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="dept" />
|
||||
</template>
|
||||
9
src/views/system2/dict/index.vue
Normal file
9
src/views/system2/dict/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Dict' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="dict" />
|
||||
</template>
|
||||
11
src/views/system2/log/index.vue
Normal file
11
src/views/system2/log/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'System2Log'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="log" />
|
||||
</template>
|
||||
9
src/views/system2/menu/index.vue
Normal file
9
src/views/system2/menu/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Menu' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="menu" />
|
||||
</template>
|
||||
2811
src/views/system2/modules/system2-list-page.vue
Normal file
2811
src/views/system2/modules/system2-list-page.vue
Normal file
File diff suppressed because it is too large
Load Diff
9
src/views/system2/notice/index.vue
Normal file
9
src/views/system2/notice/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Notice' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="notice" />
|
||||
</template>
|
||||
9
src/views/system2/oss/index.vue
Normal file
9
src/views/system2/oss/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Oss' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="oss" />
|
||||
</template>
|
||||
9
src/views/system2/post/index.vue
Normal file
9
src/views/system2/post/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Post' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="post" />
|
||||
</template>
|
||||
9
src/views/system2/role/index.vue
Normal file
9
src/views/system2/role/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2Role' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="role" />
|
||||
</template>
|
||||
9
src/views/system2/user/index.vue
Normal file
9
src/views/system2/user/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import System2ListPage from '../modules/system2-list-page.vue';
|
||||
|
||||
defineOptions({ name: 'System2User' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<System2ListPage page-key="user" />
|
||||
</template>
|
||||
Reference in New Issue
Block a user