feat(pl): 购买证运输证改为客户品类额度并支持齐备完善。
按客户与证用品类自动选证,未配置生效时间拦住安排;齐备仅放行安排,归档前须完善挂证,并补充迁移与测试造数脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
aad74505c0
commit
cd94ebc56d
31 files changed
+2835
-39
No files matched your search
@@ -111,7 +111,7 @@
|
||||
|
||||
const panelTitleClassList = [...panelTitleIconByText.map(([, cls]) => cls), 'auto-title-list']
|
||||
const panelTitleBarClass = 'title-bar-heading'
|
||||
const panelTitleBarTextRules = [/^(购买证与运输证|合法使用证明|计划用证|证件建档|合同用证|证件用量|计划段次用证|证书齐备|合同证件与额度)$/]
|
||||
const panelTitleBarTextRules = [/^(购买证与运输证|合法使用证明|计划用证|证件建档|证用品类|合同用证|证件用量|计划段次用证|证书齐备|合同证件与额度|运输证件规则)$/]
|
||||
const panelTitleHeadingSelector =
|
||||
'.page-container .panel-top h2, .page-container .panel-top h3, .page-container .storage-content-header h3, .page-container .storage-aside__head h2, .page-container .user-dept-aside__head h2, .console-container .panel-top h2'
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface CertBody {
|
||||
routeText?: string | null
|
||||
consigneeName?: string | null
|
||||
applicantName?: string | null
|
||||
customerId?: string | null
|
||||
categoryId?: string | null
|
||||
contractBindings?: CertificateContractBinding[]
|
||||
purchaseRefs?: PurchaseReference[]
|
||||
}
|
||||
@@ -61,6 +63,7 @@ export interface QuotaCert extends Omit<CertBody, 'status'> {
|
||||
usagePolicy: 'once' | 'multiple' | 'needs_confirmation'
|
||||
createTime?: string
|
||||
certType?: CertType
|
||||
derivedCategoryIds?: string[]
|
||||
}
|
||||
export interface ScopeProduct {
|
||||
id?: string
|
||||
@@ -393,7 +396,48 @@ export const certReadyApi = {
|
||||
.get(`${base}/plans/cert-ready`, { params })
|
||||
.then(pickBxData<{ total: number; list: CertReadyPlan[] }>),
|
||||
mark: (id: string) =>
|
||||
request.post(`${base}/plans/${id}/cert-ready`).then(pickBxData<CertReadyPlan>)
|
||||
request.post(`${base}/plans/${id}/cert-ready`).then(pickBxData<CertReadyPlan>),
|
||||
listPendingFulfill: (params: { keyword?: string; page: number; pageSize: number }) =>
|
||||
request
|
||||
.get(`${base}/plans/cert-ready/pending-fulfill`, { params })
|
||||
.then(pickBxData<{ total: number; list: CertReadyPlan[] }>),
|
||||
fulfill: (id: string) =>
|
||||
request.post(`${base}/plans/${id}/fulfill-quota-certs`).then(pickBxData<CertReadyPlan>),
|
||||
fulfillBatch: (planIds: string[]) =>
|
||||
request
|
||||
.post(`${base}/plans/fulfill-quota-certs/batch`, { planIds })
|
||||
.then(pickBxData<{ results: Array<{ planId: string; ok: boolean; message?: string }> }>)
|
||||
}
|
||||
|
||||
export const certCategoryApi = {
|
||||
list: (params?: { keyword?: string; status?: string }) =>
|
||||
request.get(`${base}/categories`, { params }).then(pickBxData<CertCategory[]>),
|
||||
get: (id: string) => request.get(`${base}/categories/${id}`).then(pickBxData<CertCategory>),
|
||||
create: (body: Record<string, unknown>) =>
|
||||
request.post(`${base}/categories`, body).then(pickBxData<CertCategory>),
|
||||
update: (id: string, body: Record<string, unknown>) =>
|
||||
request.put(`${base}/categories/${id}`, body).then(pickBxData<CertCategory>),
|
||||
remove: (id: string) =>
|
||||
request.delete(`${base}/categories/${id}`).then(pickBxData<{ ok?: boolean }>)
|
||||
}
|
||||
|
||||
export interface CertCategory {
|
||||
id: string
|
||||
categoryName: string
|
||||
categoryCode?: string | null
|
||||
status: string
|
||||
products?: Array<{ id?: string; productId: string; scopeStatus?: string }>
|
||||
}
|
||||
|
||||
export const certQuotaRuleApi = {
|
||||
get: () =>
|
||||
request
|
||||
.get(`${base}/rule-config`)
|
||||
.then(pickBxData<{ effectiveAt: string | null; configured: boolean }>),
|
||||
save: (effectiveAt: string | null) =>
|
||||
request
|
||||
.put(`${base}/rule-config`, { effectiveAt })
|
||||
.then(pickBxData<{ effectiveAt: string | null; configured: boolean }>)
|
||||
}
|
||||
|
||||
export const planQuotaApi = {
|
||||
|
||||
@@ -71,6 +71,23 @@
|
||||
<p v-else-if="!loading" class="pl-plan-form-empty-hint">暂无车辆信息</p>
|
||||
</section>
|
||||
|
||||
<section class="pl-plan-form-section pl-plan-form-section--plain">
|
||||
<h3 class="pl-plan-form-section-title">关联购买证 / 运输证</h3>
|
||||
<p v-if="plan?.quotaCertPendingFulfill" class="pl-plan-form-empty-hint">
|
||||
证书齐备(待完善):请到「证书齐备 → 待完善并关闭」挂证
|
||||
</p>
|
||||
<div v-else-if="linkedCertRows.length" class="pl-plan-linked-certs">
|
||||
<div v-for="item in linkedCertRows" :key="item.key" class="pl-plan-linked-cert-row">
|
||||
<el-tag size="small" :type="item.kind === 'purchase' ? 'success' : 'warning'">
|
||||
{{ item.kind === 'purchase' ? '购买证' : '运输证' }}
|
||||
</el-tag>
|
||||
<span>{{ item.certNo || item.certId }}</span>
|
||||
<span class="text-slate-500">预占 {{ item.reservedQty }} / 已用 {{ item.usedQty }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="pl-plan-form-empty-hint">暂无关联证件</p>
|
||||
</section>
|
||||
|
||||
<section class="pl-plan-form-section pl-plan-form-section--plain">
|
||||
<h3 class="pl-plan-form-section-title">装送货信息</h3>
|
||||
<PlPlanStopsSplitShell
|
||||
@@ -1633,11 +1650,41 @@
|
||||
})
|
||||
)
|
||||
|
||||
const linkedCertRows = ref<
|
||||
Array<{
|
||||
key: string
|
||||
kind: string
|
||||
certId: string
|
||||
certNo?: string
|
||||
reservedQty: string | number
|
||||
usedQty: string | number
|
||||
}>
|
||||
>([])
|
||||
|
||||
async function loadLinkedCerts(planId: string) {
|
||||
linkedCertRows.value = []
|
||||
try {
|
||||
const { planQuotaApi } = await import('@/api/pl/quota-cert')
|
||||
const data = await planQuotaApi.get(planId)
|
||||
linkedCertRows.value = (data?.usages || []).map((u: any, index: number) => ({
|
||||
key: `${u.certType || u.cert_type}-${u.certId || u.cert_id}-${index}`,
|
||||
kind: String(u.certType || u.cert_type || ''),
|
||||
certId: String(u.certId || u.cert_id || ''),
|
||||
certNo: u.certNo || u.cert_no,
|
||||
reservedQty: u.reservedQty ?? u.reserved_qty ?? '—',
|
||||
usedQty: u.usedQty ?? u.used_qty ?? '—'
|
||||
}))
|
||||
} catch {
|
||||
linkedCertRows.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function runLoadDetail(planId: string) {
|
||||
await loadDetail(planId, {
|
||||
initialPlan: props.initialPlan?.id === planId ? props.initialPlan : null,
|
||||
prefetchedArrange: props.prefetchedArrange
|
||||
})
|
||||
await loadLinkedCerts(planId)
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -68,6 +68,13 @@ export const plRoutes: AppRouteRecord[] = [
|
||||
component: '/pl/quota-cert/index',
|
||||
meta: { title: '证件建档', keepAlive: false, menuCode: 'pl:quota-cert:list' }
|
||||
},
|
||||
{
|
||||
id: 'PL_CERT_CATEGORY',
|
||||
name: 'PlCertCategory',
|
||||
path: 'cert-category',
|
||||
component: '/pl/cert-category/index',
|
||||
meta: { title: '证用品类', keepAlive: false, menuCode: 'pl:quota-cert:list' }
|
||||
},
|
||||
{
|
||||
id: 'PL_CONTRACT_QUOTA',
|
||||
name: 'PlContractQuota',
|
||||
|
||||
@@ -727,6 +727,17 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [
|
||||
menuCode: 'system:dict:list'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'SYSM_CERT_QUOTA_RULE',
|
||||
name: 'CertQuotaRule',
|
||||
path: 'cert-quota-rule',
|
||||
component: '/system/cert-quota-rule/index',
|
||||
meta: {
|
||||
title: '运输证件规则',
|
||||
keepAlive: false,
|
||||
menuCode: 'system:tenant:list'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '106',
|
||||
name: 'Menu',
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<!-- 证用品类 — P01 标准列表;与 CRM 分类独立 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">证用品类</h2>
|
||||
</div>
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<ListQuickSearchInput
|
||||
v-model="keyword"
|
||||
placeholder="品类名称"
|
||||
@search="search"
|
||||
@clear="search"
|
||||
/>
|
||||
<el-button type="primary" @click="openEdit()">
|
||||
<el-icon><Plus /></el-icon>新增品类
|
||||
</el-button>
|
||||
<el-button :loading="loading" @click="load">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap">
|
||||
<ListTable
|
||||
v-loading="loading"
|
||||
table-key="pl-cert-category"
|
||||
class="list-table"
|
||||
:data="rows"
|
||||
stripe
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column prop="categoryName" label="品类名称" min-width="160" />
|
||||
<el-table-column prop="categoryCode" label="编码" min-width="120">
|
||||
<template #default="{ row }">{{ row.categoryCode || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="row.status === '1' ? 'success' : 'info'">
|
||||
{{ row.status === '1' ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button link type="danger" @click="remove(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ListTable>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
class="dialog-form-layout"
|
||||
:title="editingId ? '编辑证用品类' : '新增证用品类'"
|
||||
width="640px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="品类名称" required>
|
||||
<el-input v-model="form.categoryName" maxlength="128" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码">
|
||||
<el-input v-model="form.categoryCode" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="form.status" class="w-full">
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="停用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="关联系统产品(产品 ID,逗号分隔,保存后按差量写入)">
|
||||
<el-input
|
||||
v-model="productIdsText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="例如:uuid1,uuid2(上线后可改产品选择器)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { certCategoryApi, type CertCategory } from '@/api/pl/quota-cert'
|
||||
import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
|
||||
import { Check, Close, Plus, Refresh } from '@element-plus/icons-vue'
|
||||
|
||||
const keyword = ref('')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const rows = ref<CertCategory[]>([])
|
||||
const visible = ref(false)
|
||||
const editingId = ref('')
|
||||
const form = reactive({ categoryName: '', categoryCode: '', status: '1' })
|
||||
const productIdsText = ref('')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
rows.value = (await certCategoryApi.list({ keyword: keyword.value || undefined })) || []
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function search() {
|
||||
void load()
|
||||
}
|
||||
|
||||
async function openEdit(row?: CertCategory) {
|
||||
editingId.value = row?.id || ''
|
||||
form.categoryName = row?.categoryName || ''
|
||||
form.categoryCode = row?.categoryCode || ''
|
||||
form.status = row?.status || '1'
|
||||
productIdsText.value = ''
|
||||
if (row?.id) {
|
||||
const detail = await certCategoryApi.get(row.id)
|
||||
productIdsText.value = (detail.products || [])
|
||||
.filter((p) => p.scopeStatus !== 'inactive')
|
||||
.map((p) => p.productId)
|
||||
.join(',')
|
||||
}
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!form.categoryName.trim()) {
|
||||
ElMessage.warning('请填写品类名称')
|
||||
return
|
||||
}
|
||||
const productIds = productIdsText.value
|
||||
.split(/[,,\s]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
const body = {
|
||||
categoryName: form.categoryName.trim(),
|
||||
categoryCode: form.categoryCode || null,
|
||||
status: form.status,
|
||||
products: productIds.map((productId) => ({ productId, scopeStatus: 'active' }))
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
if (editingId.value) await certCategoryApi.update(editingId.value, body)
|
||||
else await certCategoryApi.create(body)
|
||||
ElMessage.success('已保存')
|
||||
visible.value = false
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(row: CertCategory) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除品类「${row.categoryName}」?`, '删除', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await certCategoryApi.remove(row.id)
|
||||
ElMessage.success('已删除')
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
@@ -4,12 +4,25 @@
|
||||
<div class="panel-top"><h2 class="title-bar-heading">证书齐备</h2></div>
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<el-radio-group v-model="listMode" class="mr-3" @change="onModeChange">
|
||||
<el-radio-button value="mark">标记齐备</el-radio-button>
|
||||
<el-radio-button value="fulfill">待完善并关闭</el-radio-button>
|
||||
</el-radio-group>
|
||||
<ListQuickSearchInput
|
||||
v-model="keyword"
|
||||
placeholder="计划编号"
|
||||
@search="search"
|
||||
@clear="search"
|
||||
/>
|
||||
<el-button
|
||||
v-if="listMode === 'fulfill'"
|
||||
type="primary"
|
||||
:disabled="!selectedIds.length"
|
||||
:loading="batching"
|
||||
@click="fulfillBatch"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>批量完善并关闭
|
||||
</el-button>
|
||||
<el-button v-if="keyword || hasActiveFilters" @click="reset">
|
||||
<el-icon><RefreshLeft /></el-icon>清除筛选
|
||||
</el-button>
|
||||
@@ -29,6 +42,7 @@
|
||||
row-key="id"
|
||||
stripe
|
||||
class="list-table"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
@@ -73,17 +87,34 @@
|
||||
<span v-else>{{ row[column.prop] || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="listMode === 'fulfill'"
|
||||
type="selection"
|
||||
width="48"
|
||||
:reserve-selection="false"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="260" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="!row.quotaCertWaived"
|
||||
type="primary"
|
||||
:loading="markingId === row.id"
|
||||
@click="mark(row)"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>证书齐备
|
||||
</el-button>
|
||||
<span v-else>已确认</span>
|
||||
<template v-if="listMode === 'fulfill'">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="markingId === row.id"
|
||||
@click="fulfillOne(row)"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>完善并关闭
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
v-if="!row.quotaCertWaived"
|
||||
type="primary"
|
||||
:loading="markingId === row.id"
|
||||
@click="mark(row)"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>证书齐备
|
||||
</el-button>
|
||||
<span v-else>已确认</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ListTable>
|
||||
@@ -124,6 +155,9 @@
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const markingId = ref('')
|
||||
const batching = ref(false)
|
||||
const listMode = ref<'mark' | 'fulfill'>('mark')
|
||||
const selectedIds = ref<string[]>([])
|
||||
const rows = ref<CertReadyPlan[]>([])
|
||||
let epoch = 0
|
||||
const defaultPlanDates = certReadyDefaultPlanDates()
|
||||
@@ -203,13 +237,21 @@
|
||||
async function load() {
|
||||
const current = ++epoch
|
||||
loading.value = true
|
||||
selectedIds.value = []
|
||||
try {
|
||||
const value = await certReadyApi.list({
|
||||
keyword: keyword.value || undefined,
|
||||
page: page.value,
|
||||
pageSize: 20,
|
||||
...toQueryParams()
|
||||
})
|
||||
const value =
|
||||
listMode.value === 'fulfill'
|
||||
? await certReadyApi.listPendingFulfill({
|
||||
keyword: keyword.value || undefined,
|
||||
page: page.value,
|
||||
pageSize: 20
|
||||
})
|
||||
: await certReadyApi.list({
|
||||
keyword: keyword.value || undefined,
|
||||
page: page.value,
|
||||
pageSize: 20,
|
||||
...toQueryParams()
|
||||
})
|
||||
if (current !== epoch) return
|
||||
rows.value = value?.list || []
|
||||
total.value = value?.total || 0
|
||||
@@ -225,6 +267,11 @@
|
||||
void load()
|
||||
}
|
||||
|
||||
function onModeChange() {
|
||||
page.value = 1
|
||||
void load()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
keyword.value = ''
|
||||
if (hasActiveFilters.value) clearAllFilters()
|
||||
@@ -234,7 +281,7 @@
|
||||
async function mark(row: CertReadyPlan) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确认后,这条运输计划在安排和归档时都不再校验购买证、运输证,也不扣减额度。确认人和时间会记在本行。',
|
||||
'确认后可先安排运输;新规则下归档前仍须在「待完善并关闭」中挂上购买证/运输证。确认人和时间会记在本行。',
|
||||
'证书齐备',
|
||||
{ type: 'warning', confirmButtonText: '确认齐备', cancelButtonText: '取消' }
|
||||
)
|
||||
@@ -253,6 +300,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fulfillOne(row: CertReadyPlan) {
|
||||
markingId.value = row.id
|
||||
try {
|
||||
await certReadyApi.fulfill(row.id)
|
||||
ElMessage.success('已完善并关闭')
|
||||
await load()
|
||||
} catch (error) {
|
||||
ElMessage.error(certReadyErrorText(error, '完善并关闭失败'))
|
||||
} finally {
|
||||
markingId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectionChange(selection: CertReadyPlan[]) {
|
||||
selectedIds.value = selection.map((row) => row.id)
|
||||
}
|
||||
|
||||
async function fulfillBatch() {
|
||||
if (!selectedIds.value.length) return
|
||||
batching.value = true
|
||||
try {
|
||||
const res = await certReadyApi.fulfillBatch(selectedIds.value)
|
||||
const failed = (res?.results || []).filter((r) => !r.ok)
|
||||
if (failed.length) {
|
||||
ElMessage.warning(`部分失败:${failed.map((f) => f.message || f.planId).join(';')}`)
|
||||
} else {
|
||||
ElMessage.success('批量完善完成')
|
||||
}
|
||||
await load()
|
||||
} catch (error) {
|
||||
ElMessage.error(certReadyErrorText(error, '批量完善失败'))
|
||||
} finally {
|
||||
batching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const tableWrapRef = ref<HTMLElement | null>(null)
|
||||
const pagerRef = ref<HTMLElement | null>(null)
|
||||
const tableRef = ref()
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<!-- 运输证件规则切点 — 系统信息 Tab;P01 简表单 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">运输证件规则</h2>
|
||||
</div>
|
||||
<div class="p-6 max-w-xl">
|
||||
<p class="text-sm text-slate-500 mb-4">
|
||||
配置「客户+品类」额度生效时间。未配置时无法安排运输。计划创建时按该时间打规则版本;修改后只影响之后新建的计划。
|
||||
</p>
|
||||
<el-form label-position="top" class="dialog-form-layout">
|
||||
<el-form-item label="客户品类额度生效时间">
|
||||
<el-date-picker
|
||||
v-model="effectiveAt"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择生效时间"
|
||||
class="w-full"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="save">
|
||||
<el-icon><Check /></el-icon>保存
|
||||
</el-button>
|
||||
<el-button :loading="loading" @click="load">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<p v-if="!configured" class="text-amber-600 text-sm">当前未配置:运输计划安排将被拦住。</p>
|
||||
<p v-else class="text-emerald-700 text-sm">已配置生效时间:{{ effectiveAt }}</p>
|
||||
</el-form>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { certQuotaRuleApi } from '@/api/pl/quota-cert'
|
||||
import { Check, Refresh } from '@element-plus/icons-vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const effectiveAt = ref<string | null>(null)
|
||||
const configured = ref(false)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await certQuotaRuleApi.get()
|
||||
effectiveAt.value = data?.effectiveAt || null
|
||||
configured.value = Boolean(data?.configured)
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
const data = await certQuotaRuleApi.save(effectiveAt.value)
|
||||
effectiveAt.value = data?.effectiveAt || null
|
||||
configured.value = Boolean(data?.configured)
|
||||
ElMessage.success(configured.value ? '已保存生效时间' : '已清空生效时间(安排将被拦住)')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void load()
|
||||
})
|
||||
</script>
|
||||
Reference in new issue
Block a user