| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- <template>
- <div class="payment-entry-page">
- <div class="page-toolbar">
- <div>
- <h2>支付入口</h2>
- <p>定义支付从哪里发起,并为每个入口配置可用的支付方式</p>
- </div>
- <el-button type="primary" icon="Plus" @click="openCreate">新增支付入口</el-button>
- </div>
- <el-table v-loading="loading" :data="entries" border>
- <el-table-column label="支付入口" min-width="190">
- <template #default="{ row }">
- <div class="entry-name">
- <span>{{ row.name }}</span>
- <el-tooltip v-if="row.is_system" content="系统内置入口,只能调整配置,不能删除" placement="top">
- <el-icon class="locked"><Lock /></el-icon>
- </el-tooltip>
- </div>
- <span class="entry-code">{{ row.code }}</span>
- </template>
- </el-table-column>
- <el-table-column label="可用支付方式" min-width="280">
- <template #default="{ row }">
- <el-tag v-for="code in row.method_codes" :key="code" class="method-tag" effect="plain">
- {{ methodLabels[code] || code }}
- </el-tag>
- <span v-if="!row.method_codes?.length" class="muted">未配置</span>
- </template>
- </el-table-column>
- <el-table-column label="排序" prop="sort" width="80" align="center" />
- <el-table-column label="状态" width="110" align="center">
- <template #default="{ row }">
- <el-switch
- v-model="row.enabled"
- inline-prompt
- active-text="启用"
- inactive-text="停用"
- @change="changeStatus(row)"
- />
- </template>
- </el-table-column>
- <el-table-column label="说明" prop="description" min-width="240" show-overflow-tooltip />
- <el-table-column label="操作" width="150" fixed="right" align="center">
- <template #default="{ row }">
- <el-button text type="primary" icon="Edit" @click="openEdit(row)">编辑</el-button>
- <el-button text type="danger" icon="Delete" :disabled="row.is_system" @click="removeEntry(row)">删除</el-button>
- </template>
- </el-table-column>
- <template #empty>
- <el-empty description="暂无支付入口" />
- </template>
- </el-table>
- <el-dialog v-model="dialogVisible" :title="editingID ? '编辑支付入口' : '新增支付入口'" width="620px" destroy-on-close>
- <el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
- <el-form-item label="名称" prop="name">
- <el-input v-model="form.name" maxlength="50" placeholder="例如:收费岗亭、停车小票" />
- </el-form-item>
- <el-form-item label="入口编码" prop="code">
- <el-input v-model="form.code" :disabled="Boolean(editingID)" maxlength="32" placeholder="例如:counter" />
- <div class="form-hint">保存后编码不可修改,业务记录会使用该编码关联入口</div>
- </el-form-item>
- <el-form-item label="支付方式" prop="method_codes">
- <el-checkbox-group v-model="form.method_codes">
- <el-checkbox v-for="method in methods" :key="method.code" :label="method.code" :disabled="method.code === 'cash'">
- {{ method.name }}
- </el-checkbox>
- </el-checkbox-group>
- <div class="form-hint">现金支付是基础方式,始终保留在入口中;其它方式可按入口单独启停。</div>
- </el-form-item>
- <el-form-item label="排序">
- <el-input-number v-model="form.sort" :min="0" :max="999" controls-position="right" />
- </el-form-item>
- <el-form-item label="启用">
- <el-switch v-model="form.enabled" />
- </el-form-item>
- <el-form-item label="说明">
- <el-input v-model="form.description" type="textarea" :rows="3" maxlength="200" show-word-limit />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" :loading="saving" @click="saveEntry">保存</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { onMounted, reactive, ref } from 'vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import {
- createPaymentEntry,
- deletePaymentEntry,
- getPaymentEntries,
- getPaymentMethods,
- setPaymentEntryStatus,
- updatePaymentEntry
- } from '@/api/payment'
- const codePattern = /^[a-z][a-z0-9_]{1,31}$/
- const entries = ref([])
- const methods = ref([])
- const methodLabels = reactive({})
- const loading = ref(false)
- const saving = ref(false)
- const dialogVisible = ref(false)
- const editingID = ref(0)
- const formRef = ref()
- const form = reactive(defaultForm())
- const rules = {
- name: [{ required: true, message: '请输入支付入口名称', trigger: 'blur' }],
- code: [
- { required: true, message: '请输入入口编码', trigger: 'blur' },
- { pattern: codePattern, message: '以小写字母开头,只能包含小写字母、数字和下划线', trigger: 'blur' }
- ],
- method_codes: [{ type: 'array', required: true, min: 1, message: '请至少选择一种支付方式', trigger: 'change' }]
- }
- function defaultForm() {
- return { code: '', name: '', method_codes: ['cash'], enabled: true, sort: 50, description: '' }
- }
- async function loadData() {
- loading.value = true
- try {
- const [entryRes, methodRes] = await Promise.all([getPaymentEntries(), getPaymentMethods()])
- if (entryRes.code === 0) entries.value = entryRes.data || []
- if (methodRes.code === 0) {
- methods.value = (methodRes.data || []).filter(item => !item.is_legacy)
- Object.keys(methodLabels).forEach(key => delete methodLabels[key])
- methods.value.forEach(item => { methodLabels[item.code] = item.name })
- }
- } finally {
- loading.value = false
- }
- }
- function openCreate() {
- editingID.value = 0
- Object.assign(form, defaultForm())
- dialogVisible.value = true
- }
- function openEdit(row) {
- editingID.value = row.ID
- Object.assign(form, {
- code: row.code,
- name: row.name,
- method_codes: [...(row.method_codes || ['cash'])],
- enabled: row.enabled,
- sort: row.sort,
- description: row.description || ''
- })
- if (!form.method_codes.includes('cash')) form.method_codes.unshift('cash')
- dialogVisible.value = true
- }
- async function saveEntry() {
- await formRef.value.validate()
- saving.value = true
- try {
- const payload = { ...form, method_codes: [...new Set(['cash', ...form.method_codes])] }
- const res = editingID.value
- ? await updatePaymentEntry(editingID.value, payload)
- : await createPaymentEntry(payload)
- if (res.code === 0) {
- ElMessage.success(editingID.value ? '支付入口已更新' : '支付入口已新增')
- dialogVisible.value = false
- await loadData()
- }
- } finally {
- saving.value = false
- }
- }
- async function changeStatus(row) {
- const target = row.enabled
- try {
- const res = await setPaymentEntryStatus(row.ID, target)
- if (res.code === 0) ElMessage.success(target ? '支付入口已启用' : '支付入口已停用')
- else row.enabled = !target
- } catch (e) {
- row.enabled = !target
- }
- }
- async function removeEntry(row) {
- await ElMessageBox.confirm(`确定删除“${row.name}”吗?历史支付记录不会被删除。`, '删除支付入口', {
- type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消'
- })
- const res = await deletePaymentEntry(row.ID)
- if (res.code === 0) {
- ElMessage.success('支付入口已删除')
- await loadData()
- }
- }
- onMounted(loadData)
- </script>
- <style scoped>
- .page-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
- .page-toolbar h2 { margin: 0; font-size: 18px; font-weight: 600; color: #303133; }
- .page-toolbar p { margin: 5px 0 0; font-size: 13px; color: #909399; }
- .entry-name { display: flex; align-items: center; gap: 6px; font-weight: 600; color: #303133; }
- .entry-code { display: block; margin-top: 3px; color: #909399; font-family: Consolas, monospace; font-size: 12px; }
- .locked { color: #e6a23c; }
- .method-tag { margin-right: 6px; }
- .muted { color: #909399; font-size: 12px; }
- .form-hint { margin-top: 4px; color: #909399; font-size: 12px; line-height: 1.5; }
- </style>
|