admin_new P0+P1 全批次合并 (84 commits) #2

Merged
direv merged 85 commits from develop into main 2026-08-07 11:27:17 +08:00
2 changed files with 262 additions and 0 deletions
Showing only changes of commit 4d1e125840 - Show all commits

View File

@ -0,0 +1,256 @@
<script setup lang="ts">
/**
* 价格公式系统变量管理 (运营后台)
*
* 走 /supplier/supplierGoodPriceFormulaVariable/ API
* 功能: 变量 CRUD + 快速改值 (改后 SKU 公式价格自动变化)
* 字段: varKey/varName/varUnit/currentValue/sourceType/sourceCode/formula/sort/status
*/
import { onMounted, ref, reactive } from 'vue'
import { httpGet, httpPost } from '@/utils/request'
import { ElMessage, ElMessageBox } from 'element-plus'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
interface Variable {
id: string
varKey: string
varName: string
varUnit: string
currentValue: number
sourceType: number
sourceCode: string
formula: string
sort: number
status: number
createDate: string
updateDate: string
}
const list = ref<Variable[]>([])
const loading = ref(false)
const total = ref(0)
const page = ref(1)
const limit = ref(20)
const keyword = ref('')
// 新增/编辑弹窗
const dialogVisible = ref(false)
const saving = ref(false)
const form = reactive<any>({
id: null, varKey: '', varName: '', varUnit: '', currentValue: 0,
sourceType: 1, sourceCode: '', formula: '', sort: 0, status: 1
})
// 快速改值
const quickEditId = ref<string | null>(null)
const quickValue = ref('')
const SOURCE_MAP: Record<number, string> = { 1: '手工维护', 2: 'AKShare采集' }
async function load() {
loading.value = true
try {
const data: any = await httpGet('/supplier/supplierGoodPriceFormulaVariable/page', {
page: page.value, limit: limit.value, keyword: keyword.value || undefined
})
list.value = (data?.list || []).map((it: any) => ({
id: String(it.id), varKey: it.var_key || '--', varName: it.var_name || '--',
varUnit: it.var_unit || '', currentValue: Number(it.current_value || 0),
sourceType: Number(it.source_type || 1), sourceCode: it.source_code || '',
formula: it.formula || '', sort: Number(it.sort || 0), status: Number(it.status || 1),
createDate: it.create_date || '--', updateDate: it.update_date || '--'
}))
total.value = Number(data?.total || 0)
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '加载失败')
list.value = []
} finally {
loading.value = false
}
}
function onSearch() { page.value = 1; load() }
function onReset() { keyword.value = ''; page.value = 1; load() }
function onPageChange(p: number) { page.value = p; load() }
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
function openAdd() {
Object.assign(form, { id: null, varKey: '', varName: '', varUnit: '', currentValue: 0, sourceType: 1, sourceCode: '', formula: '', sort: 0, status: 1 })
dialogVisible.value = true
}
function openEdit(row: Variable) {
Object.assign(form, {
id: row.id, varKey: row.varKey, varName: row.varName, varUnit: row.varUnit,
currentValue: row.currentValue, sourceType: row.sourceType, sourceCode: row.sourceCode,
formula: row.formula, sort: row.sort, status: row.status
})
dialogVisible.value = true
}
async function save() {
if (!form.varKey.trim() || !form.varName.trim()) {
ElMessage.warning('变量标识和变量名必填'); return
}
saving.value = true
try {
await httpPost('/supplier/supplierGoodPriceFormulaVariable/save', { ...form })
ElMessage.success('保存成功')
dialogVisible.value = false
load()
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '保存失败')
} finally { saving.value = false }
}
async function quickUpdateValue(row: Variable) {
if (quickValue.value === '') return
try {
await httpPost('/supplier/supplierGoodPriceFormulaVariable/updateValue', { id: row.id, currentValue: quickValue.value })
ElMessage.success(`「${row.varName}」值已更新为 ${quickValue.value}, 相关商品价格将自动变化`)
quickEditId.value = null
quickValue.value = ''
load()
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '更新失败')
}
}
async function remove(row: Variable) {
try {
await ElMessageBox.confirm(`确认删除变量「${row.varName}」? 引用该变量的商品公式将无法计算`, '删除确认', { type: 'warning' })
await httpGet('/supplier/supplierGoodPriceFormulaVariable/delete', { id: row.id })
ElMessage.success('删除成功')
load()
} catch (e: any) {
if (e === 'cancel' || e === 'close') return
ElMessage.error(e?.msg || e?.message || '删除失败')
}
}
function toggleStatus(row: Variable) {
const next = row.status === 1 ? 0 : 1
httpPost('/supplier/supplierGoodPriceFormulaVariable/save', {
id: row.id, varKey: row.varKey, varName: row.varName, varUnit: row.varUnit,
currentValue: row.currentValue, sourceType: row.sourceType, sourceCode: row.sourceCode,
formula: row.formula, sort: row.sort, status: next
}).then(() => {
ElMessage.success(next === 1 ? '已启用' : '已禁用')
load()
}).catch((e: any) => ElMessage.error(e?.msg || e?.message || '操作失败'))
}
onMounted(load)
</script>
<template>
<div class="rr-page">
<PageHeader title="价格变量管理" :sub="`系统级价格变量 (共 ${total} 个), 修改变量值后所有引用商品的售价自动更新`">
<el-button type="primary" @click="openAdd">新增变量</el-button>
</PageHeader>
<div class="rr-card">
<div class="flex items-center gap-3 mb-4">
<el-input v-model="keyword" placeholder="按变量名/标识搜索" clearable style="width: 260px" @keyup.enter="onSearch" />
<el-button type="primary" @click="onSearch">搜索</el-button>
<el-button @click="onReset">重置</el-button>
</div>
<DataTable
:columns="[
{ prop: 'varName', label: '变量名', width: 120 },
{ prop: 'varKey', label: '标识', width: 140 },
{ prop: 'varUnit', label: '单位', width: 80 },
{ prop: 'currentValue', label: '当前值', width: 130, slot: 'value' },
{ prop: 'sourceType', label: '取值方式', width: 110, slot: 'source' },
{ prop: 'sourceCode', label: '采集代码', width: 100 },
{ prop: 'status', label: '状态', width: 80, slot: 'status' },
{ prop: 'updateDate', label: '更新时间', width: 170 },
{ prop: 'action', label: '操作', width: 220, slot: 'action', fixed: 'right' }
]"
:data="list"
:loading="loading"
:total="total"
:page="page"
:limit="limit"
:show-pagination="true"
:page-sizes="[10, 20, 50, 100]"
@page-change="onPageChange"
@limit-change="onLimitChange"
empty-text="暂无变量"
borderless
row-key="id"
>
<template #value="{ row }">
<div class="flex items-center gap-2">
<span class="font-bold text-primary">¥{{ Number(row.currentValue).toLocaleString() }}</span>
<template v-if="quickEditId === row.id">
<el-input v-model="quickValue" size="small" style="width: 90px" placeholder="新值" @keyup.enter="quickUpdateValue(row)" />
<el-button size="small" type="primary" @click="quickUpdateValue(row)">确认</el-button>
<el-button size="small" @click="quickEditId = null; quickValue = ''">取消</el-button>
</template>
<el-button v-else size="small" text type="primary" @click="quickEditId = row.id; quickValue = String(row.currentValue)">改值</el-button>
</div>
</template>
<template #source="{ row }">
<el-tag :type="row.sourceType === 2 ? 'warning' : 'info'" size="small" effect="light">
{{ SOURCE_MAP[row.sourceType] || '未知' }}
</el-tag>
</template>
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small" effect="light">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
<template #action="{ row }">
<el-link type="primary" :underline="false" @click="openEdit(row)">编辑</el-link>
<el-divider direction="vertical" />
<el-link type="warning" :underline="false" @click="toggleStatus(row)">{{ row.status === 1 ? '禁用' : '启用' }}</el-link>
<el-divider direction="vertical" />
<el-link type="danger" :underline="false" @click="remove(row)">删除</el-link>
</template>
</DataTable>
</div>
<!-- 新增/编辑弹窗 -->
<el-dialog v-model="dialogVisible" :title="form.id ? '编辑变量' : '新增变量'" width="520px" destroy-on-close>
<el-form label-width="90px" label-position="left">
<el-form-item label="变量标识" required>
<el-input v-model="form.varKey" placeholder="如 copper_price (英文/下划线, 唯一)" :disabled="!!form.id" />
</el-form-item>
<el-form-item label="变量名" required>
<el-input v-model="form.varName" placeholder="如 铜价 (公式中显示的名称)" />
</el-form-item>
<el-form-item label="单位">
<el-input v-model="form.varUnit" placeholder="如 元/吨" />
</el-form-item>
<el-form-item label="当前值">
<el-input-number v-model="form.currentValue" :precision="4" :step="100" style="width: 100%" />
</el-form-item>
<el-form-item label="取值方式">
<el-radio-group v-model="form.sourceType">
<el-radio :value="1">手工维护</el-radio>
<el-radio :value="2">AKShare采集</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.sourceType === 2" label="采集代码">
<el-input v-model="form.sourceCode" placeholder="AKShare 商品代码, 如 copper/aluminum/steel" />
</el-form-item>
<el-form-item v-if="form.sourceType === 2" label="换算公式">
<el-input v-model="form.formula" placeholder="可选, 如 raw * 1.05 (raw=采集原始值)" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="form.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
</template>

View File

@ -150,6 +150,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/modules/other/goodunit/GoodunitListView.vue'),
meta: { title: '商品单位' }
},
{
path: "/other/priceFormulaVariable",
name: "OtherPriceFormulaVariable",
component: () => import("@/modules/other/priceFormulaVariable/PriceFormulaVariableListView.vue"),
meta: { title: "价格变量管理" }
},
{
path: '/other/label',
name: 'OtherLabel',