Merge branch 'wxd' into hebing

# Conflicts:
#	src/components/workbench/TaskManagement.vue
This commit is contained in:
2025-12-28 19:59:05 +08:00
18 changed files with 394 additions and 80 deletions

2
.env
View File

@@ -1,5 +1,5 @@
# 网站主标题 # 网站主标题
VITE_GLOBAL_TITLE= 'PIGX ADMIN' VITE_GLOBAL_TITLE= '投资管理门户'
# footer # footer
VITE_FOOTER_TITLE= '©2025 pig4cloud.com' VITE_FOOTER_TITLE= '©2025 pig4cloud.com'

View File

@@ -14,7 +14,7 @@
<script setup lang="ts" name="StrengthMeter"> <script setup lang="ts" name="StrengthMeter">
import { verifyPasswordStrength } from '/@/utils/toolsValidate'; import { verifyPasswordStrength } from '/@/utils/toolsValidate';
const props = defineProps({ const props = defineProps({
value: { modelValue: {
type: String, type: String,
}, },
showInput: { showInput: {
@@ -28,7 +28,7 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['score', 'change', 'update:value']); const emit = defineEmits(['score', 'change', 'update:modelValue']);
// 计算密码强度 // 计算密码强度
const getPasswordStrength = computed(() => { const getPasswordStrength = computed(() => {
@@ -47,13 +47,13 @@ const handleChange = (e: any) => {
}; };
watchEffect(() => { watchEffect(() => {
innerValueRef.value = props.value || ''; innerValueRef.value = props.modelValue || '';
}); });
watch( watch(
() => unref(innerValueRef), () => unref(innerValueRef),
(val) => { (val) => {
emit('update:value', val); emit('update:modelValue', val);
emit('change', val); emit('change', val);
} }
); );

View File

@@ -75,7 +75,7 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('progressReport.form.completionRate')" required prop="completionRate"> <el-form-item :label="t('progressReport.form.completionRate')" required prop="completionRate">
<el-input v-model="formData.completionRate" <el-input v-model="formData.completionRate"
:placeholder="t('progressReport.form.completionRatePlaceholder')"> :placeholder="t('progressReport.form.completionRatePlaceholder')" disabled>
<template #append>%</template> <template #append>%</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -107,13 +107,14 @@
<div style="flex: 1;"> <div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.reportYear')"> <el-form-item style="height: 82px;" :label="t('progressReport.form.reportYear')">
<el-date-picker v-model="formData.annualReport" type="year" value-format="YYYY" <el-date-picker v-model="formData.annualReport" type="year" value-format="YYYY"
:placeholder="t('progressReport.form.selectYearPlaceholder')" style="width: 100%" /> :placeholder="t('progressReport.form.selectYearPlaceholder')" style="width: 100%"
@change="handleYearChange" />
</el-form-item> </el-form-item>
<el-form-item style="height: 82px;" <el-form-item style="height: 82px;"
:label="t('progressReport.form.currentYearPlannedInvestment')" required :label="t('progressReport.form.currentYearPlannedInvestment')" required
prop="annualPlannedInvestment"> prop="annualPlannedInvestment">
<el-input v-model="formData.annualPlannedInvestment" <el-input v-model="formData.annualPlannedInvestment"
:placeholder="t('progressReport.form.inputPlaceholder')"> :placeholder="t('progressReport.form.inputPlaceholder')" disabled>
<template #append>{{ t('progressReport.form.unitSuffix') }}</template> <template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -128,7 +129,7 @@
<el-form-item style="height: 82px;" <el-form-item style="height: 82px;"
:label="t('progressReport.form.investmentCompletionRate')"> :label="t('progressReport.form.investmentCompletionRate')">
<el-input v-model="formData.investmentCompletionRate" <el-input v-model="formData.investmentCompletionRate"
:placeholder="t('progressReport.form.inputPlaceholder')"> :placeholder="t('progressReport.form.inputPlaceholder')" disabled>
<template #append>%</template> <template #append>%</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -141,7 +142,7 @@
<div style="flex: 1;"> <div style="flex: 1;">
<el-form-item style="height: 82px;" :label="t('progressReport.form.plannedPaymentAmount')"> <el-form-item style="height: 82px;" :label="t('progressReport.form.plannedPaymentAmount')">
<el-input v-model="formData.plannedPayment" <el-input v-model="formData.plannedPayment"
:placeholder="t('progressReport.form.inputPlaceholder')"> :placeholder="t('progressReport.form.inputPlaceholder')" disabled>
<template #append>{{ t('progressReport.form.unitSuffix') }}</template> <template #append>{{ t('progressReport.form.unitSuffix') }}</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -155,7 +156,7 @@
<el-form-item style="height: 82px;" <el-form-item style="height: 82px;"
:label="t('progressReport.form.plannedAmountPaymentCompletionRate')"> :label="t('progressReport.form.plannedAmountPaymentCompletionRate')">
<el-input v-model="formData.investmentPlanCompletionRate" <el-input v-model="formData.investmentPlanCompletionRate"
:placeholder="t('progressReport.form.inputPlaceholder')"> :placeholder="t('progressReport.form.inputPlaceholder')" disabled>
<template #append>%</template> <template #append>%</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -273,6 +274,10 @@ const emit = defineEmits<{
(e: 'update:modelValue', value: InvestmentProjectProgress): void; (e: 'update:modelValue', value: InvestmentProjectProgress): void;
}>(); }>();
const useForm = ref<FormInstance | undefined>() const useForm = ref<FormInstance | undefined>()
// 获取当前年份
const currentYear = String(new Date().getFullYear());
const defaultForm = reactive<InvestmentProjectProgress>({ const defaultForm = reactive<InvestmentProjectProgress>({
id: undefined, id: undefined,
projectName: '', projectName: '',
@@ -285,7 +290,7 @@ const defaultForm = reactive<InvestmentProjectProgress>({
completionRate: undefined, completionRate: undefined,
cumulativePaymentToDate: undefined, cumulativePaymentToDate: undefined,
paymentCompletionRate: undefined, paymentCompletionRate: undefined,
annualReport: undefined, annualReport: currentYear, // 默认当前年度
annualPlannedInvestment: undefined, annualPlannedInvestment: undefined,
ytdRemainingInvestment: undefined, ytdRemainingInvestment: undefined,
investmentCompletionRate: undefined, investmentCompletionRate: undefined,
@@ -308,6 +313,12 @@ const defaultForm = reactive<InvestmentProjectProgress>({
deptId: '' deptId: ''
}) })
const formData = reactive<InvestmentProjectProgress>({ ...defaultForm, ...props.modelValue }); const formData = reactive<InvestmentProjectProgress>({ ...defaultForm, ...props.modelValue });
// 确保汇报年度有默认值
if (!formData.annualReport) {
formData.annualReport = currentYear;
}
const projectNameData = ref<ProjectPlanApplyFormItem[]>([]); const projectNameData = ref<ProjectPlanApplyFormItem[]>([]);
// 获取当前日期作为汇报时间 // 获取当前日期作为汇报时间
const reportTime = computed(() => { const reportTime = computed(() => {
@@ -333,6 +344,17 @@ const getProjectNameList = async () => {
} }
} }
getProjectNameList(); getProjectNameList();
// 项目总计划支付额(所有年度计划支付额度的和)
const totalPlannedPayment = ref<number>(0);
/**
* 项目选择改变
* @param {string | number} value - 选中的项目ID
*/
// 当前选中的项目
const currentSelectedProject = ref<ProjectPlanApplyFormItem | null>(null);
/** /**
* 项目选择改变 * 项目选择改变
* @param {string | number} value - 选中的项目ID * @param {string | number} value - 选中的项目ID
@@ -344,11 +366,190 @@ const handelSelectChange = (value: string | number) => {
); );
// 如果找到了对应的项目,则更新实施单位字段 // 如果找到了对应的项目,则更新实施单位字段
if (selectedProject) { if (selectedProject) {
currentSelectedProject.value = selectedProject;
formData.implementingBody = selectedProject.projectMainEntity || selectedProject.projectOwnerUnit || ''; formData.implementingBody = selectedProject.projectMainEntity || selectedProject.projectOwnerUnit || '';
formData.projectName = selectedProject.projectName || ''; formData.projectName = selectedProject.projectName || '';
formData.deptId = String(selectedProject.deptId); formData.deptId = String(selectedProject.deptId);
// 从项目中获取项目总投资金额
const projectTotalAmount = selectedProject.projectTotalAmount || selectedProject.totalInvestment;
if (projectTotalAmount) {
formData.projectTotalAmount = Number(projectTotalAmount);
}
// 计算项目总计划支付额(所有年度计划支付额度的和)
const investmentEntities = selectedProject.projectInvestmentEntities || [];
let totalPayment = 0;
investmentEntities.forEach((entity: any) => {
const paymentAmount = Number(entity.plannedPaymentAmount) || 0;
totalPayment += paymentAmount;
});
totalPlannedPayment.value = totalPayment;
// 根据当前选中的年份更新本年度计划投资额和计划支付额
updateAnnualPlanData(formData.annualReport as string, investmentEntities);
// 触发完成率计算
calculateCompletionRates();
} }
} }
/**
* 年份变化处理
* @param {string} year - 选中的年份
*/
const handleYearChange = (year: string) => {
if (currentSelectedProject.value) {
const investmentEntities = currentSelectedProject.value.projectInvestmentEntities || [];
updateAnnualPlanData(year, investmentEntities);
// 重新计算本年度完成率
calculateAnnualCompletionRates();
}
}
/**
* 更新本年度计划投资额和计划支付额
* @param {string} year - 年份
* @param {any[]} investmentEntities - 投资计划实体列表
*/
const updateAnnualPlanData = (year: string, investmentEntities: any[]) => {
// 查找当前年份对应的投资计划
const currentYearPlan = investmentEntities.find((entity: any) =>
entity.plannedInvestmentYear === year ||
String(entity.plannedInvestmentYear) === year
);
if (currentYearPlan) {
// 设置本年度计划投资额
formData.annualPlannedInvestment = Number(currentYearPlan.plannedImageAmount) || 0;
// 设置本年度计划支付额
formData.plannedPayment = Number(currentYearPlan.plannedPaymentAmount) || 0;
} else {
// 如果没有找到当前年份的计划,清空数据
formData.annualPlannedInvestment = 0;
formData.plannedPayment = 0;
}
}
/**
* 计算总投资完成率和总支付完成率
*/
const calculateCompletionRates = () => {
// 总投资完成率 = 项目开始截至本月末累计完成投资 / 项目总投资金额 * 100
const cumulativeInvestment = Number(formData.cumulativeInvestmentToDate) || 0;
const projectTotal = Number(formData.projectTotalAmount) || 0;
if (projectTotal > 0) {
const rate = (cumulativeInvestment / projectTotal) * 100;
formData.completionRate = Number(rate.toFixed(2));
} else {
formData.completionRate = 0;
}
// 总支付完成率 = 项目开始截至本月末累计完成支付额 / 项目总计划支付额 * 100
const cumulativePayment = Number(formData.cumulativePaymentToDate) || 0;
const totalPayment = totalPlannedPayment.value || 0;
if (totalPayment > 0) {
const rate = (cumulativePayment / totalPayment) * 100;
formData.paymentCompletionRate = Number(rate.toFixed(2));
} else {
formData.paymentCompletionRate = 0;
}
// 同时计算本年度完成率
calculateAnnualCompletionRates();
}
/**
* 计算本年度投资完成率和支付完成率
*/
const calculateAnnualCompletionRates = () => {
// 本年度投资完成率 = 本企业本年度截至本月末完成投资 / 本年度计划投资额 * 100
const ytdInvestment = Number(formData.ytdRemainingInvestment) || 0;
const annualPlannedInvestment = Number(formData.annualPlannedInvestment) || 0;
if (annualPlannedInvestment > 0) {
const rate = (ytdInvestment / annualPlannedInvestment) * 100;
formData.investmentCompletionRate = Number(rate.toFixed(2));
} else {
formData.investmentCompletionRate = 0;
}
// 本年度支付完成率 = 本企业本年度支付完成额 / 本年度计划支付额 * 100
const actualPayment = Number(formData.actualPayment) || 0;
const plannedPayment = Number(formData.plannedPayment) || 0;
if (plannedPayment > 0) {
const rate = (actualPayment / plannedPayment) * 100;
formData.investmentPlanCompletionRate = Number(rate.toFixed(2));
} else {
formData.investmentPlanCompletionRate = 0;
}
}
// 监听累计完成投资和项目总投资金额变化,自动计算总投资完成率
watch(
[() => formData.cumulativeInvestmentToDate, () => formData.projectTotalAmount],
() => {
const cumulativeInvestment = Number(formData.cumulativeInvestmentToDate) || 0;
const projectTotal = Number(formData.projectTotalAmount) || 0;
if (projectTotal > 0) {
const rate = (cumulativeInvestment / projectTotal) * 100;
formData.completionRate = Number(rate.toFixed(2));
} else {
formData.completionRate = 0;
}
}
);
// 监听累计完成支付额变化,自动计算总支付完成率(可编辑,仅作为默认值)
watch(
() => formData.cumulativePaymentToDate,
() => {
const cumulativePayment = Number(formData.cumulativePaymentToDate) || 0;
const totalPayment = totalPlannedPayment.value || 0;
if (totalPayment > 0) {
const rate = (cumulativePayment / totalPayment) * 100;
formData.paymentCompletionRate = Number(rate.toFixed(2));
}
}
);
// 监听本年度完成投资额变化,自动计算本年度投资完成率
watch(
() => formData.ytdRemainingInvestment,
() => {
const ytdInvestment = Number(formData.ytdRemainingInvestment) || 0;
const annualPlannedInvestment = Number(formData.annualPlannedInvestment) || 0;
if (annualPlannedInvestment > 0) {
const rate = (ytdInvestment / annualPlannedInvestment) * 100;
formData.investmentCompletionRate = Number(rate.toFixed(2));
} else {
formData.investmentCompletionRate = 0;
}
}
);
// 监听本年度完成支付额变化,自动计算本年度支付完成率
watch(
() => formData.actualPayment,
() => {
const actualPayment = Number(formData.actualPayment) || 0;
const plannedPayment = Number(formData.plannedPayment) || 0;
if (plannedPayment > 0) {
const rate = (actualPayment / plannedPayment) * 100;
formData.investmentPlanCompletionRate = Number(rate.toFixed(2));
} else {
formData.investmentPlanCompletionRate = 0;
}
}
);
/** /**
* 页码大小改变 * 页码大小改变
* @param {number} pageSize - 新的页码大小 * @param {number} pageSize - 新的页码大小
@@ -384,6 +585,10 @@ watch(
if (typeof newVal.annualReport === 'number') newVal.annualReport = newVal.annualReport.toString() if (typeof newVal.annualReport === 'number') newVal.annualReport = newVal.annualReport.toString()
Object.assign(formData, defaultForm, newVal); Object.assign(formData, defaultForm, newVal);
} }
// 确保汇报年度有默认值
if (!formData.annualReport) {
formData.annualReport = currentYear;
}
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
); );

View File

@@ -2,7 +2,7 @@
<div class="task-management"> <div class="task-management">
<div class="task-header"> <div class="task-header">
<el-tabs v-model="activeTab" @tab-change="handleTabChange"> <el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane :label="t('workbench.task.pendingReview') + ` (${pendingCount})`" name="pending" <el-tab-pane :label="t('workbench.task.pendingReview') + ` (${pendingTotal})`" name="pending"
class="task-tab-pane"> class="task-tab-pane">
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="t('workbench.task.saved')" name="saved"></el-tab-pane> <el-tab-pane :label="t('workbench.task.saved')" name="saved"></el-tab-pane>
@@ -49,6 +49,28 @@ const activeTab = ref('pending');
const tableData = ref<any[]>([]); const tableData = ref<any[]>([]);
const loading = ref(false); const loading = ref(false);
const pendingTotal = ref(0);
/**
* 根据任务名称判断类型
* @param name 任务名称
* @returns 任务类型
*/
const getTaskType = (name: string): string => {
if (name.includes('投资申报') || name.includes('项目投资')) {
return 'investment';
}
if (name.includes('专家') || name.includes('专家登记')) {
return 'expert';
}
if (name.includes('投资储备') || name.includes('储备')) {
return 'reserve';
}
if (name.includes('合作单位') || name.includes('合作')) {
return 'cooperation';
}
// 默认类型
return 'default';
};
/** /**
* 根据类型获取审批页面路由 * 根据类型获取审批页面路由
@@ -59,7 +81,7 @@ const loading = ref(false);
const getApprovalRoute = (type: string, taskId: string): string => { const getApprovalRoute = (type: string, taskId: string): string => {
const routeMap: Record<string, string> = { const routeMap: Record<string, string> = {
investment: '/invMid/planApplyExamine/index', // 投资申报审批 investment: '/invMid/planApplyExamine/index', // 投资申报审批
expert: '/workbench/expertApproval/index', // 专家登记审批 expert: '/investment/expertApplyExamine/index', // 专家信息登记审批
reserve: '/workbench/reserveApproval/index', // 投资储备审批 reserve: '/workbench/reserveApproval/index', // 投资储备审批
cooperation: '/workbench/cooperationApproval/index', // 合作单位审批 cooperation: '/workbench/cooperationApproval/index', // 合作单位审批
default: '/workbench/approvalDetail', // 默认审批详情页 default: '/workbench/approvalDetail', // 默认审批详情页
@@ -73,30 +95,21 @@ const getApprovalRoute = (type: string, taskId: string): string => {
* @param row 行数据 * @param row 行数据
*/ */
const handleRowClick = (row: any) => { const handleRowClick = (row: any) => {
console.log(row); const taskType = row.type || getTaskType(row.name);
const taskId = row.taskId || row.id; const taskId = row.taskId || row.id;
const route = getApprovalRoute(row.flowType, taskId); const route = getApprovalRoute(taskType, taskId);
const routePath = examineDict[row.flowType]
if (!routePath){
useMessage().error('未定义的审批类型!')
return
}
console.log(routePath)
// 跳转到对应的审批页面 // 跳转到对应的审批页面
router.push({ router.push({
path: routePath, path: route.split('?')[0],
query: { query: {
processInstanceId: row.processInstanceId, id: taskId,
type: taskType,
tab: activeTab.value, // 传递当前标签页信息,用于返回时定位 tab: activeTab.value, // 传递当前标签页信息,用于返回时定位
}, },
}); });
}; };
// request('admin/sys/code/list',{ const getQueryMineTask = (): Promise<any> => {
// method:'get',
// }).then(res=>{
// console.log('test',res)
// })
const getQueryMineTask = () =>{
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
queryMineTask({ queryMineTask({
title: '', title: '',
@@ -135,6 +148,7 @@ const loadData = async (tab: string) => {
if (tab === 'pending') { if (tab === 'pending') {
const response = await getQueryMineTask() const response = await getQueryMineTask()
res = response?.data?.records res = response?.data?.records
pendingTotal.value = response?.data?.total || 0
} }
if (tab === 'myInitiated') { if (tab === 'myInitiated') {
const response = await getQueryMineStarted() const response = await getQueryMineStarted()

View File

@@ -415,6 +415,6 @@ export const yesOrNo:Enums[] = [
* 级别 * 级别
* */ * */
export const level:Enums[] = [ export const level:Enums[] = [
{label:'一级',value:'1'}, {label:'一级',value:'national'},
{label:'二级',value:'2'}, {label:'二级',value:'provincial'},
] ]

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="layout-navbars-breadcrumb-user pr15" :style="{ flex: layoutUserFlexNum }"> <div class="layout-navbars-breadcrumb-user pr15" :style="{ flex: layoutUserFlexNum }">
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange"> <!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon"> <div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="state.disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('user.title1')"></i> <i class="iconfont" :class="state.disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('user.title1')"></i>
</div> </div>
@@ -10,7 +10,7 @@
<el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item> <el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown> -->
<div class="layout-navbars-breadcrumb-user-icon" @click="onLockClick"> <div class="layout-navbars-breadcrumb-user-icon" @click="onLockClick">
<el-icon :title="$t('layout.threeLockScreenTime')"> <el-icon :title="$t('layout.threeLockScreenTime')">
<ele-Lock /> <ele-Lock />

View File

@@ -255,11 +255,11 @@ export function verifyPasswordPowerful(val: string) {
export function verifyPasswordStrength(val: string) { export function verifyPasswordStrength(val: string) {
let v = '0'; let v = '0';
// 弱:纯数字,纯字母,纯特殊字符 // 弱:纯数字,纯字母,纯特殊字符
if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,100}$/.test(val)) v = '1'; if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.\*_]+){6,100}$/.test(val)) v = '1';
// 中:字母+数字,字母+特殊字符,数字+特殊字符 // 中:字母+数字,字母+特殊字符,数字+特殊字符
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,100}$/.test(val)) v = '2'; if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.\*_]+$)[a-zA-Z\d!@#$%^&\.\*_]{6,100}$/.test(val)) v = '2';
// 强:字母+数字+特殊字符 // 强:字母+数字+特殊字符
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,100}$/.test(val)) if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.\*_]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.\*_]+$)(?![\d!@#$%^&\.\*_]+$)[a-zA-Z\d!@#$%^&\.\*_]{6,100}$/.test(val))
v = '3'; v = '3';
// 返回结果 // 返回结果
return v; return v;

View File

@@ -38,7 +38,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-row> </el-row>
<el-row> <!-- <el-row>
<div class="mb8" style="width: 100%"> <div class="mb8" style="width: 100%">
<el-button v-auth="'sys_user_add'" icon="folder-add" type="primary" @click="userDialogRef.openDialog()"> <el-button v-auth="'sys_user_add'" icon="folder-add" type="primary" @click="userDialogRef.openDialog()">
{{ $t('common.addBtn') }} {{ $t('common.addBtn') }}
@@ -68,7 +68,7 @@
style="float: right" style="float: right"
/> />
</div> </div>
</el-row> </el-row> -->
<el-table <el-table
v-loading="state.loading" v-loading="state.loading"
:data="state.dataList" :data="state.dataList"
@@ -111,7 +111,7 @@
{{ $t('common.editBtn') }} {{ $t('common.editBtn') }}
</el-button> </el-button>
<!-- 删除用户 --> <!-- 删除用户 -->
<el-tooltip :content="$t('sysuser.deleteDisabledTip')" :disabled="scope.row.userId !== '1'" placement="top"> <!-- <el-tooltip :content="$t('sysuser.deleteDisabledTip')" :disabled="scope.row.userId !== '1'" placement="top">
<span style="margin-left: 12px"> <span style="margin-left: 12px">
<el-button <el-button
icon="delete" icon="delete"
@@ -123,7 +123,7 @@
>{{ $t('common.delBtn') }} >{{ $t('common.delBtn') }}
</el-button> </el-button>
</span> </span>
</el-tooltip> </el-tooltip> -->
</div> </div>
</template> </template>
</el-table-column> </el-table-column>

View File

@@ -65,7 +65,7 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24" class="mb20"> <el-col :span="24" class="mb20">
<el-form-item label="原密码" prop="password"> <el-form-item label="原密码" prop="password">
<el-input v-model="passwordFormData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable type="password"> <el-input v-model="passwordFormData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable>
<template #suffix> <template #suffix>
<i <i
class="iconfont el-input__icon login-content-password" class="iconfont el-input__icon login-content-password"
@@ -102,7 +102,7 @@
</el-row> </el-row>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="第三方账号"> <!-- <el-tab-pane label="第三方账号">
<template #label> <template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" /> <path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" />
@@ -126,7 +126,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane> -->
</el-tabs> </el-tabs>
</el-drawer> </el-drawer>
</template> </template>

View File

@@ -150,12 +150,12 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="isUpdate" prop="expertStatus" min-width="120" :label="t('expertApply.table.expertStatus')"> <el-table-column v-if="isUpdate" prop="externalStatus" min-width="120" :label="t('expertApply.table.externalStatus')">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.expertStatus" :placeholder="t('expertApply.table.selectPlaceholder')" <el-select v-model="scope.row.externalStatus" :placeholder="t('expertApply.table.selectPlaceholder')"
style="width: 100%;"> style="width: 100%;">
<el-option :label="t('expertApply.expertStatusOptions.normal')" value="normal" /> <el-option :label="t('expertApply.externalStatusOptions.normal')" value="normal" />
<el-option :label="t('expertApply.expertStatusOptions.invalid')" value="invalid" /> <el-option :label="t('expertApply.externalStatusOptions.invalid')" value="invalid" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
@@ -232,8 +232,7 @@ const createEmptyExpert = () => ({
attachments: '', attachments: '',
evidences: '', evidences: '',
level: '', level: '',
expertStatus: 'normal', externalStatus: 'normal',
externalStatus: '',
attachmentUrl: '', attachmentUrl: '',
testimonyMaterials: '', testimonyMaterials: '',
createBy: '', createBy: '',
@@ -269,7 +268,7 @@ const onExpertSelected = (row: any) => {
target.professionalTitle = row?.professionalTitle ?? target.professionalTitle; target.professionalTitle = row?.professionalTitle ?? target.professionalTitle;
target.workUnit = row?.workUnit ?? target.workUnit; target.workUnit = row?.workUnit ?? target.workUnit;
target.level = row?.level ?? target.level; target.level = row?.level ?? target.level;
target.expertStatus = row?.expertStatus ?? target.expertStatus; target.externalStatus = row?.externalStatus ?? target.externalStatus;
target.attachments = row?.attachmentUrl ? JSON.parse(row.attachmentUrl) : [] target.attachments = row?.attachmentUrl ? JSON.parse(row.attachmentUrl) : []
target.evidences = row?.testimonyMaterials ? JSON.parse(row.testimonyMaterials) : [] target.evidences = row?.testimonyMaterials ? JSON.parse(row.testimonyMaterials) : []
}; };
@@ -314,8 +313,7 @@ const toSubmitExpert = (expert: ReturnType<typeof createEmptyExpert>) => ({
attachmentUrl: JSON.stringify(expert.attachments), attachmentUrl: JSON.stringify(expert.attachments),
testimonyMaterials: JSON.stringify(expert.evidences), testimonyMaterials: JSON.stringify(expert.evidences),
level: expert.level || '', level: expert.level || '',
expertStatus: expert.expertStatus || 'normal', externalStatus: expert.externalStatus || 'normal',
externalStatus: expert.externalStatus || '',
createBy: expert.createBy || '', createBy: expert.createBy || '',
createTime: expert.createTime || '', createTime: expert.createTime || '',
updateBy: expert.updateBy || '', updateBy: expert.updateBy || '',
@@ -463,7 +461,7 @@ watch(()=>tempId.value,()=>{
professionalTitle: item.professionalTitle, professionalTitle: item.professionalTitle,
workUnit: item.workUnit, workUnit: item.workUnit,
level: item.level, level: item.level,
expertStatus: item.expertStatus || 'normal', externalStatus: item.externalStatus || 'normal',
attachments: item.attachmentUrl ? JSON.parse(item.attachmentUrl) : [], attachments: item.attachmentUrl ? JSON.parse(item.attachmentUrl) : [],
evidences: item.testimonyMaterials ? JSON.parse(item.testimonyMaterials) : [] evidences: item.testimonyMaterials ? JSON.parse(item.testimonyMaterials) : []
} }

View File

@@ -29,10 +29,10 @@
<span>{{ level.find((item:any) => item.value === row.level)?.label || '--' }}</span> <span>{{ level.find((item:any) => item.value === row.level)?.label || '--' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('expertLibrary.table.status')" prop="externalStatus" min-width="120" <el-table-column :label="t('expertApply.table.externalStatus')" prop="externalStatus" min-width="120"
show-overflow-tooltip> show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="['info', 'primary', 'success', 'danger'][row.status]">{{ externalStatusLabel(row.status) }}</el-tag> <el-tag :type="row.externalStatus === 'normal' ? 'success' : 'danger'">{{ externalStatusLabel(row.externalStatus) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="t('expertLibrary.table.action')" width="100" fixed="right"> <el-table-column :label="t('expertLibrary.table.action')" width="100" fixed="right">
@@ -108,19 +108,15 @@ const handleView = (row: any) => {
}; };
const levelLabel = (val: string) => { const levelLabel = (val: string) => {
if (val === 'national') return t('expertApply.level.national'); if (val === 'national') return '一级';
if (val === 'provincial') return t('expertApply.level.provincial'); if (val === 'provincial') return '二级';
if (val === 'city') return t('expertApply.level.city');
return val || '-'; return val || '-';
}; };
const externalStatusLabel = (val: string) => { const externalStatusLabel = (val: string) => {
return { if (val === 'normal') return t('expertApply.externalStatusOptions.normal');
'0': t('expertLibrary.status.pending'), if (val === 'invalid') return t('expertApply.externalStatusOptions.invalid');
'1': t('expertLibrary.status.reviewing'), return val || '-';
'2': t('expertLibrary.status.approved'),
'3': t('expertLibrary.status.rejected'),
}[val] || 1
}; };
onMounted(() => { onMounted(() => {

View File

@@ -109,11 +109,11 @@ export default {
attachments: 'Attachments', attachments: 'Attachments',
evidences: 'Supporting Materials', evidences: 'Supporting Materials',
level: 'Level', level: 'Level',
expertStatus: 'Expert Status', externalStatus: 'Expert Status',
inputPlaceholder: 'Please enter', inputPlaceholder: 'Please enter',
selectPlaceholder: 'Please select', selectPlaceholder: 'Please select',
}, },
expertStatusOptions: { externalStatusOptions: {
normal: 'Normal', normal: 'Normal',
invalid: 'Invalid', invalid: 'Invalid',
}, },

View File

@@ -109,11 +109,11 @@ export default {
attachments: '附件', attachments: '附件',
evidences: '佐证材料', evidences: '佐证材料',
level: '级别', level: '级别',
expertStatus: '专家状态', externalStatus: '专家状态',
inputPlaceholder: '请输入', inputPlaceholder: '请输入',
selectPlaceholder: '请选择', selectPlaceholder: '请选择',
}, },
expertStatusOptions: { externalStatusOptions: {
normal: '正常', normal: '正常',
invalid: '作废', invalid: '作废',
}, },

View File

@@ -124,7 +124,19 @@
<span class="detail-title">{{ t('reserveLibrary.detail.title') }}</span> <span class="detail-title">{{ t('reserveLibrary.detail.title') }}</span>
<el-button class="close-btn" link icon="Close" @click="detailDrawerVisible = false" /> <el-button class="close-btn" link icon="Close" @click="detailDrawerVisible = false" />
</div> </div>
<div class="detail-content"> <!-- Tab 锚点导航 -->
<div class="detail-tabs">
<div
v-for="tab in detailTabs"
:key="tab.id"
class="detail-tab-item"
:class="{ active: activeTab === tab.id }"
@click="scrollToSection(tab.id)"
>
{{ tab.label }}
</div>
</div>
<div class="detail-content" ref="detailContentRef" @scroll="handleDetailScroll">
<ProjectBasicInfoView :model-value="detailFormData" :main-title="t('reserveLibrary.detail.formTitle')" /> <ProjectBasicInfoView :model-value="detailFormData" :main-title="t('reserveLibrary.detail.formTitle')" />
</div> </div>
</el-drawer> </el-drawer>
@@ -235,6 +247,59 @@ const detailFormData = reactive<ProjectBasicInfoFormData>(createEmptyDetail());
const tableLoading = ref(false); const tableLoading = ref(false);
const detailLoading = ref(false); const detailLoading = ref(false);
// Tab 锚点导航相关
const detailContentRef = ref<HTMLElement | null>(null);
const activeTab = ref('basicInfo');
const detailTabs = [
{ id: 'basicInfo', label: t('reserveRegistration.basicInfo.title') || '项目基本信息' },
{ id: 'investmentEstimate', label: t('reserveRegistration.investmentEstimate.title') || '投资估算' },
{ id: 'partnerInfo', label: t('reserveRegistration.partnerInfo.title') || '合作方信息' },
{ id: 'decisionInfo', label: t('reserveRegistration.decisionInfo.title') || '决策信息' },
];
const scrollToSection = (sectionId: string) => {
activeTab.value = sectionId;
const contentEl = detailContentRef.value;
if (!contentEl) return;
// 根据 sectionId 查找对应的标题元素
const sectionMap: Record<string, number> = {
'basicInfo': 0,
'investmentEstimate': 1,
'partnerInfo': 2,
'decisionInfo': 3,
};
const sectionTitles = contentEl.querySelectorAll('.form-section-title, .sub-section-title');
const targetIndex = sectionMap[sectionId];
if (sectionTitles[targetIndex]) {
sectionTitles[targetIndex].scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
const handleDetailScroll = () => {
const contentEl = detailContentRef.value;
if (!contentEl) return;
const sectionTitles = contentEl.querySelectorAll('.form-section-title, .sub-section-title');
const scrollTop = contentEl.scrollTop;
const offsetTop = 100; // 偏移量
let currentSection = 'basicInfo';
const sectionIds = ['basicInfo', 'investmentEstimate', 'partnerInfo', 'decisionInfo'];
sectionTitles.forEach((el, index) => {
const rect = el.getBoundingClientRect();
const containerRect = contentEl.getBoundingClientRect();
if (rect.top - containerRect.top <= offsetTop) {
currentSection = sectionIds[index] || currentSection;
}
});
activeTab.value = currentSection;
};
const resetDetailForm = () => { const resetDetailForm = () => {
const empty = createEmptyDetail(); const empty = createEmptyDetail();
Object.assign(detailFormData, empty); Object.assign(detailFormData, empty);
@@ -453,10 +518,39 @@ onMounted(() => {
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
.detail-tabs {
display: flex;
align-items: center;
gap: 0;
padding: 0 20px;
border-bottom: 1px solid var(--el-border-color);
background: var(--el-bg-color);
}
.detail-tab-item {
padding: 12px 20px;
font-size: 14px;
color: var(--el-text-color-regular);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
white-space: nowrap;
}
.detail-tab-item:hover {
color: var(--el-color-primary);
}
.detail-tab-item.active {
color: var(--el-color-primary);
border-bottom-color: var(--el-color-primary);
font-weight: 500;
}
.detail-content { .detail-content {
padding: 0 20px 20px; padding: 0 20px 20px;
overflow: auto; overflow: auto;
height: calc(100% - 52px); height: calc(100% - 96px);
} }
.el-form-item--default { .el-form-item--default {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="fixed top-0 right-0 z-10 flex items-center p-5 space-x-2"> <div class="fixed top-0 right-0 z-10 flex items-center p-5 space-x-2">
<!-- 语言切换 --> <!-- 语言切换 -->
<el-dropdown v-if="isI18nEnabled" trigger="click" @command="onLanguageChange"> <!-- <el-dropdown v-if="isI18nEnabled" trigger="click" @command="onLanguageChange">
<div <div
class="flex items-center justify-center transition-colors rounded-lg cursor-pointer w-9 h-9 bg-white/80 dark:bg-slate-800/80 hover:bg-gray-100 dark:hover:bg-slate-700 backdrop-blur-sm" class="flex items-center justify-center transition-colors rounded-lg cursor-pointer w-9 h-9 bg-white/80 dark:bg-slate-800/80 hover:bg-gray-100 dark:hover:bg-slate-700 backdrop-blur-sm"
> >
@@ -16,7 +16,7 @@
<el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item> <el-dropdown-item command="en" :disabled="state.disabledI18n === 'en'">English</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown> -->
<!-- 主题切换 --> <!-- 主题切换 -->
<el-tooltip v-if="isDarkModeEnabled" :content="getThemeConfig.isDark ? '切换亮色模式' : '切换暗色模式'" placement="bottom"> <el-tooltip v-if="isDarkModeEnabled" :content="getThemeConfig.isDark ? '切换亮色模式' : '切换暗色模式'" placement="bottom">

View File

@@ -208,6 +208,7 @@ export default {
startTime: 'Initiation Time', startTime: 'Initiation Time',
receivedTime: 'Received Time', receivedTime: 'Received Time',
processTime: 'Process Time', processTime: 'Process Time',
currentNode: 'Current Node',
status: 'Approval Status', status: 'Approval Status',
actions: 'Actions', actions: 'Actions',
}, },

View File

@@ -208,6 +208,7 @@ export default {
startTime: '发起时间', startTime: '发起时间',
receivedTime: '接收时间', receivedTime: '接收时间',
processTime: '处理时间', processTime: '处理时间',
currentNode: '当前节点',
status: '审批状态', status: '审批状态',
actions: '操作', actions: '操作',
}, },

View File

@@ -55,6 +55,11 @@
min-width="180" /> min-width="180" />
<el-table-column prop="updateTime" :label="t('workbenchPage.pending.table.processTime')" <el-table-column prop="updateTime" :label="t('workbenchPage.pending.table.processTime')"
min-width="180" /> min-width="180" />
<el-table-column prop="taskName" :label="t('workbenchPage.pending.table.currentNode')" min-width="140">
<template #default="scope">
{{ scope.row.taskName || scope.row.nodeName || scope.row.currentNode || '--' }}
</template>
</el-table-column>
<el-table-column prop="status" :label="t('workbenchPage.pending.table.status')" min-width="140"> <el-table-column prop="status" :label="t('workbenchPage.pending.table.status')" min-width="140">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status == 1" type="primary">进行中</el-tag> <el-tag v-if="scope.row.status == 1" type="primary">进行中</el-tag>