feat(goauto): add managed purchase rules (#127)

This commit is contained in:
QiuSW
2026-08-29 21:20:55 +08:00
parent 7afe5ae9ce
commit 148ba96521
19 changed files with 833 additions and 24 deletions
+7
View File
@@ -0,0 +1,7 @@
import request from '@/utils/request'
export function listPurchaseRules(params) { return request({ url: '/api/admin/v1/purchase-rules', method: 'get', params }) }
export function createPurchaseRule(data) { return request({ url: '/api/admin/v1/purchase-rules', method: 'post', data }) }
export function updatePurchaseRule(ruleId, data) { return request({ url: `/api/admin/v1/purchase-rules/${ruleId}`, method: 'patch', data }) }
export function deletePurchaseRule(ruleId, data) { return request({ url: `/api/admin/v1/purchase-rules/${ruleId}`, method: 'delete', data }) }
export function setCurrentPurchaseRule(data) { return request({ url: '/api/admin/v1/purchase-rules/current', method: 'put', data }) }
@@ -0,0 +1,66 @@
<template>
<BasicLayout><template #wrapper>
<el-card class="page-card" shadow="never">
<div class="page-heading">
<div><h1>采购规则</h1><p>维护 PDD 采购执行规则。只有标记为“当前”的规则用于新任务,已有任务继续使用自己的规则快照。</p></div>
<el-button type="primary" :icon="Plus" @click="openCreate">新建采购规则</el-button>
</div>
<el-alert type="warning" :closable="false" show-icon title="规则缺失或校验失败时,新建和重试采购任务会被明确阻断,不会回退到内置规则。" />
<el-form :model="query" inline class="search-form" @submit.prevent="load">
<el-form-item label="规则名称"><el-input v-model="query.name" clearable placeholder="输入规则名称" @keyup.enter="search" /></el-form-item>
<el-form-item><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="RefreshLeft" @click="reset">重置</el-button></el-form-item>
</el-form>
<el-table v-loading="loading" :data="items" border stripe empty-text="暂无采购规则">
<el-table-column label="ID" prop="id" width="80" />
<el-table-column label="规则名称" prop="name" min-width="200" />
<el-table-column label="状态" width="110"><template #default="{ row }"><el-tag v-if="row.isCurrent" type="success">当前规则</el-tag><span v-else>历史版本</span></template></el-table-column>
<el-table-column label="规则摘要" min-width="360"><template #default="{ row }">{{ summary(row.content) }}</template></el-table-column>
<el-table-column label="更新时间" prop="updatedAt" width="190" />
<el-table-column label="操作" width="260" fixed="right"><template #default="{ row }">
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="success" :disabled="row.isCurrent || switching === row.id" :loading="switching === row.id" @click="setCurrent(row)">设为当前</el-button>
<el-button link type="danger" :disabled="row.isCurrent" @click="remove(row)">删除</el-button>
</template></el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="query.page" v-model:limit="query.pageSize" :total="total" @pagination="load" />
</el-card>
<el-dialog v-model="dialog.open" :title="dialog.ruleId ? '编辑采购规则' : '新建采购规则'" width="760px" destroy-on-close>
<el-form ref="form" :model="form" :rules="rules" label-position="top">
<el-form-item label="规则名称" prop="name"><el-input v-model="form.name" maxlength="120" show-word-limit /></el-form-item>
<el-form-item label="规则 JSON" prop="contentText"><el-input v-model="form.contentText" type="textarea" :rows="18" class="json-editor" spellcheck="false" /><div class="field-help">仅支持 schemaVersion=1、ruleType=pddPurchase;服务端会再次执行严格字段和安全动作校验。</div></el-form-item>
</el-form>
<template #footer><el-button :disabled="dialog.saving" @click="dialog.open=false">取消</el-button><el-button type="primary" :loading="dialog.saving" @click="save">保存</el-button></template>
</el-dialog>
</template></BasicLayout>
</template>
<script>
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, RefreshLeft, Search } from '@element-plus/icons-vue'
import { createPurchaseRule, deletePurchaseRule, listPurchaseRules, setCurrentPurchaseRule, updatePurchaseRule } from '@/api/goauto/purchase-rules'
const defaultRule = { schemaVersion: 1, ruleType: 'pddPurchase', requiredCapabilities: ['purchase.live.v1'], actions: [{ type: 'openProduct' }, { type: 'verifyProduct' }, { type: 'openSpecPanel' }, { type: 'selectSpec' }, { type: 'setQuantity' }, { type: 'verifyUnitPrice' }, { type: 'verifyOrderSummary' }] }
export default {
name: 'GoAutoPurchaseRules',
setup() { return { Plus, RefreshLeft, Search } },
data() {
const jsonRule = (_r, value, callback) => { try { const parsed = JSON.parse(value); if (!parsed || Array.isArray(parsed) || parsed.schemaVersion !== 1 || parsed.ruleType !== 'pddPurchase') return callback(new Error('请输入有效的 PDD 采购规则 JSON')); callback() } catch (_) { callback(new Error('请输入有效 JSON')) } }
return { loading: false, switching: null, items: [], total: 0, query: { page: 1, pageSize: 20, name: '' }, dialog: { open: false, saving: false, ruleId: null }, form: { name: '', contentText: '' }, rules: { name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }], contentText: [{ required: true, validator: jsonRule, trigger: 'blur' }] }}
},
created() { this.load() },
methods: {
async load() { this.loading = true; try { const response = await listPurchaseRules(this.query); this.items = response.data.items; this.total = response.data.total } finally { this.loading = false } },
search() { this.query.page = 1; this.load() }, reset() { this.query = { page: 1, pageSize: 20, name: '' }; this.load() },
summary(content) { return `${content.requiredCapabilities?.length || 0} 项能力 · ${content.actions?.length || 0} 个动作` },
openCreate() { this.dialog = { open: true, saving: false, ruleId: null }; this.form = { name: '', contentText: JSON.stringify(defaultRule, null, 2) } },
openEdit(row) { this.dialog = { open: true, saving: false, ruleId: row.id }; this.form = { name: row.name, contentText: JSON.stringify(row.content, null, 2) } },
async save() { const valid = await this.$refs.form.validate().catch(() => false); if (!valid) return; this.dialog.saving = true; const payload = { requestId: crypto.randomUUID(), name: this.form.name.trim(), content: JSON.parse(this.form.contentText) }; try { if (this.dialog.ruleId) await updatePurchaseRule(this.dialog.ruleId, payload); else await createPurchaseRule(payload); ElMessage.success('采购规则已保存'); this.dialog.open = false; await this.load() } finally { this.dialog.saving = false } },
async setCurrent(row) { await ElMessageBox.confirm(`设为当前后,新建及安全重试的采购任务将使用“${row.name}”。`, '切换当前采购规则', { type: 'warning', confirmButtonText: '确认切换', cancelButtonText: '取消' }); this.switching = row.id; try { await setCurrentPurchaseRule({ requestId: crypto.randomUUID(), ruleId: row.id }); ElMessage.success('当前采购规则已切换'); await this.load() } finally { this.switching = null } },
async remove(row) { await ElMessageBox.confirm(`确定删除“${row.name}”吗?已有任务快照不受影响。`, '删除采购规则', { type: 'warning', confirmButtonText: '确认删除', cancelButtonText: '取消' }); await deletePurchaseRule(row.id, { requestId: crypto.randomUUID() }); ElMessage.success('采购规则已删除'); await this.load() }
}
}
</script>
<style lang="scss" scoped>
.page-card{min-height:calc(100vh - 124px)}.page-heading{display:flex;justify-content:space-between;gap:16px;margin-bottom:18px}.page-heading h1{margin:0 0 6px;font-size:24px;color:#1f2937}.page-heading p,.field-help{margin:0;color:#64748b;line-height:1.5}.search-form{margin:18px 0 16px;padding:16px 16px 0;border:1px solid #e5e7eb;border-radius:8px;background:#f8fafc}:deep(.json-editor textarea){font-family:"Cascadia Code",Consolas,monospace;line-height:1.55}.field-help{margin-top:6px;font-size:13px}@media(max-width:768px){.page-heading{flex-direction:column}}
</style>