347 lines
11 KiB
Python
347 lines
11 KiB
Python
import sys
|
|
import os
|
|
|
|
filepath = r'd:\\haomi\\cursor_projects\\writeOff\\frontend\\src\\views\\modules\\meeting-page\\MeetingMaterialDrawer.vue'
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
text = f.read()
|
|
|
|
# 1. Update Scripts
|
|
script_idx = text.rfind('defineEmits<{')
|
|
|
|
insert_script = """const emit = defineEmits<{
|
|
materialTabChange: [name: string];
|
|
saveMaterial: [];
|
|
submitMaterial: [];
|
|
}>();
|
|
|
|
const handleSidebarMenuClick = async (moduleCode: string) => {
|
|
if (selectedModuleCode.value === moduleCode) return;
|
|
const canLeave = await handleBeforeTabLeave(moduleCode, selectedModuleCode.value);
|
|
if (canLeave) {
|
|
selectedModuleCode.value = moduleCode;
|
|
emit('materialTabChange', moduleCode);
|
|
}
|
|
};
|
|
|
|
const displayModuleTitle = computed(() => {
|
|
const map: Record<string, string> = {
|
|
BASIC_INFO: '会议基本信息',
|
|
WRITE_OFF_DOCS: '核销材料',
|
|
EXPERT_PROFILE: '专家简介/串场',
|
|
EXPERT_LIST: '专家配置及材料',
|
|
MEETING_INVOICE: '会议各项发票'
|
|
};
|
|
return map[selectedModuleCode.value] || '会议资料';
|
|
});
|
|
|
|
"""
|
|
|
|
end_emits_idx = text.find('}();', script_idx) + 5
|
|
if end_emits_idx < script_idx + 5: # fallback
|
|
end_emits_idx = text.find('}>();', script_idx) + 5
|
|
|
|
text = text[:script_idx] + insert_script + text[end_emits_idx:]
|
|
|
|
# 2. Top layout replacement
|
|
basic_info_idx = text.find("<el-form v-if=\"selectedModuleCode === 'BASIC_INFO'\"")
|
|
template_idx = text.rfind('<template>', 0, basic_info_idx)
|
|
|
|
sidebar_replacement = """<template>
|
|
<el-drawer v-model="materialDialogVisible" size="1200px" :with-header="false" :before-close="handleBeforeCloseDrawer" destroy-on-close class="custom-material-drawer">
|
|
<div class="material-layout">
|
|
<div class="material-sidebar">
|
|
<div class="sidebar-header">
|
|
<div class="sidebar-title">{{ materialDialogTitle }}</div>
|
|
</div>
|
|
<ul class="sidebar-menu">
|
|
<li :class="{ 'is-active': selectedModuleCode === 'BASIC_INFO'}" @click="handleSidebarMenuClick('BASIC_INFO')">会议基本信息</li>
|
|
<li :class="{ 'is-active': selectedModuleCode === 'WRITE_OFF_DOCS'}" @click="handleSidebarMenuClick('WRITE_OFF_DOCS')">核销材料</li>
|
|
<li :class="{ 'is-active': selectedModuleCode === 'EXPERT_PROFILE'}" @click="handleSidebarMenuClick('EXPERT_PROFILE')">专家简介/串场</li>
|
|
<li :class="{ 'is-active': selectedModuleCode === 'EXPERT_LIST'}" @click="handleSidebarMenuClick('EXPERT_LIST')">专家列表</li>
|
|
<li :class="{ 'is-active': selectedModuleCode === 'MEETING_INVOICE'}" @click="handleSidebarMenuClick('MEETING_INVOICE')">会议发票</li>
|
|
</ul>
|
|
|
|
<div class="sidebar-budget-card" v-if="materialBudgetSummary.ready">
|
|
<div class="budget-title">预算看板</div>
|
|
<div class="budget-item"><span class="text-secondary">设定预算</span> <span>¥ {{ toYuan(materialBudgetSummary.budgetCent) }}</span></div>
|
|
<div class="budget-item"><span class="text-secondary">劳务总计</span> <span>¥ {{ toYuan(materialBudgetSummary.laborTotalCent) }}</span></div>
|
|
<div class="budget-item"><span class="text-secondary">发票总计</span> <span>¥ {{ toYuan(materialBudgetSummary.meetingInvoiceTotalCent) }}</span></div>
|
|
<div class="budget-item"><span class="text-secondary">已用额度</span> <span>¥ {{ toYuan(materialBudgetSummary.usedTotalCent) }}</span></div>
|
|
<div class="budget-divider"></div>
|
|
<div class="budget-item" :class="materialBudgetSummary.overCent > 0 ? 'text-danger fw-bold' : 'text-success fw-bold'">
|
|
<span>{{ materialBudgetSummary.overCent > 0 ? '超发预算' : '剩余额度' }}</span>
|
|
<span>¥ {{ toYuan(materialBudgetSummary.overCent > 0 ? materialBudgetSummary.overCent : materialBudgetSummary.remainCent) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="materialReviewNotice" class="sidebar-notice-card">
|
|
<div class="notice-title">审核通知</div>
|
|
<div class="notice-content">{{ materialReviewNotice }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="material-main">
|
|
<div class="main-header">
|
|
<div class="main-title">{{ displayModuleTitle }}</div>
|
|
<div class="main-actions">
|
|
<el-button @click="handleCancelClick" round>关闭页面</el-button>
|
|
<el-button v-if="canMaterialSave" type="primary" @click="emit('saveMaterial')" round>保存更改</el-button>
|
|
</div>
|
|
</div>
|
|
<div class="main-content scrollbar-custom">
|
|
<div class="material-module-card" v-if="selectedModuleCode === 'BASIC_INFO'">
|
|
<el-form label-position="left" :label-width="LABEL_WIDTH.lg">
|
|
"""
|
|
|
|
text = text[:template_idx] + sidebar_replacement + text[basic_info_idx + len('<el-form v-if="selectedModuleCode === \'BASIC_INFO\'" label-position="left" :label-width="LABEL_WIDTH.lg">'):]
|
|
|
|
# 3. Replace v-else-ifs
|
|
text = text.replace(
|
|
'<el-form v-else-if="selectedModuleCode === \'WRITE_OFF_DOCS\'" label-position="left" :label-width="LABEL_WIDTH.lg">',
|
|
'</div>\n <div class="material-module-card" v-else-if="selectedModuleCode === \'WRITE_OFF_DOCS\'">\n <el-form label-position="left" :label-width="LABEL_WIDTH.lg">'
|
|
)
|
|
text = text.replace(
|
|
'<el-form v-else-if="selectedModuleCode === \'EXPERT_PROFILE\'" label-position="left" :label-width="LABEL_WIDTH.lg">',
|
|
'</div>\n <div class="material-module-card" v-else-if="selectedModuleCode === \'EXPERT_PROFILE\'">\n <el-form label-position="left" :label-width="LABEL_WIDTH.lg">'
|
|
)
|
|
text = text.replace(
|
|
'<div v-else-if="selectedModuleCode === \'EXPERT_LIST\'">',
|
|
'</div>\n <div class="material-module-card p-0" v-else-if="selectedModuleCode === \'EXPERT_LIST\'">\n <div class="expert-list-container">'
|
|
)
|
|
text = text.replace(
|
|
'<el-form v-else-if="selectedModuleCode === \'MEETING_INVOICE\'" label-position="left" :label-width="LABEL_WIDTH.lg">',
|
|
'</div>\n <div class="material-module-card" v-else-if="selectedModuleCode === \'MEETING_INVOICE\'">\n <el-form label-position="left" :label-width="LABEL_WIDTH.lg">'
|
|
)
|
|
|
|
# Replace EXPERT_LIST structure
|
|
# <div class="expert-list-container"> wrapper has been added. So the last closing tag needs adjustment
|
|
text = text.replace(
|
|
'</el-drawer>\n</template>',
|
|
' </div>\n </div>\n </div>\n </div>\n</el-drawer>\n</template>'
|
|
)
|
|
# We also have <template #footer> ... </template> which we just delete entirely.
|
|
start_footer = text.find('<template #footer>')
|
|
end_footer = text.find('</template>', start_footer) + len('</template>')
|
|
if start_footer != -1:
|
|
text = text[:start_footer] + '<!-- Footer replaced by main-header -->' + text[end_footer:]
|
|
|
|
text = text.replace('</div>\n <!-- Footer replaced by main-header -->', '</div>\n </div>\n </div>\n </div>\n </div>\n</el-drawer>')
|
|
|
|
|
|
# 4. Segmented Control for EXPERT_LIST internal tabs:
|
|
text = text.replace(
|
|
''' <el-tabs v-model="expertSubModule" class="mb-md">
|
|
<el-tab-pane label="现场照片" name="ONSITE_PHOTO" />
|
|
<el-tab-pane label="劳务协议" name="LABOR_PROTOCOL" />
|
|
</el-tabs>''',
|
|
''' <div class="custom-segmented-control mb-md">
|
|
<div class="segmented-item" :class="{ 'is-active': expertSubModule === 'ONSITE_PHOTO' }" @click="expertSubModule = 'ONSITE_PHOTO'">现场照片</div>
|
|
<div class="segmented-item" :class="{ 'is-active': expertSubModule === 'LABOR_PROTOCOL' }" @click="expertSubModule = 'LABOR_PROTOCOL'">劳务协议</div>
|
|
</div>'''
|
|
)
|
|
|
|
# 5. Add custom CSS at the end
|
|
style_idx = text.rfind('</style>')
|
|
custom_css = """
|
|
/* Custom Material Drawer Styles */
|
|
:global(.custom-material-drawer .el-drawer__body) {
|
|
padding: 0 !important;
|
|
background-color: var(--wo-bg-light, #f5f7fa);
|
|
}
|
|
|
|
.material-layout {
|
|
display: flex;
|
|
height: 100%;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.material-sidebar {
|
|
width: 280px;
|
|
background-color: var(--wo-bg-side, #ffffff);
|
|
border-right: 1px solid var(--wo-border-light, #e4e7ed);
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-shrink: 0;
|
|
padding: 24px 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.sidebar-header {
|
|
padding: 0 24px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.sidebar-title {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--wo-text-primary, #303133);
|
|
}
|
|
|
|
.sidebar-menu {
|
|
list-style: none;
|
|
padding: 0 12px;
|
|
margin: 0;
|
|
flex: 1;
|
|
}
|
|
|
|
.sidebar-menu li {
|
|
padding: 12px 16px;
|
|
margin-bottom: 8px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
color: var(--wo-text-regular, #606266);
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sidebar-menu li:hover {
|
|
background-color: var(--wo-bg-light, #f5f7fa);
|
|
color: var(--wo-text-primary, #303133);
|
|
}
|
|
|
|
.sidebar-menu li.is-active {
|
|
background-color: var(--el-color-primary-light-9);
|
|
color: var(--el-color-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.sidebar-budget-card {
|
|
margin: 20px 16px 0;
|
|
padding: 16px;
|
|
border-radius: 12px;
|
|
background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);
|
|
border: 1px solid rgba(0,0,0,0.05);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
|
|
}
|
|
|
|
.sidebar-notice-card {
|
|
margin: 16px;
|
|
padding: 16px;
|
|
border-radius: 12px;
|
|
background-color: var(--el-color-info-light-9);
|
|
border: 1px solid var(--el-color-info-light-7);
|
|
}
|
|
|
|
.notice-title {
|
|
color: var(--el-color-info);
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.notice-content {
|
|
font-size: 13px;
|
|
color: var(--wo-text-regular);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.budget-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--wo-text-primary);
|
|
}
|
|
|
|
.budget-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 13px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.budget-divider {
|
|
height: 1px;
|
|
background-color: rgba(0,0,0,0.06);
|
|
margin: 12px 0;
|
|
}
|
|
|
|
.material-main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
background-color: #f7f8fa;
|
|
}
|
|
|
|
.main-header {
|
|
height: 72px;
|
|
padding: 0 32px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background-color: #ffffff;
|
|
border-bottom: 1px solid var(--wo-border-light, #e4e7ed);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.main-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--wo-text-primary);
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
padding: 32px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.material-module-card {
|
|
background-color: #ffffff;
|
|
border-radius: 12px;
|
|
padding: 32px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.02);
|
|
border: 1px solid var(--wo-border-light, #e4e7ed);
|
|
}
|
|
|
|
.material-module-card.p-0 {
|
|
padding: 0;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
border: none;
|
|
}
|
|
|
|
/* Custom Segmented Control */
|
|
.custom-segmented-control {
|
|
display: inline-flex;
|
|
background-color: var(--wo-bg-light, #f5f7fa);
|
|
padding: 4px;
|
|
border-radius: 8px;
|
|
}
|
|
.segmented-item {
|
|
padding: 8px 24px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--wo-text-regular);
|
|
cursor: pointer;
|
|
border-radius: 6px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.segmented-item.is-active {
|
|
background-color: #ffffff;
|
|
color: var(--el-color-primary);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
}
|
|
.expert-list-container {
|
|
display: flex;
|
|
gap: 16px;
|
|
height: 100%;
|
|
}
|
|
.expert-list-container .section-card-sm,
|
|
.expert-list-container .section-card {
|
|
background-color: #ffffff;
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.02);
|
|
border: 1px solid var(--wo-border-light, #e4e7ed);
|
|
}
|
|
"""
|
|
if style_idx != -1:
|
|
text = text[:style_idx] + custom_css + text[style_idx:]
|
|
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.write(text)
|
|
print('Done!')
|