zlwu 5 meses atrás
pai
commit
2c9a98c601

+ 5 - 0
bx/bx-mobile/src/styles/index.css

@@ -888,6 +888,11 @@ a { color: inherit; text-decoration: none }
   box-shadow: 0 0 0 3px rgba(0, 201, 141, .1);
 }
 
+.form-textarea.error {
+  border-color: var(--danger);
+  box-shadow: 0 0 0 3px rgba(239, 68, 68, .1);
+}
+
 .form-textarea-counter {
   text-align: right;
   font-size: 11px;

+ 13 - 313
bx/bx-mobile/src/views/advance/AdvanceApplyPage.vue

@@ -19,27 +19,15 @@
             <span class="form-badge">{{ isEdit ? '编辑' : '新建' }}</span>
           </div>
 
-          <!-- 费用项目 -->
-          <div class="field">
-            <label class="field-label">费用项目</label>
-            <div class="picker-trigger" @click="showExpensePicker = true">
-              <div class="left">
-                <div class="value" :class="{ ph: !expenseItemDisplay }">
-                  {{ expenseItemDisplay || '请选择费用项目' }}
-                </div>
-              </div>
-              <van-icon name="arrow" class="arrow" />
-            </div>
-          </div>
-
           <!-- 申请原因 -->
           <div class="field">
             <label class="field-label">申请原因<span class="req">*</span></label>
             <textarea
-              class="form-textarea"
+              :class="['form-textarea', { error: fieldErr.reason }]"
               rows="3"
               placeholder="请输入申请原因"
               v-model="form.reason"
+              @input="fieldErr.reason = false"
             ></textarea>
           </div>
 
@@ -48,10 +36,11 @@
             <label class="field-label">申请金额<span class="req">*</span></label>
             <div class="form-input-prefix">
               <input
-                class="form-input"
+                :class="['form-input', { error: fieldErr.amount }]"
                 type="number"
-                placeholder="0.00"
+                placeholder="请输入金额"
                 v-model="form.amount"
+                @input="fieldErr.amount = false"
               />
             </div>
           </div>
@@ -126,80 +115,15 @@
         保存
       </button>
     </BottomActionBar>
-
-    <!-- 费用项目:左一级 / 右二级 双栏联动 -->
-    <van-popup
-      v-model:show="showExpensePicker"
-      position="bottom"
-      round
-      :style="{ height: '62vh', maxHeight: '520px' }"
-      class="expense-picker-popup"
-    >
-      <div class="expense-two-col-sheet">
-        <div class="expense-two-col-sheet__head">
-          <span class="expense-two-col-sheet__title">选择费用项目</span>
-          <button type="button" class="expense-two-col-sheet__close" aria-label="关闭" @click="showExpensePicker = false">
-            <van-icon name="cross" size="20" color="var(--text-muted)" />
-          </button>
-        </div>
-        <div class="expense-two-col-sheet__cols">
-          <div class="expense-col expense-col--left">
-            <div class="expense-col__caption">一级</div>
-            <div class="expense-col__scroll">
-              <button
-                v-for="item in leftItems"
-                :key="item.id"
-                type="button"
-                :class="['expense-col__row', { 'is-active': item.id === selectedLeftId }]"
-                @click="onPickLeft(item)"
-              >
-                {{ item.itemName }}
-              </button>
-              <div v-if="!leftItems.length" class="expense-col__empty">暂无费用大类</div>
-            </div>
-          </div>
-          <div class="expense-col expense-col--right">
-            <div class="expense-col__caption">二级</div>
-            <div class="expense-col__scroll">
-              <template v-if="selectedLeft">
-                <template v-if="rightItems.length">
-                  <button
-                    v-for="child in rightItems"
-                    :key="child.id"
-                    type="button"
-                    :class="['expense-col__row', { 'is-active': child.id === highlightRightId }]"
-                    @click="confirmExpensePick(child.id)"
-                  >
-                    {{ selectedLeft ? `${selectedLeft.itemName}/${child.itemName}` : child.itemName }}
-                  </button>
-                </template>
-                <div v-else class="expense-col__leaf-hint">
-                  <p class="expense-col__leaf-text">该大类下暂无明细项,可直接选用本级。</p>
-                  <button type="button" class="btn btn-primary btn-block btn-sm" @click="confirmExpensePick(selectedLeft.id)">
-                    选用「{{ selectedLeft.itemName }}」
-                  </button>
-                </div>
-              </template>
-              <div v-else class="expense-col__empty">请先选择左侧大类</div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </van-popup>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, watch, onMounted } from 'vue'
+import { ref, reactive, computed, watch, onMounted } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { showToast, showDialog } from 'vant'
 import { advanceApi } from '@/api'
 import { BizError } from '@/api/client'
-import {
-  useExpenseItemTree,
-  formatExpenseName,
-  type ExpenseItem,
-} from '@/hooks/useExpenseItemTree'
 import { useAttachment } from '@/hooks/useAttachment'
 import { SUBMIT_APPROVE_BUTTON_COLOR } from '@/constants/dialog'
 import BottomActionBar from '@/components/mobile/BottomActionBar.vue'
@@ -245,115 +169,39 @@ const attachmentAccept =
     '.7z',
   ].join(',')
 
-const { expenseTree, flatList, load: loadExpenseItems } = useExpenseItemTree()
 const attachment = useAttachment()
 
 const isEdit = computed(() => !!route.query.id)
 const savedId = ref<number | null>(null)
 const submitting = ref(false)
-const showExpensePicker = ref(false)
-/** 左栏当前选中的一级 id */
-const selectedLeftId = ref<number | null>(null)
 
 const form = ref({
   reason: '',
   amount: '',
   remark: '',
-  expenseItemId: null as number | null,
-})
-
-const expenseItemDisplay = computed(() => {
-  if (!form.value.expenseItemId) return ''
-  return formatExpenseName(form.value.expenseItemId, flatList.value)
-})
-
-function isExpenseEnabled(item: ExpenseItem): boolean {
-  return item.status === 1 || item.status === '1'
-}
-
-const leftItems = computed(() =>
-  [...expenseTree.value].filter(isExpenseEnabled).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)),
-)
-
-const selectedLeft = computed(() => leftItems.value.find((i) => i.id === selectedLeftId.value) ?? null)
-
-const rightItems = computed(() => {
-  const raw = selectedLeft.value?.children
-  if (!Array.isArray(raw) || !raw.length) return []
-  return [...raw].filter(isExpenseEnabled).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
-})
-
-/** 右栏高亮:当前表单已选项若在右侧列表中则高亮 */
-const highlightRightId = computed(() => {
-  const id = form.value.expenseItemId
-  if (id == null || !rightItems.value.some((c) => c.id === id)) return null
-  return id
-})
-
-/** 从 expenseItemId 推导所属一级(沿 parent 链回到根) */
-function findRootForItem(itemId: number): ExpenseItem | null {
-  const node = flatList.value.find((i) => i.id === itemId)
-  if (!node) return null
-  let cur: ExpenseItem | null = node
-  const guard = new Set<number>()
-  while (cur !== null && !guard.has(cur.id)) {
-    guard.add(cur.id)
-    if (!cur.parentId || cur.parentId === 0) return cur
-    const pid: number = cur.parentId
-    cur = flatList.value.find((i) => i.id === pid) ?? null
-  }
-  return node
-}
-
-function syncExpensePickerSelection() {
-  const id = form.value.expenseItemId
-  if (!leftItems.value.length) {
-    selectedLeftId.value = null
-    return
-  }
-  if (id == null) {
-    selectedLeftId.value = leftItems.value[0].id
-    return
-  }
-  const root = findRootForItem(id)
-  const match = root && leftItems.value.some((x) => x.id === root.id) ? root : null
-  selectedLeftId.value = match?.id ?? leftItems.value[0].id
-}
-
-watch(showExpensePicker, (open) => {
-  if (open) syncExpensePickerSelection()
 })
 
-function onPickLeft(item: ExpenseItem) {
-  selectedLeftId.value = item.id
-}
-
-function confirmExpensePick(id: number) {
-  form.value.expenseItemId = id
-  showExpensePicker.value = false
-}
-
-function validateAmount(val: string): boolean {
-  return Number(val) > 0
-}
+/** 与校验提示联动:未填「申请原因 / 申请金额」时红框提示 */
+const fieldErr = reactive({ reason: false, amount: false })
 
 /** 保存草稿前校验(与表单项 * 一致) */
 function validateAdvanceApplyForm(): boolean {
+  fieldErr.reason = false
+  fieldErr.amount = false
   if (!form.value.reason?.trim()) {
+    fieldErr.reason = true
     showToast('请输入申请原因')
     return false
   }
-  if (form.value.expenseItemId == null || form.value.expenseItemId === 0) {
-    showToast('请选择费用项目')
-    return false
-  }
   const raw = String(form.value.amount ?? '').trim()
   if (!raw) {
+    fieldErr.amount = true
     showToast('请输入申请金额')
     return false
   }
   const amt = Number(raw)
   if (Number.isNaN(amt) || amt <= 0) {
+    fieldErr.amount = true
     showToast('请输入大于 0 的申请金额')
     return false
   }
@@ -393,9 +241,6 @@ async function onSubmit() {
       amount: Number(form.value.amount),
       remark: form.value.remark,
     }
-    if (form.value.expenseItemId) {
-      data.expenseItemId = form.value.expenseItemId
-    }
 
     let id: number
     if (isEdit.value || savedId.value) {
@@ -442,13 +287,6 @@ async function onSubmit() {
   }
 }
 
-function pickExpenseItemId(data: Record<string, unknown>): number | null {
-  const raw = data.expenseItemId ?? data.expense_item_id
-  if (raw == null || raw === '') return null
-  const n = Number(raw)
-  return Number.isFinite(n) && n > 0 ? n : null
-}
-
 function pickRemark(data: Record<string, unknown>): string {
   const v = data.remark
   if (v == null) return ''
@@ -466,7 +304,6 @@ async function loadEditData() {
         reason: (data.reason as string) || '',
         amount: data.amount != null ? String(data.amount) : '',
         remark: pickRemark(data),
-        expenseItemId: pickExpenseItemId(data),
       }
       await attachment.loadList('advance', id)
     }
@@ -479,7 +316,6 @@ onMounted(async () => {
   // 从详情「重新提交」会写入 bx_edit_resubmit;已进入编辑页即视为完成跳转,
   // 若保留标记,用户点顶栏返回详情时仍会误触发「是否立即提交」弹窗。
   sessionStorage.removeItem('bx_edit_resubmit')
-  await loadExpenseItems()
   await loadEditData()
 })
 
@@ -581,140 +417,4 @@ watch(
   word-break: break-word;
 }
 
-.expense-picker-popup.van-popup--bottom.van-popup--round {
-  overflow: hidden;
-}
-
-.expense-two-col-sheet {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  min-height: 280px;
-  background: var(--surface);
-}
-
-.expense-two-col-sheet__head {
-  flex-shrink: 0;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 16px 10px;
-  border-bottom: 1px solid var(--border);
-}
-
-.expense-two-col-sheet__title {
-  font-size: 16px;
-  font-weight: 700;
-  color: var(--text);
-}
-
-.expense-two-col-sheet__close {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 36px;
-  height: 36px;
-  margin: -8px -8px -8px 0;
-  padding: 0;
-  border: none;
-  background: transparent;
-  cursor: pointer;
-  border-radius: 8px;
-}
-
-.expense-two-col-sheet__close:active {
-  background: var(--bg);
-}
-
-.expense-two-col-sheet__cols {
-  flex: 1;
-  display: flex;
-  min-height: 0;
-}
-
-.expense-col {
-  display: flex;
-  flex-direction: column;
-  min-width: 0;
-  border-right: 1px solid var(--border);
-}
-
-.expense-col:last-child {
-  border-right: none;
-}
-
-.expense-col--left {
-  flex: 0 0 42%;
-  max-width: 46%;
-  background: var(--bg);
-}
-
-.expense-col--right {
-  flex: 1;
-  background: var(--surface);
-}
-
-.expense-col__caption {
-  flex-shrink: 0;
-  padding: 8px 12px;
-  font-size: 11px;
-  font-weight: 600;
-  color: var(--text-muted);
-  letter-spacing: 0.06em;
-  border-bottom: 1px solid var(--border);
-}
-
-.expense-col__scroll {
-  flex: 1;
-  overflow-y: auto;
-  -webkit-overflow-scrolling: touch;
-  padding: 6px 0 12px;
-}
-
-.expense-col__row {
-  display: block;
-  width: 100%;
-  text-align: left;
-  padding: 12px 14px;
-  font-size: 14px;
-  line-height: 1.35;
-  color: var(--text);
-  background: transparent;
-  border: none;
-  cursor: pointer;
-  transition: background 0.12s;
-}
-
-.expense-col__row:active {
-  background: rgba(16, 185, 129, 0.08);
-}
-
-.expense-col__row.is-active {
-  background: var(--primary-bg);
-  color: var(--primary-dark);
-  font-weight: 600;
-}
-
-.expense-col__empty {
-  padding: 24px 14px;
-  font-size: 13px;
-  color: var(--text-muted);
-  text-align: center;
-}
-
-.expense-col__leaf-hint {
-  padding: 16px 14px;
-}
-
-.expense-col__leaf-text {
-  font-size: 13px;
-  color: var(--text-sec);
-  line-height: 1.5;
-  margin: 0 0 12px;
-}
-
-.btn-sm {
-  padding: 10px 14px;
-  font-size: 14px;
-}
 </style>

+ 1 - 20
bx/bx-mobile/src/views/advance/AdvanceApprovePage.vue

@@ -146,10 +146,6 @@
               <div class="di-label">申请人</div>
               <div class="di-value">{{ detailData.applicantName || '-' }}</div>
             </div>
-            <div v-if="expenseItemDisplayPopup" class="detail-item full">
-              <div class="di-label">费用项目</div>
-              <div class="di-value">{{ expenseItemDisplayPopup }}</div>
-            </div>
             <div class="detail-item">
               <div class="di-label">申请原因</div>
               <div class="di-value">{{ detailData.reason || '-' }}</div>
@@ -319,27 +315,16 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, onMounted } from 'vue'
+import { ref, reactive, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { showToast, showImagePreview } from 'vant'
 import { PRIMARY_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
 import { advanceApi, paymentApi, attachmentApi } from '@/api'
 import { statusLabel, formatAmount, formatDateTime, formatApplyDateTime } from '@/utils/format'
-import { useExpenseItemTree, formatExpenseName } from '@/hooks/useExpenseItemTree'
 import StatusTag from '@/components/mobile/StatusTag.vue'
 
 const router = useRouter()
 
-const { flatList, load: loadExpenseTree } = useExpenseItemTree()
-
-const expenseItemDisplayPopup = computed(() => {
-  const raw = detailData.value?.expenseItemId ?? detailData.value?.expense_item_id
-  if (raw == null || raw === '') return ''
-  const id = Number(raw)
-  if (!Number.isFinite(id)) return ''
-  return formatExpenseName(id, flatList.value) || `ID ${id}`
-})
-
 const isAdvanceIssuedInPopup = computed(() => {
   const d = detailData.value
   return !!(d && (d.issueStatus === 'issued' || d.status === 'issued'))
@@ -556,10 +541,6 @@ async function openDetail(item: any) {
   }
 }
 
-onMounted(() => {
-  loadExpenseTree()
-})
-
 </script>
 
 <style scoped>

+ 0 - 14
bx/bx-mobile/src/views/advance/AdvanceDetailPage.vue

@@ -52,10 +52,6 @@
               }}
             </div>
           </div>
-          <div v-if="expenseItemDisplay" class="detail-item">
-            <div class="di-label">费用项目</div>
-            <div class="di-value">{{ expenseItemDisplay }}</div>
-          </div>
           <div class="detail-item">
             <div class="di-label">申请人</div>
             <div class="di-value">{{ detail.applicantName || '-' }}</div>
@@ -252,8 +248,6 @@ import { showToast, showDialog, showConfirmDialog, showImagePreview } from 'vant
 import { DELETE_CONFIRM_BUTTON_COLOR } from '@/constants/dialog'
 import { advanceApi, attachmentApi, paymentApi } from '@/api'
 import { statusLabel, statusTone, formatAmount, formatDateTime, formatApplyDateTime } from '@/utils/format'
-import { useExpenseItemTree, formatExpenseName } from '@/hooks/useExpenseItemTree'
-import StatusTag from '@/components/mobile/StatusTag.vue'
 
 interface AttachmentInfo {
   id: number
@@ -265,8 +259,6 @@ interface AttachmentInfo {
 
 const router = useRouter()
 const route = useRoute()
-const { flatList, load: loadExpenseItems } = useExpenseItemTree()
-
 const detail = ref<any>(null)
 const approvals = ref<any[]>([])
 const attachments = ref<AttachmentInfo[]>([])
@@ -292,11 +284,6 @@ const offsetTotalDisplay = computed(() => {
   return formatAmount(sum)
 })
 
-const expenseItemDisplay = computed(() => {
-  if (!detail.value?.expenseItemId) return ''
-  return formatExpenseName(detail.value.expenseItemId, flatList.value)
-})
-
 const showActions = computed(() => {
   if (!detail.value) return false
   const status = detail.value.status
@@ -494,7 +481,6 @@ async function checkResubmitPrompt() {
 }
 
 onMounted(async () => {
-  await loadExpenseItems()
   await loadDetail()
   await checkResubmitPrompt()
 })

+ 42 - 3
bx/bx-web/src/styles/index.css

@@ -107,12 +107,17 @@ body {
 
 .bx-layout {
   display: flex;
-  min-height: 100vh;
+  height: 100vh;
+  min-height: 0;
+  overflow: hidden;
 }
 
 .sidebar {
   width: 220px;
   flex-shrink: 0;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
   background: var(--surface);
   border-right: 1px solid var(--border);
   padding: 24px 16px;
@@ -120,6 +125,12 @@ body {
   overflow: hidden;
 }
 
+/* 侧栏菜单区单独纵向滚动(logo 固定) */
+.sidebar > .el-scrollbar {
+  flex: 1;
+  min-height: 0;
+}
+
 .sidebar.collapsed {
   width: 0;
   padding-left: 0;
@@ -128,6 +139,7 @@ body {
 }
 
 .logo {
+  flex-shrink: 0;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -313,11 +325,14 @@ body {
 .main {
   flex: 1;
   min-width: 0;
+  min-height: 0;
   display: flex;
   flex-direction: column;
+  overflow: hidden;
 }
 
 .main > .header {
+  flex-shrink: 0;
   height: 56px;
   padding: 0 24px;
   display: flex;
@@ -476,10 +491,21 @@ body {
 .content {
   flex: 1;
   min-height: 0;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
   padding: 24px;
   background: var(--bg-page);
 }
 
+/* 右侧业务内容区单独纵向滚动(页签条固定在上) */
+.content-scroll {
+  flex: 1;
+  min-height: 0;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
 .tabs-strip {
   display: flex;
   flex-wrap: wrap;
@@ -1085,10 +1111,23 @@ body {
   color: var(--text-muted);
 }
 
-/* 列表合计:第 4 列显示「本页合计(n单)」加粗 */
-.table-scroll.table-page-summary .el-table__footer-wrapper td:nth-child(4) .cell {
+/* 列表合计:第 4 列常为「本页合计(n单)」加粗(备用金申请页除外,见下条) */
+.table-scroll.table-page-summary:not(.advance-apply-summary-merge)
+  .el-table__footer-wrapper
+  td:nth-child(4)
+  .cell {
+  font-weight: 600;
+  color: var(--text);
+}
+
+/* 备用金申请:合计行首格 colspan=4 合并多选+序号+单号+申请人 */
+.table-scroll.table-page-summary.advance-apply-summary-merge
+  .el-table__footer-wrapper
+  td:first-child
+  .cell {
   font-weight: 600;
   color: var(--text);
+  text-align: center;
 }
 
 .bx-table .col-actions .cell > * {

+ 3 - 1
bx/bx-web/src/views/MainLayout.vue

@@ -375,7 +375,9 @@ onMounted(async () => {
           <button class="menu-item" @click="closeAllTabs">关闭全部页签</button>
         </div>
 
-        <component :is="currentPage" v-if="currentPage" />
+        <div class="content-scroll">
+          <component :is="currentPage" v-if="currentPage" />
+        </div>
       </div>
     </section>
   </div>

+ 72 - 9
bx/bx-web/src/views/pages/AdvanceApplyPage.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { reactive, ref, computed, onMounted } from 'vue'
+import { reactive, ref, computed, onMounted, nextTick, watch } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, EditPen, Upload, View, Close, Delete } from '@element-plus/icons-vue'
 import { advanceApi, paymentApi } from '../../api'
@@ -90,6 +90,27 @@ const flowLoading = ref(false)
 const viewFlowNodes = ref<any[]>([])
 const viewOffsetRecords = ref<any[]>([])
 
+/** 备用金列表合计行共 11 列;首 4 列(多选+序号+单号+申请人)合并展示「本页合计」 */
+const ADVANCE_APPLY_SUMMARY_COLS = 11
+const ADVANCE_APPLY_SUMMARY_LABEL_COLSPAN = 4
+
+function mergeAdvanceApplySummaryFooter() {
+  nextTick(() => {
+    const root = document.querySelector('.advance-apply-summary-merge .el-table__footer-wrapper')
+    if (!root) return
+    const tr = root.querySelector('tfoot tr') || root.querySelector('table tr')
+    if (!tr) return
+    const cells = Array.from(tr.querySelectorAll('td')) as HTMLTableCellElement[]
+    if (cells.length !== ADVANCE_APPLY_SUMMARY_COLS) return
+    const first = cells[0]
+    if (first.colSpan === ADVANCE_APPLY_SUMMARY_LABEL_COLSPAN) return
+    first.colSpan = ADVANCE_APPLY_SUMMARY_LABEL_COLSPAN
+    for (let i = ADVANCE_APPLY_SUMMARY_LABEL_COLSPAN - 1; i >= 1; i--) {
+      cells[i].remove()
+    }
+  })
+}
+
 function onSelectionChange(rows: any[]) {
   selectedRows.value = rows
 }
@@ -103,6 +124,8 @@ onMounted(() => {
   refreshListAndStats()
 })
 
+watch([rows, loading], () => mergeAdvanceApplySummaryFooter(), { flush: 'post' })
+
 function toggleMoreFilters() {
   showMoreFilters.value = !showMoreFilters.value
 }
@@ -173,16 +196,36 @@ async function handleToolbarSubmit() {
 }
 
 async function submitAdd() {
+  const reason = addForm.reason?.trim() ?? ''
+  if (!reason) {
+    ElMessage.warning('请输入申请原因')
+    return
+  }
+  const amtRaw = String(addForm.amount ?? '').trim()
+  if (!amtRaw) {
+    ElMessage.warning('请输入申请金额')
+    return
+  }
+  const amountNum = Number(amtRaw)
+  if (Number.isNaN(amountNum) || amountNum <= 0) {
+    ElMessage.warning('请输入大于 0 的申请金额')
+    return
+  }
   loadingMap.addSubmit = true
   try {
     if (editingId.value) {
       await advanceApi.update(editingId.value, {
-        reason: addForm.reason,
-        amount: Number(addForm.amount),
+        reason,
+        amount: amountNum,
+        remark: addForm.remark || undefined,
       })
       ElMessage.success('保存成功')
     } else {
-      await advanceApi.create(addForm)
+      await advanceApi.create({
+        reason,
+        amount: amountNum,
+        remark: addForm.remark || undefined,
+      })
       ElMessage.success('新增成功')
     }
     dialogs.add = false
@@ -280,7 +323,20 @@ function summaryMethod(params: { data: any[] }) {
   }, 0)
   const totalOffset = data.reduce((s, r) => s + Number(r.offsetAmount || 0), 0)
   const lbl = `本页合计(${n}单)`
-  return ['', '', '', lbl, '', formatSumMoney(totalApply), formatSumMoney(totalActual), formatSumMoney(totalOffset), '', '', '']
+  // 文案放在前 4 个单元格首项,由 mergeAdvanceApplySummaryFooter 合并 colspan=4
+  return [
+    lbl,
+    '',
+    '',
+    '',
+    '',
+    formatSumMoney(totalApply),
+    formatSumMoney(totalActual),
+    formatSumMoney(totalOffset),
+    '',
+    '',
+    '',
+  ]
 }
 </script>
 
@@ -311,7 +367,7 @@ function summaryMethod(params: { data: any[] }) {
       :loading="loading"
       :show-summary="true"
       :summary-method="summaryMethod"
-      table-scroll-class="table-page-summary"
+      table-scroll-class="table-page-summary advance-apply-summary-merge"
       :table-max-height="'min(520px, calc(100vh - 300px))'"
       @toggle-more="toggleMoreFilters"
       @reset="handleReset"
@@ -408,7 +464,7 @@ function summaryMethod(params: { data: any[] }) {
             <span v-else class="node-current-empty">-</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" :min-width="420" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="300" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
             <BaseOpButton v-if="row.status === 'draft' || row.status === 'rejected'" :icon="EditPen" @click="openEdit(row)">编辑</BaseOpButton>
@@ -426,11 +482,11 @@ function summaryMethod(params: { data: any[] }) {
         <h4>基础信息</h4>
         <div class="form-grid-2">
           <div class="field">
-            <label>申请原因</label>
+            <label>申请原因<span class="req">*</span></label>
             <BaseInput v-model="addForm.reason" type="textarea" :rows="3" placeholder="请输入申请原因" />
           </div>
           <div class="field">
-            <label>申请金额(元)</label>
+            <label>申请金额(元)<span class="req">*</span></label>
             <BaseInput v-model="addForm.amount" placeholder="请输入申请金额" />
           </div>
         </div>
@@ -517,3 +573,10 @@ function summaryMethod(params: { data: any[] }) {
     </BaseDialog>
   </div>
 </template>
+
+<style scoped>
+.field > label .req {
+  color: var(--el-color-danger);
+  margin-left: 2px;
+}
+</style>

+ 2 - 2
bx/bx-web/src/views/pages/AdvanceApprovePage.vue

@@ -323,7 +323,7 @@ async function handleConfirmApprove() {
               </template>
             </el-table-column>
             <el-table-column prop="applyTime" label="申请时间" min-width="140" />
-            <el-table-column label="操作" :min-width="340" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="260" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
                 <BaseOpButton :icon="Check" @click="openApproveDialog([row], 'approve')">通过</BaseOpButton>
@@ -408,7 +408,7 @@ async function handleConfirmApprove() {
               </template>
             </el-table-column>
             <el-table-column prop="applyTime" label="申请时间" min-width="140" />
-            <el-table-column label="操作" :min-width="120" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="120" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
               </template>

+ 1 - 1
bx/bx-web/src/views/pages/AdvanceIssuePage.vue

@@ -445,7 +445,7 @@ async function openView(row: any) {
         </el-table-column>
         <el-table-column
           label="操作"
-          :min-width="activeTab === 'pending' ? 340 : 200"
+          :width="activeTab === 'pending' ? 240 : 140"
           fixed="right"
           class-name="col-actions"
         >

+ 1 - 1
bx/bx-web/src/views/pages/AllPage.vue

@@ -322,7 +322,7 @@ onMounted(() => {
           </template>
         </el-table-column>
         <el-table-column prop="applyTime" label="申请时间" min-width="150" />
-        <el-table-column label="操作" :min-width="120" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="120" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openViewDetail(row)">查看</BaseOpButton>
           </template>

+ 1 - 1
bx/bx-web/src/views/pages/DemoPage.vue

@@ -284,7 +284,7 @@ onMounted(() => {
           <BaseTag :tone="statusTone(row.status)">{{ statusLabel(row.status) }}</BaseTag>
         </template>
       </el-table-column>
-      <el-table-column label="操作" :min-width="220" fixed="right" class-name="col-actions">
+      <el-table-column label="操作" width="180" fixed="right" class-name="col-actions">
         <template #default>
           <BaseOpButton
             primary

+ 1 - 1
bx/bx-web/src/views/pages/ExpenseProjectPage.vue

@@ -281,7 +281,7 @@ onMounted(() => {
             <BaseTag :tone="statusTone(row.status)">{{ statusLabel(row.status) }}</BaseTag>
           </template>
         </el-table-column>
-        <el-table-column label="操作" :min-width="360" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="280" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="EditPen" :loading="loadingMap.rowEdit" @click="openEditDialog(row)">
               编辑

+ 1 - 1
bx/bx-web/src/views/pages/MenuManagePage.vue

@@ -204,7 +204,7 @@ onMounted(() => {
               <BaseTag :tone="statusTone(row.status)">{{ statusLabel(row.status) }}</BaseTag>
             </template>
           </el-table-column>
-          <el-table-column label="操作" :min-width="380" fixed="right" class-name="col-actions">
+          <el-table-column label="操作" width="300" fixed="right" class-name="col-actions">
             <template #default="{ row }">
               <BaseOpButton :icon="EditPen" @click="openEditDialog(row)">编辑</BaseOpButton>
               <BaseOpButton primary :icon="Plus" @click="openAddDialog(row.id)">新增子菜单</BaseOpButton>

+ 1 - 1
bx/bx-web/src/views/pages/MinePage.vue

@@ -246,7 +246,7 @@ onMounted(() => {
           </template>
         </el-table-column>
         <el-table-column prop="applyTime" label="申请时间" min-width="150" />
-        <el-table-column label="操作" :min-width="120" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="120" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openViewDetail(row)">查看</BaseOpButton>
           </template>

+ 2 - 2
bx/bx-web/src/views/pages/PaymentPage.vue

@@ -597,7 +597,7 @@ function processedSummaryMethod(params: { data: any[] }) {
                 }}
               </template>
             </el-table-column>
-            <el-table-column label="操作" :min-width="340" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="260" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
                 <BaseOpButton :icon="Money" @click="openIssueDialog(row)">发放</BaseOpButton>
@@ -696,7 +696,7 @@ function processedSummaryMethod(params: { data: any[] }) {
                 {{ row.payTime || '-' }}
               </template>
             </el-table-column>
-            <el-table-column label="操作" :min-width="120" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="120" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openProcessedView(row)">查看</BaseOpButton>
               </template>

+ 1 - 1
bx/bx-web/src/views/pages/ReimburseApplyPage.vue

@@ -591,7 +591,7 @@ function viewDetailSummary({ columns, data }: { columns: any[]; data: any[] }) {
             <span v-else class="node-current-empty">-</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" :min-width="420" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="300" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
             <BaseOpButton v-if="row.status === 'draft' || row.status === 'rejected'" :icon="EditPen" @click="openEditDialog(row)">编辑</BaseOpButton>

+ 2 - 2
bx/bx-web/src/views/pages/ReimburseApprovePage.vue

@@ -443,7 +443,7 @@ function viewDetailSummary({ columns, data }: { columns: any[]; data: any[] }) {
               </template>
             </el-table-column>
             <el-table-column prop="applyTime" label="申请时间" min-width="150" />
-            <el-table-column label="操作" :min-width="340" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="260" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
                 <BaseOpButton :icon="Check" @click="openApproveDialog([row], 'approve')">通过</BaseOpButton>
@@ -558,7 +558,7 @@ function viewDetailSummary({ columns, data }: { columns: any[]; data: any[] }) {
               </template>
             </el-table-column>
             <el-table-column prop="applyTime" label="申请时间" min-width="150" />
-            <el-table-column label="操作" :min-width="120" fixed="right" class-name="col-actions">
+            <el-table-column label="操作" width="120" fixed="right" class-name="col-actions">
               <template #default="{ row }">
                 <BaseOpButton primary :icon="View" @click="openView(row)">查看</BaseOpButton>
               </template>

+ 1 - 1
bx/bx-web/src/views/pages/RoleManagePage.vue

@@ -248,7 +248,7 @@ onMounted(() => {
           <el-table-column prop="userCount" label="关联用户数" min-width="110" />
           <el-table-column prop="remark" label="备注" min-width="200" />
           <el-table-column prop="createTime" label="创建时间" min-width="150" />
-          <el-table-column label="操作" :min-width="380" fixed="right" class-name="col-actions">
+          <el-table-column label="操作" width="320" fixed="right" class-name="col-actions">
             <template #default="{ row }">
               <BaseOpButton primary :icon="View" @click="openViewDialog(row)">查看</BaseOpButton>
               <BaseOpButton :icon="Menu" @click="openMenuAssignDialog(row)">分配菜单</BaseOpButton>

+ 1 - 1
bx/bx-web/src/views/pages/TodoPage.vue

@@ -360,7 +360,7 @@ onMounted(() => {
           </template>
         </el-table-column>
         <el-table-column prop="applyTime" label="申请时间" min-width="150" />
-        <el-table-column label="操作" :min-width="340" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="260" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openViewDetail(row)">查看</BaseOpButton>
             <BaseOpButton :icon="Check" :loading="loadingMap.rowPass" @click="handleApprove(row)">通过</BaseOpButton>

+ 1 - 1
bx/bx-web/src/views/pages/UserManagePage.vue

@@ -368,7 +368,7 @@ onMounted(() => {
           </template>
         </el-table-column>
         <el-table-column prop="createTime" label="创建时间" min-width="150" />
-        <el-table-column label="操作" :min-width="460" fixed="right" class-name="col-actions">
+        <el-table-column label="操作" width="380" fixed="right" class-name="col-actions">
           <template #default="{ row }">
             <BaseOpButton primary :icon="View" @click="openViewDialog(row)">查看</BaseOpButton>
             <BaseOpButton :icon="EditPen" @click="openEditDialog(row)">编辑</BaseOpButton>

+ 1 - 1
bx/bx-web/src/views/pages/WfAdvancePage.vue

@@ -238,7 +238,7 @@ async function moveDown(node: Record<string, any>) {
               </BaseTag>
             </template>
           </el-table-column>
-          <el-table-column label="操作" :min-width="420" fixed="right" class-name="col-actions">
+          <el-table-column label="操作" width="300" fixed="right" class-name="col-actions">
             <template #default="{ row }">
               <BaseOpButton primary :icon="EditPen" @click="openEditDialog(row)">
                 编辑

+ 1 - 1
bx/bx-web/src/views/pages/WfReimbursePage.vue

@@ -238,7 +238,7 @@ async function moveDown(node: Record<string, any>) {
               </BaseTag>
             </template>
           </el-table-column>
-          <el-table-column label="操作" :min-width="420" fixed="right" class-name="col-actions">
+          <el-table-column label="操作" width="300" fixed="right" class-name="col-actions">
             <template #default="{ row }">
               <BaseOpButton primary :icon="EditPen" @click="openEditDialog(row)">
                 编辑