提交
This commit is contained in:
1 parent
c3bd2c00ae
commit
a52fe77e34
39 files changed
+609647
-9238
No files matched your search
@@ -57,6 +57,12 @@
|
||||
<el-button class="btn-main" type="primary" @click="openAddDialog">
|
||||
<el-icon><Plus /></el-icon>新增报销申请
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :loading="templateLoading" @click="downloadImportTemplate">
|
||||
<el-icon><Download /></el-icon>下载模板
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" @click="openImportDialog">
|
||||
<el-icon><FolderAdd /></el-icon>批量导入
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="selectedRows.length !== 1 || !isDraftEditable(selectedRows[0])"
|
||||
@@ -83,6 +89,13 @@
|
||||
<el-button class="btn-ghost" @click="openFlowDialog()">
|
||||
<el-icon><View /></el-icon>查看审批流
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasActiveFilters"
|
||||
class="btn-ghost"
|
||||
@click="clearAllFilters"
|
||||
>
|
||||
<el-icon><Filter /></el-icon>清空列过滤
|
||||
</el-button>
|
||||
<ListToolbarEndActions
|
||||
:loading="loading"
|
||||
:column-defs="listColumnDefs"
|
||||
@@ -100,7 +113,7 @@
|
||||
<div ref="reimburseApplyTableWrapRef" class="table-scroll bx-list-table-wrap">
|
||||
<ListTable table-key="bx-reimburse-apply"
|
||||
class="list-table"
|
||||
:data="rows"
|
||||
:data="filteredRows"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
@selection-change="onSelectionChange"
|
||||
@@ -113,20 +126,51 @@
|
||||
v-if="isColumnVisible('applyTime')"
|
||||
prop="applyTime"
|
||||
label="申请时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="applyCode"
|
||||
label="报销单号"
|
||||
class-name="col-apply-code"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">申请时间</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="applyTime"
|
||||
label="申请时间"
|
||||
type="datetime"
|
||||
:is-active="isFilterActive('applyTime')"
|
||||
:model-value="filters.applyTime || null"
|
||||
@update:model-value="(v) => setFilterState('applyTime', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
{{ formatApplyDateOnly(row.applyTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="applyCode"
|
||||
label="报销单号"
|
||||
class-name="col-apply-code"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">报销单号</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="applyCode"
|
||||
label="报销单号"
|
||||
type="text"
|
||||
:is-active="isFilterActive('applyCode')"
|
||||
:model-value="filters.applyCode || null"
|
||||
:options="collectUniqueOptions('applyCode')"
|
||||
@update:model-value="(v) => setFilterState('applyCode', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="openView(row)">{{ row.applyCode }}</el-link>
|
||||
</template>
|
||||
@@ -135,20 +179,49 @@
|
||||
v-if="isColumnVisible('title')"
|
||||
prop="title"
|
||||
label="报销标题"
|
||||
min-width="180"
|
||||
align="left"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('expenseName')"
|
||||
prop="expenseName"
|
||||
label="费用项目"
|
||||
min-width="260"
|
||||
min-width="200"
|
||||
align="left"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">报销标题</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="title"
|
||||
label="报销标题"
|
||||
type="text"
|
||||
:is-active="isFilterActive('title')"
|
||||
:model-value="filters.title || null"
|
||||
:options="collectUniqueOptions('title')"
|
||||
@update:model-value="(v) => setFilterState('title', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('expenseName')"
|
||||
prop="expenseName"
|
||||
label="费用项目"
|
||||
min-width="280"
|
||||
align="left"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">费用项目</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="expenseName"
|
||||
label="费用项目"
|
||||
type="text"
|
||||
:is-active="isFilterActive('expenseName')"
|
||||
:model-value="filters.expenseName || null"
|
||||
:options="collectUniqueOptions('expenseName')"
|
||||
@update:model-value="(v) => setFilterState('expenseName', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<div class="expense-items-lines">
|
||||
<div
|
||||
@@ -167,18 +240,46 @@
|
||||
label="报销金额(元)"
|
||||
:formatter="moneyTableFormatter"
|
||||
class-name="col-money"
|
||||
min-width="120"
|
||||
min-width="150"
|
||||
align="right"
|
||||
header-align="center"
|
||||
/>
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label is-right">报销金额(元)</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="amount"
|
||||
label="报销金额(元)"
|
||||
type="number"
|
||||
:is-active="isFilterActive('amount')"
|
||||
:model-value="filters.amount || null"
|
||||
@update:model-value="(v) => setFilterState('amount', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="isColumnVisible('status')"
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="110"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">状态</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="status"
|
||||
label="状态"
|
||||
type="enum"
|
||||
:is-active="isFilterActive('status')"
|
||||
:model-value="filters.status || null"
|
||||
:options="collectUniqueOptions('status')"
|
||||
@update:model-value="(v) => setFilterState('status', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" size="small">
|
||||
{{ statusLabel(row.status) }}
|
||||
@@ -189,10 +290,24 @@
|
||||
v-if="isColumnVisible('currentNode')"
|
||||
prop="currentNode"
|
||||
label="当前节点"
|
||||
min-width="120"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #header>
|
||||
<div class="col-header-with-filter">
|
||||
<span class="col-header-with-filter__label">当前节点</span>
|
||||
<ColumnHeaderFilter
|
||||
prop="currentNode"
|
||||
label="当前节点"
|
||||
type="text"
|
||||
:is-active="isFilterActive('currentNode')"
|
||||
:model-value="filters.currentNode || null"
|
||||
:options="collectUniqueOptions('currentNode')"
|
||||
@update:model-value="(v) => setFilterState('currentNode', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="shouldShowWorkflowNodeTag(row.currentNode, row.status)"
|
||||
@@ -209,7 +324,7 @@
|
||||
ref="reimburseApplyStatsRowRef"
|
||||
storage-key="bx-reimburse-apply-column-stats"
|
||||
:columns="reimburseApplyStatsRowColumns"
|
||||
:data="rows"
|
||||
:data="filteredRows"
|
||||
:table-wrap-el="reimburseApplyTableWrapRef"
|
||||
:get-cell-value="getReimburseApplyStatCellValue"
|
||||
/>
|
||||
@@ -459,6 +574,81 @@
|
||||
@confirm="onInvoicesPicked"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="importDialogVisible"
|
||||
title="批量导入报销申请"
|
||||
width="720px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<p class="import-hint">
|
||||
请先下载最新模板填写。灰色【示例】行导入时自动跳过。导入结果为「待提交」草稿,发票请事后在系统中关联。导入完成后可下载带「导入结果」列的结果表。
|
||||
</p>
|
||||
<el-upload
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
accept=".xlsx,.xls"
|
||||
:file-list="importFileList"
|
||||
:on-change="onImportFileChange"
|
||||
:on-remove="onImportFileRemove"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div v-if="importResult" class="import-result">
|
||||
<p v-if="importResult.tip" class="import-tip">{{ importResult.tip }}</p>
|
||||
<p>
|
||||
成功 {{ importResult.successGroups || 0 }} 组({{ importResult.successRows || 0 }} 行),失败
|
||||
{{ importResult.failGroups || 0 }} 组({{ importResult.failRows || 0 }} 行)
|
||||
<span v-if="importResult.skippedRows">,跳过 {{ importResult.skippedRows }} 行</span>
|
||||
</p>
|
||||
<el-table
|
||||
v-if="importResult.rowResults?.length"
|
||||
:data="importResult.rowResults"
|
||||
size="small"
|
||||
max-height="280"
|
||||
>
|
||||
<el-table-column prop="excelRow" label="行号" min-width="70" align="center" />
|
||||
<el-table-column prop="groupNo" label="分组号" min-width="80" />
|
||||
<el-table-column prop="status" label="导入结果" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.status === '成功' ? 'success' : row.status === '失败' ? 'danger' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" label="说明" min-width="220" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="importDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>关闭
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="importResult?.resultFileBase64"
|
||||
class="btn-ghost"
|
||||
@click="downloadImportResultFile"
|
||||
>
|
||||
<el-icon><Download /></el-icon>下载结果表
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="importLoading"
|
||||
:disabled="!importFileList.length"
|
||||
@click="submitImport"
|
||||
>
|
||||
<el-icon><Check /></el-icon>开始导入
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情 -->
|
||||
<el-dialog
|
||||
v-model="dialogs.view"
|
||||
@@ -600,8 +790,13 @@
|
||||
<script setup lang="ts">
|
||||
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
|
||||
import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
|
||||
import ColumnHeaderFilter from '@/components/business/column-header-filter/index.vue'
|
||||
import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
|
||||
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
|
||||
import {
|
||||
useClientColumnFilters,
|
||||
type ColumnFilterDef
|
||||
} from '@/composables/useClientColumnFilters'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
@@ -619,8 +814,13 @@ import {
|
||||
Promotion,
|
||||
VideoPlay,
|
||||
Document,
|
||||
Download
|
||||
Download,
|
||||
FolderAdd,
|
||||
UploadFilled,
|
||||
Check,
|
||||
Filter
|
||||
} from '@element-plus/icons-vue'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import { reimburseApi, invoiceWalletApi } from '@/api/bx'
|
||||
import { buildApplyDateRangeQuery, type ApplyDateRange } from '@/utils/bx/apply-date-filter'
|
||||
import { canDeleteByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
|
||||
@@ -680,6 +880,49 @@ const { rows, total, currentPage, pageSize, rangeText, loading, refresh, search,
|
||||
}
|
||||
)
|
||||
|
||||
const columnFilterDefs: ColumnFilterDef[] = [
|
||||
{ prop: 'applyTime', label: '申请时间', type: 'datetime' },
|
||||
{ prop: 'applyCode', label: '报销单号', type: 'text' },
|
||||
{ prop: 'title', label: '报销标题', type: 'text' },
|
||||
{
|
||||
prop: 'expenseName',
|
||||
label: '费用项目',
|
||||
type: 'text',
|
||||
getValue: (row) => {
|
||||
const lines = getReimburseExpenseItemLines(row.details, row)
|
||||
return lines?.length ? lines.join('\n') : row.expenseName || ''
|
||||
}
|
||||
},
|
||||
{ prop: 'amount', label: '报销金额(元)', type: 'number' },
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
type: 'enum',
|
||||
enumOptions: [
|
||||
{ value: 'draft', label: '待提交' },
|
||||
{ value: 'pending', label: '审批中' },
|
||||
{ value: 'approved', label: '审批通过' },
|
||||
{ value: 'rejected', label: '审批驳回' }
|
||||
]
|
||||
},
|
||||
{
|
||||
prop: 'currentNode',
|
||||
label: '当前节点',
|
||||
type: 'text',
|
||||
getValue: (row) => workflowNodeLabel(row.currentNode, row.status) || ''
|
||||
}
|
||||
]
|
||||
|
||||
const {
|
||||
filters,
|
||||
filteredRows,
|
||||
hasActiveFilters,
|
||||
setFilterState,
|
||||
clearAllFilters,
|
||||
isFilterActive,
|
||||
collectUniqueOptions
|
||||
} = useClientColumnFilters(rows, columnFilterDefs)
|
||||
|
||||
const { expenseTree } = useExpenseItemTree()
|
||||
const { applicantOptions, loadApplicantOptions } = useApplicantOptions()
|
||||
const userStore = useUserStore()
|
||||
@@ -742,11 +985,20 @@ const reimburseApplyStatsRowColumns = computed((): StatsRowColumn[] => {
|
||||
return cols
|
||||
})
|
||||
|
||||
function formatApplyDateOnly(value: unknown): string {
|
||||
const text = String(value ?? '').trim()
|
||||
if (!text) return '-'
|
||||
return text.slice(0, 10)
|
||||
}
|
||||
|
||||
function getReimburseApplyStatCellValue(row: any, prop: string): unknown {
|
||||
if (prop === 'expenseName') {
|
||||
const lines = getReimburseExpenseItemLines(row.details, row)
|
||||
return lines?.length ? lines.join('\n') : row.expenseName
|
||||
}
|
||||
if (prop === 'applyTime') {
|
||||
return formatApplyDateOnly(row?.applyTime)
|
||||
}
|
||||
return row?.[prop]
|
||||
}
|
||||
|
||||
@@ -837,6 +1089,12 @@ const walletBalance = ref(0)
|
||||
const editingId = ref<number | null>(null)
|
||||
const titleAutoLinked = ref(true)
|
||||
|
||||
const templateLoading = ref(false)
|
||||
const importDialogVisible = ref(false)
|
||||
const importLoading = ref(false)
|
||||
const importFileList = ref<UploadFile[]>([])
|
||||
const importResult = ref<Record<string, any> | null>(null)
|
||||
|
||||
/** 同一发票共用边框色(与 BxInvoiceSelectDialog / bx-mobile 一致) */
|
||||
const INVOICE_GROUP_COLORS = [
|
||||
'#0891b2',
|
||||
@@ -1113,7 +1371,8 @@ async function openEditDialog(row: any) {
|
||||
titleAutoLinked.value = isAutoReimburseTitle(addForm.title)
|
||||
addForm.remark = data.remark || ''
|
||||
addForm.details = (data.details || []).map((d: any) => ({
|
||||
expenseItemId: d.expenseId,
|
||||
// 费用项 id 可能是纯数字字符串;必须 String,否则 el-tree-select 对不上树节点会直接显示数字
|
||||
expenseItemId: d.expenseId != null && d.expenseId !== '' ? String(d.expenseId) : '',
|
||||
amount: d.amount,
|
||||
remark: d.remark || '',
|
||||
occurDate: d.occurDate || ''
|
||||
@@ -1150,6 +1409,94 @@ async function refreshList() {
|
||||
await refresh()
|
||||
}
|
||||
|
||||
async function downloadImportTemplate() {
|
||||
templateLoading.value = true
|
||||
try {
|
||||
const res: any = await reimburseApi.downloadImportTemplate()
|
||||
const blob =
|
||||
res instanceof Blob
|
||||
? res
|
||||
: new Blob([res as BlobPart], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = '报销申请导入模板.xlsx'
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
ElMessage.error('下载模板失败')
|
||||
} finally {
|
||||
templateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openImportDialog() {
|
||||
importFileList.value = []
|
||||
importResult.value = null
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
function onImportFileChange(_file: UploadFile, files: UploadFile[]) {
|
||||
importFileList.value = files.slice(-1)
|
||||
}
|
||||
|
||||
function onImportFileRemove() {
|
||||
importFileList.value = []
|
||||
}
|
||||
|
||||
async function submitImport() {
|
||||
const raw = importFileList.value[0]?.raw
|
||||
if (!raw) {
|
||||
ElMessage.warning('请选择要导入的文件')
|
||||
return
|
||||
}
|
||||
importLoading.value = true
|
||||
importResult.value = null
|
||||
try {
|
||||
const res: any = await reimburseApi.importExcel(raw)
|
||||
const data = pickBxData(res) || res?.data || res
|
||||
importResult.value = data
|
||||
const ok = Number(data?.successGroups || 0)
|
||||
const fail = Number(data?.failGroups || 0)
|
||||
if (data?.tip) {
|
||||
ElMessage.warning(String(data.tip))
|
||||
} else if (ok > 0 && fail === 0) {
|
||||
ElMessage.success(`成功导入 ${ok} 组`)
|
||||
} else if (ok > 0) {
|
||||
ElMessage.warning(`部分成功:成功 ${ok} 组,失败 ${fail} 组`)
|
||||
} else if (fail > 0) {
|
||||
ElMessage.error('导入失败,请查看结果清单或下载结果表')
|
||||
} else {
|
||||
ElMessage.warning('没有导入任何数据')
|
||||
}
|
||||
if (ok > 0) await refreshList()
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function downloadImportResultFile() {
|
||||
const b64 = importResult.value?.resultFileBase64
|
||||
if (!b64) {
|
||||
ElMessage.warning('暂无结果文件可下载')
|
||||
return
|
||||
}
|
||||
const binary = atob(String(b64))
|
||||
const bytes = new Uint8Array(binary.length)
|
||||
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
|
||||
const blob = new Blob([bytes], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = String(importResult.value?.resultFileName || '报销申请导入结果.xlsx')
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshList()
|
||||
})
|
||||
@@ -1484,6 +1831,24 @@ function viewDetailSummary({ columns, data }: { columns: any[]; data: any[] }) {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.import-hint {
|
||||
margin: 0 0 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.import-tip {
|
||||
margin: 0 0 8px;
|
||||
color: #d97706;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.import-result {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.invoice-balance-tip {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<PlPlanTypeTabs v-model="activePlanTypeTab" @change="handlePlanTypeTabChange" />
|
||||
<PlPlanTypeTabs
|
||||
:model-value="activePlanTypeTab"
|
||||
:tabs="PL_PLAN_LIST_TABS"
|
||||
@change="handlePlanTypeTabChange"
|
||||
/>
|
||||
|
||||
<!-- 筛选区 -->
|
||||
<div class="filter-block">
|
||||
@@ -17,7 +21,12 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="跟进状态">
|
||||
<el-select v-model="searchForm.followStatus" placeholder="全部" clearable>
|
||||
<el-select
|
||||
v-model="searchForm.followStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
:disabled="activePlanTypeTab === 'pending_arrange'"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in PL_PLAN_FOLLOW_STATUS_OPTIONS"
|
||||
:key="opt.value"
|
||||
@@ -226,9 +235,7 @@
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getPlanTypeTagType(row.planType)" size="small">
|
||||
{{ getPlanTypeLabel(row.planType) }}
|
||||
</el-tag>
|
||||
{{ getPlanTypeLabel(row.planType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -557,8 +564,9 @@ import {
|
||||
getPlPlanFollowStatusTagType,
|
||||
normalizePlPersonnelSummaryDisplay,
|
||||
PL_PLAN_FOLLOW_STATUS_OPTIONS,
|
||||
plPlanTypeTabToFilter,
|
||||
type PlPlanTypeTab,
|
||||
PL_PLAN_LIST_TABS,
|
||||
plPlanListTabToFilters,
|
||||
type PlPlanListTab,
|
||||
type PlPrintSlipCommand
|
||||
} from '@/utils/pl-display'
|
||||
import { syncViewRowFromFlatList } from '@/utils/view-dialog-refresh'
|
||||
@@ -742,7 +750,7 @@ async function handlePlanMutationSuccess(payload?: { planId?: string; openArrang
|
||||
const selectedPlan = computed(() => (selectedRows.value.length === 1 ? selectedRows.value[0] : null))
|
||||
const dateRange = ref<string[]>([])
|
||||
const quickSearch = ref('')
|
||||
const activePlanTypeTab = ref<PlPlanTypeTab>('all')
|
||||
const activePlanTypeTab = ref<PlPlanListTab>('all')
|
||||
|
||||
const searchForm = reactive({
|
||||
planNo: '',
|
||||
@@ -759,11 +767,6 @@ const pagination = reactive({
|
||||
total: 0
|
||||
})
|
||||
|
||||
function getPlanTypeTagType(type: string) {
|
||||
const map: Record<string, string> = { direct: 'primary', inbound: 'success', outbound: 'warning' }
|
||||
return map[type] || 'info'
|
||||
}
|
||||
|
||||
function getPlanTypeLabel(type?: string | null) {
|
||||
if (!type) return '—'
|
||||
const map: Record<string, string> = { direct: '直销单', inbound: '入库单', outbound: '出库单' }
|
||||
@@ -788,11 +791,12 @@ async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const keyword = quickSearch.value.trim()
|
||||
const tabFilters = plPlanListTabToFilters(activePlanTypeTab.value)
|
||||
const res = await planApi.list({
|
||||
q: keyword || undefined,
|
||||
planNo: keyword ? undefined : searchForm.planNo || undefined,
|
||||
planType: searchForm.planType || undefined,
|
||||
followStatus: searchForm.followStatus || undefined,
|
||||
planType: tabFilters.planType || undefined,
|
||||
followStatus: tabFilters.followStatus || searchForm.followStatus || undefined,
|
||||
customerName: keyword ? undefined : searchForm.customerName || undefined,
|
||||
productName: keyword ? undefined : searchForm.productName || undefined,
|
||||
licensePlate: keyword ? undefined : searchForm.licensePlate || undefined,
|
||||
@@ -814,8 +818,17 @@ function handleSearch() {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
function handlePlanTypeTabChange(tab: PlPlanTypeTab) {
|
||||
searchForm.planType = plPlanTypeTabToFilter(tab)
|
||||
function handlePlanTypeTabChange(tab: PlPlanListTab | string) {
|
||||
const nextTab = tab as PlPlanListTab
|
||||
const prevTab = activePlanTypeTab.value
|
||||
activePlanTypeTab.value = nextTab
|
||||
const filters = plPlanListTabToFilters(nextTab)
|
||||
searchForm.planType = filters.planType
|
||||
if (nextTab === 'pending_arrange') {
|
||||
searchForm.followStatus = 'pending_arrange'
|
||||
} else if (prevTab === 'pending_arrange') {
|
||||
searchForm.followStatus = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user