同步仓库名称变更,涉及 16 个文件 66 处引用: - ai-skills: 菜单配置 - backend/guides: AI操作手册、环境配置、部署、gitnexus、opencode 工作流 - backend: 模块创建规则、通信规范、协作工作流、实施规范 - frontend: 收银设计、管理后台实施计划 - standards: 数据库设计规范
37 KiB
收银系统后台管理功能完善实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers-subagent-driven-development (recommended) or superpowers-executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 完善收银系统后台管理的前端页面功能,为所有列表页面添加新增/编辑表单弹窗,完善订单开台功能,优化营业报表展示
Architecture: 基于现有 admin-ui 框架和 RuiTable 组件,为每个收银模块创建 FormDialog 组件,遵循项目已有的组件模式和编码规范
Tech Stack: Vue 3 + TypeScript + Element Plus + Vite
文件结构
新增文件
src/views/cashier/store/StoreFormDialog.vue- 门店表单弹窗src/views/cashier/room/RoomFormDialog.vue- 包间表单弹窗src/views/cashier/product/ProductFormDialog.vue- 商品表单弹窗src/views/cashier/pricing/PricingStrategyFormDialog.vue- 定价策略表单弹窗src/views/cashier/pricing/PricingPackageDialog.vue- 套餐管理弹窗src/views/cashier/order/OpenRoomDialog.vue- 开台弹窗
修改文件
src/views/cashier/store/Index.vue- 集成门店表单弹窗src/views/cashier/room/Index.vue- 集成包间表单弹窗src/views/cashier/product/Index.vue- 集成商品表单弹窗src/views/cashier/pricing/Index.vue- 集成定价策略表单弹窗和套餐管理src/views/cashier/order/Index.vue- 集成开台弹窗src/service/cashier/*Service.ts- 补充缺失的方法
模块优先级
按业务依赖关系,实施顺序为:
- 门店管理(最基础,其他模块依赖门店)
- 商品管理
- 包间管理(依赖门店和包间类型)
- 定价策略(依赖包间类型)
- 订单管理(依赖门店和包间)
- 营业报表优化
Task 1: 完善门店管理
Files:
- Create:
src/views/cashier/store/StoreFormDialog.vue - Modify:
src/views/cashier/store/Index.vue
Context: 门店实体字段:name, address, phone, contactName, status, remark
Step 1: 创建门店表单弹窗
Create src/views/cashier/store/StoreFormDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { storeService } from '@/service/cashier/storeService'
const props = defineProps<{
visible: boolean
row?: any
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
// 表单数据
const form = reactive({
id: undefined as number | undefined,
name: '',
address: '',
phone: '',
contactName: '',
status: 1,
remark: '',
})
// 表单引用
const formRef = ref()
// 加载状态
const loading = ref(false)
// 是否编辑模式
const isEdit = ref(false)
// 监听 visible 变化
watch(() => props.visible, (val) => {
if (val) {
if (props.row) {
isEdit.value = true
Object.assign(form, props.row)
} else {
isEdit.value = false
Object.assign(form, {
id: undefined,
name: '',
address: '',
phone: '',
contactName: '',
status: 1,
remark: '',
})
}
}
})
// 表单校验规则
const rules = {
name: [
{ required: true, message: '请输入门店名称', trigger: 'blur' },
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' },
],
}
// 提交表单
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
if (isEdit.value) {
await storeService.update(form as any)
ElMessage.success('修改成功')
} else {
await storeService.add(form)
ElMessage.success('新增成功')
}
emit('success')
emit('update:visible', false)
} catch {
// 错误已由请求拦截器统一提示
} finally {
loading.value = false
}
}
// 关闭弹窗
function handleClose() {
emit('update:visible', false)
}
</script>
<template>
<el-dialog
class="rui-dialog"
:title="isEdit ? '编辑门店' : '新增门店'"
:model-value="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="门店名称" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入门店名称" />
</el-form-item>
<el-form-item label="门店地址" prop="address">
<el-input v-model.trim="form.address" placeholder="请输入门店地址" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model.trim="form.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="联系人" prop="contactName">
<el-input v-model.trim="form.contactName" placeholder="请输入联系人姓名" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio-button :label="1">启用</el-radio-button>
<el-radio-button :label="0">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model.trim="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">
确定
</el-button>
</template>
</el-dialog>
</template>
Step 2: 修改门店管理页面集成弹窗
Modify src/views/cashier/store/Index.vue:
Add imports:
import StoreFormDialog from './StoreFormDialog.vue'
Add state:
const dialogVisible = ref(false)
const currentRow = ref<any>()
Replace handleAdd:
function handleAdd() {
currentRow.value = undefined
dialogVisible.value = true
}
Replace handleEdit:
function handleEdit(row: any) {
currentRow.value = row
dialogVisible.value = true
}
Add dialog component in template:
<StoreFormDialog
v-model:visible="dialogVisible"
:row="currentRow"
@success="tableRef?.refresh()"
/>
Step 3: 验证
Run:
cd /Users/zhangsheng/rhkj/rui-framework/admin-ui
pnpm dev:cashier
Expected: 门店管理页面可以正常打开新增/编辑弹窗
Step 4: Commit
git add src/views/cashier/store/
git commit -m "feat(cashier): 完善门店管理新增编辑功能"
Task 2: 完善商品管理
Files:
- Create:
src/views/cashier/product/ProductFormDialog.vue - Modify:
src/views/cashier/product/Index.vue - Modify:
src/service/cashier/productService.ts
Context: 商品实体字段:name, price, productType(1实物/2服务/3虚拟), category, unit, stock, status, description, storeId
Step 1: 补充商品 Service 方法
Verify src/service/cashier/productService.ts has:
import { BaseService } from '../BaseService'
class ProductService extends BaseService {
constructor() {
super('/cashier/admin/product')
}
}
export const productService = new ProductService()
Step 2: 创建商品表单弹窗
Create src/views/cashier/product/ProductFormDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { productService } from '@/service/cashier/productService'
const props = defineProps<{
visible: boolean
row?: any
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
const form = reactive({
id: undefined as number | undefined,
name: '',
price: 0,
productType: 1,
category: '',
unit: '',
stock: 0,
status: 1,
description: '',
storeId: undefined as number | undefined,
})
const formRef = ref()
const loading = ref(false)
const isEdit = ref(false)
watch(() => props.visible, (val) => {
if (val) {
if (props.row) {
isEdit.value = true
Object.assign(form, props.row)
} else {
isEdit.value = false
Object.assign(form, {
id: undefined,
name: '',
price: 0,
productType: 1,
category: '',
unit: '',
stock: 0,
status: 1,
description: '',
storeId: undefined,
})
}
}
})
const rules = {
name: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
price: [{ required: true, message: '请输入售价', trigger: 'blur' }],
productType: [{ required: true, message: '请选择商品类型', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
if (isEdit.value) {
await productService.update(form as any)
ElMessage.success('修改成功')
} else {
await productService.add(form)
ElMessage.success('新增成功')
}
emit('success')
emit('update:visible', false)
} catch {
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
</script>
<template>
<el-dialog
class="rui-dialog"
:title="isEdit ? '编辑商品' : '新增商品'"
:model-value="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="商品名称" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入商品名称" />
</el-form-item>
<el-form-item label="商品类型" prop="productType">
<el-radio-group v-model="form.productType">
<el-radio-button :label="1">实物商品</el-radio-button>
<el-radio-button :label="2">服务商品</el-radio-button>
<el-radio-button :label="3">虚拟商品</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="售价" prop="price">
<el-input-number v-model="form.price" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="分类" prop="category">
<el-input v-model.trim="form.category" placeholder="请输入分类" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model.trim="form.unit" placeholder="如:个、杯、小时" />
</el-form-item>
<el-form-item label="库存" prop="stock" v-if="form.productType === 1">
<el-input-number v-model="form.stock" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio-button :label="1">启用</el-radio-button>
<el-radio-button :label="0">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model.trim="form.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
Step 3: 修改商品管理页面
Similar to Task 1 Step 2, integrate ProductFormDialog into product/Index.vue
Step 4: Commit
git add src/views/cashier/product/ src/service/cashier/productService.ts
git commit -m "feat(cashier): 完善商品管理新增编辑功能"
Task 3: 完善包间管理
Files:
- Create:
src/views/cashier/room/RoomFormDialog.vue - Modify:
src/views/cashier/room/Index.vue
Context: 包间实体字段:storeId, roomTypeId, name, roomNo, roomStatus, enabled, sort 需要获取门店列表和包间类型列表
Step 1: 创建包间表单弹窗
Create src/views/cashier/room/RoomFormDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { roomService } from '@/service/cashier/roomService'
import { storeService } from '@/service/cashier/storeService'
const props = defineProps<{
visible: boolean
row?: any
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
const form = reactive({
id: undefined as number | undefined,
storeId: undefined as number | undefined,
roomTypeId: undefined as number | undefined,
name: '',
roomNo: '',
enabled: 1,
sort: 0,
})
const formRef = ref()
const loading = ref(false)
const isEdit = ref(false)
const storeList = ref<any[]>([])
const roomTypeList = ref<any[]>([])
watch(() => props.visible, (val) => {
if (val) {
loadStoreList()
if (props.row) {
isEdit.value = true
Object.assign(form, props.row)
} else {
isEdit.value = false
Object.assign(form, {
id: undefined,
storeId: undefined,
roomTypeId: undefined,
name: '',
roomNo: '',
enabled: 1,
sort: 0,
})
}
}
})
async function loadStoreList() {
try {
storeList.value = await storeService.list({ status: 1 })
} catch {
storeList.value = []
}
}
const rules = {
storeId: [{ required: true, message: '请选择门店', trigger: 'change' }],
name: [{ required: true, message: '请输入包间名称', trigger: 'blur' }],
roomNo: [{ required: true, message: '请输入包间编号', trigger: 'blur' }],
enabled: [{ required: true, message: '请选择状态', trigger: 'change' }],
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
if (isEdit.value) {
await roomService.update(form as any)
ElMessage.success('修改成功')
} else {
await roomService.add(form)
ElMessage.success('新增成功')
}
emit('success')
emit('update:visible', false)
} catch {
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
</script>
<template>
<el-dialog
class="rui-dialog"
:title="isEdit ? '编辑包间' : '新增包间'"
:model-value="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="所属门店" prop="storeId">
<el-select v-model="form.storeId" placeholder="请选择门店" style="width: 100%">
<el-option
v-for="item in storeList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="包间名称" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入包间名称" />
</el-form-item>
<el-form-item label="包间编号" prop="roomNo">
<el-input v-model.trim="form.roomNo" placeholder="请输入包间编号" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-radio-group v-model="form.enabled">
<el-radio-button :label="1">启用</el-radio-button>
<el-radio-button :label="0">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
Step 2: 修改包间管理页面
Integrate RoomFormDialog into room/Index.vue (similar pattern)
Step 3: Commit
git add src/views/cashier/room/
git commit -m "feat(cashier): 完善包间管理新增编辑功能"
Task 4: 完善定价策略
Files:
- Create:
src/views/cashier/pricing/PricingStrategyFormDialog.vue - Create:
src/views/cashier/pricing/PricingPackageDialog.vue - Modify:
src/views/cashier/pricing/Index.vue
Context: 定价策略实体:strategyName, roomTypeId, status 套餐实体:name, price, duration, durationUnit, description, strategyId, billingType, minDuration, restrictions, isDefault, sort, status
Step 1: 创建定价策略表单弹窗
Create src/views/cashier/pricing/PricingStrategyFormDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { pricingService } from '@/service/cashier/pricingService'
const props = defineProps<{
visible: boolean
row?: any
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
const form = reactive({
id: undefined as number | undefined,
strategyName: '',
roomTypeId: undefined as number | undefined,
status: 1,
})
const formRef = ref()
const loading = ref(false)
const isEdit = ref(false)
watch(() => props.visible, (val) => {
if (val) {
if (props.row) {
isEdit.value = true
Object.assign(form, props.row)
} else {
isEdit.value = false
Object.assign(form, {
id: undefined,
strategyName: '',
roomTypeId: undefined,
status: 1,
})
}
}
})
const rules = {
strategyName: [{ required: true, message: '请输入策略名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
if (isEdit.value) {
await pricingService.update(form as any)
ElMessage.success('修改成功')
} else {
await pricingService.add(form)
ElMessage.success('新增成功')
}
emit('success')
emit('update:visible', false)
} catch {
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
</script>
<template>
<el-dialog
class="rui-dialog"
:title="isEdit ? '编辑策略' : '新增策略'"
:model-value="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="策略名称" prop="strategyName">
<el-input v-model.trim="form.strategyName" placeholder="请输入策略名称" />
</el-form-item>
<el-form-item label="包间类型" prop="roomTypeId">
<el-input-number v-model="form.roomTypeId" :min="1" style="width: 100%" placeholder="请输入包间类型ID" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio-button :label="1">启用</el-radio-button>
<el-radio-button :label="0">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
Step 2: 创建套餐管理弹窗
Create src/views/cashier/pricing/PricingPackageDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { pricingService } from '@/service/cashier/pricingService'
const props = defineProps<{
visible: boolean
strategyId?: number
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
}>()
const packageList = ref<any[]>([])
const loading = ref(false)
const dialogVisible = ref(false)
const currentPackage = ref<any>()
const isEditPackage = ref(false)
const form = reactive({
id: undefined as number | undefined,
name: '',
price: 0,
duration: 1,
durationUnit: 'hour',
description: '',
strategyId: undefined as number | undefined,
billingType: 1,
minDuration: 0,
isDefault: 0,
sort: 0,
status: 1,
})
const formRef = ref()
const formLoading = ref(false)
watch(() => props.visible, (val) => {
if (val && props.strategyId) {
loadPackages()
}
})
async function loadPackages() {
loading.value = true
try {
const res = await pricingService.getPackages(props.strategyId!)
packageList.value = res || []
} catch {
packageList.value = []
} finally {
loading.value = false
}
}
function handleAddPackage() {
isEditPackage.value = false
currentPackage.value = undefined
Object.assign(form, {
id: undefined,
name: '',
price: 0,
duration: 1,
durationUnit: 'hour',
description: '',
strategyId: props.strategyId,
billingType: 1,
minDuration: 0,
isDefault: 0,
sort: 0,
status: 1,
})
dialogVisible.value = true
}
function handleEditPackage(row: any) {
isEditPackage.value = true
currentPackage.value = row
Object.assign(form, row)
dialogVisible.value = true
}
async function handleDeletePackage(row: any) {
ElMessageBox.confirm(`确认删除套餐 "${row.name}" 吗?`, '提示', {
type: 'warning',
}).then(async () => {
try {
await pricingService.deletePackage(row.id)
ElMessage.success('删除成功')
loadPackages()
} catch {}
}).catch(() => {})
}
async function handleSubmitPackage() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
formLoading.value = true
try {
if (isEditPackage.value) {
await pricingService.updatePackage(form as any)
ElMessage.success('修改成功')
} else {
await pricingService.addPackage(form)
ElMessage.success('新增成功')
}
dialogVisible.value = false
loadPackages()
} catch {
} finally {
formLoading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
function handleCloseForm() {
dialogVisible.value = false
}
</script>
<template>
<el-dialog
class="rui-dialog"
title="套餐管理"
:model-value="visible"
width="800px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="mb-4">
<el-button type="primary" @click="handleAddPackage">新增套餐</el-button>
</div>
<el-table :data="packageList" v-loading="loading" border>
<el-table-column prop="name" label="套餐名称" min-width="150" />
<el-table-column prop="price" label="价格" width="100" align="right">
<template #default="{ row }">¥{{ (row.price || 0).toFixed(2) }}</template>
</el-table-column>
<el-table-column prop="duration" label="时长" width="100" align="center">
<template #default="{ row }">{{ row.duration }}{{ row.durationUnit === 'hour' ? '小时' : '天' }}</template>
</el-table-column>
<el-table-column prop="billingType" label="计费类型" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.billingType === 1">按时</el-tag>
<el-tag v-else-if="row.billingType === 2" type="success">按局</el-tag>
<el-tag v-else type="warning">包时段</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="handleEditPackage(row)">编辑</el-button>
<el-button link type="danger" size="small" @click="handleDeletePackage(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 套餐表单弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="isEditPackage ? '编辑套餐' : '新增套餐'"
width="500px"
:close-on-click-modal="false"
append-to-body
@close="handleCloseForm"
>
<el-form ref="formRef" :model="form" label-width="100px">
<el-form-item label="套餐名称" prop="name" required>
<el-input v-model.trim="form.name" placeholder="请输入套餐名称" />
</el-form-item>
<el-form-item label="价格" prop="price" required>
<el-input-number v-model="form.price" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="时长" prop="duration" required>
<el-input-number v-model="form.duration" :min="1" style="width: 100%" />
</el-form-item>
<el-form-item label="时长单位" prop="durationUnit">
<el-radio-group v-model="form.durationUnit">
<el-radio-button label="hour">小时</el-radio-button>
<el-radio-button label="day">天</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="计费类型" prop="billingType">
<el-radio-group v-model="form.billingType">
<el-radio-button :label="1">按时计费</el-radio-button>
<el-radio-button :label="2">按局计费</el-radio-button>
<el-radio-button :label="3">包时段</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="最小时长" prop="minDuration">
<el-input-number v-model="form.minDuration" :min="0" style="width: 100%" placeholder="分钟" />
</el-form-item>
<el-form-item label="默认套餐" prop="isDefault">
<el-radio-group v-model="form.isDefault">
<el-radio-button :label="1">是</el-radio-button>
<el-radio-button :label="0">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio-button :label="1">启用</el-radio-button>
<el-radio-button :label="0">禁用</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model.trim="form.description" type="textarea" :rows="2" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCloseForm">取消</el-button>
<el-button type="primary" :loading="formLoading" @click="handleSubmitPackage">确定</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
Step 3: 补充 pricingService 方法
Modify src/service/cashier/pricingService.ts:
import { BaseService } from '../BaseService'
import { request } from '@/utils/request'
class PricingService extends BaseService {
constructor() {
super('/cashier/admin/pricing-strategy')
}
async getPackages(strategyId: number) {
const res: any = await request({
url: '/cashier/admin/pricing-package/list',
method: 'get',
params: { strategyId },
})
return res.data || []
}
async addPackage(data: any) {
const res: any = await request({
url: '/cashier/admin/pricing-package',
method: 'post',
data,
})
return res.data
}
async updatePackage(data: any) {
const res: any = await request({
url: '/cashier/admin/pricing-package',
method: 'put',
data,
})
return res.data
}
async deletePackage(id: number) {
const res: any = await request({
url: `/cashier/admin/pricing-package/${id}`,
method: 'delete',
})
return res.data
}
}
export const pricingService = new PricingService()
Step 4: 修改定价策略页面
Integrate both dialogs into pricing/Index.vue
Step 5: Commit
git add src/views/cashier/pricing/ src/service/cashier/pricingService.ts
git commit -m "feat(cashier): 完善定价策略和套餐管理功能"
Task 5: 完善订单管理(开台功能)
Files:
- Create:
src/views/cashier/order/OpenRoomDialog.vue - Modify:
src/views/cashier/order/Index.vue - Modify:
src/service/cashier/orderService.ts
Context: 开台需要:门店ID、包间ID、顾客姓名、顾客电话、订单类型、备注
Step 1: 补充 orderService 方法
Modify src/service/cashier/orderService.ts:
import { BaseService } from '../BaseService'
import { request } from '@/utils/request'
class OrderService extends BaseService {
constructor() {
super('/cashier/admin/order')
}
async checkout(id: number) {
const res: any = await request({
url: `${this.baseUrl}/${id}/checkout`,
method: 'post',
})
return res.data
}
async refund(id: number, data: { amount: number; reason: string }) {
const res: any = await request({
url: `${this.baseUrl}/${id}/refund`,
method: 'post',
data,
})
return res.data
}
async openRoom(data: {
storeId: number
roomId: number
customerName?: string
customerPhone?: string
orderType?: number
remark?: string
}) {
const res: any = await request({
url: `${this.baseUrl}/open`,
method: 'post',
data,
})
return res.data
}
}
export const orderService = new OrderService()
Step 2: 创建开台弹窗
Create src/views/cashier/order/OpenRoomDialog.vue:
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { orderService } from '@/service/cashier/orderService'
import { storeService } from '@/service/cashier/storeService'
import { roomService } from '@/service/cashier/roomService'
const props = defineProps<{
visible: boolean
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'success'): void
}>()
const form = reactive({
storeId: undefined as number | undefined,
roomId: undefined as number | undefined,
customerName: '',
customerPhone: '',
orderType: 1,
remark: '',
})
const formRef = ref()
const loading = ref(false)
const storeList = ref<any[]>([])
const roomList = ref<any[]>([])
watch(() => props.visible, (val) => {
if (val) {
loadStores()
Object.assign(form, {
storeId: undefined,
roomId: undefined,
customerName: '',
customerPhone: '',
orderType: 1,
remark: '',
})
}
})
async function loadStores() {
try {
storeList.value = await storeService.list({ status: 1 })
} catch {
storeList.value = []
}
}
async function handleStoreChange(storeId: number) {
form.roomId = undefined
if (!storeId) {
roomList.value = []
return
}
try {
roomList.value = await roomService.list({ storeId, enabled: 1 })
} catch {
roomList.value = []
}
}
const rules = {
storeId: [{ required: true, message: '请选择门店', trigger: 'change' }],
roomId: [{ required: true, message: '请选择包间', trigger: 'change' }],
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid) return
loading.value = true
try {
await orderService.openRoom(form as any)
ElMessage.success('开台成功')
emit('success')
emit('update:visible', false)
} catch {
} finally {
loading.value = false
}
}
function handleClose() {
emit('update:visible', false)
}
</script>
<template>
<el-dialog
class="rui-dialog"
title="开台"
:model-value="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="选择门店" prop="storeId">
<el-select
v-model="form.storeId"
placeholder="请选择门店"
style="width: 100%"
@change="handleStoreChange"
>
<el-option
v-for="item in storeList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="选择包间" prop="roomId">
<el-select v-model="form.roomId" placeholder="请选择包间" style="width: 100%">
<el-option
v-for="item in roomList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="顾客姓名" prop="customerName">
<el-input v-model.trim="form.customerName" placeholder="请输入顾客姓名" />
</el-form-item>
<el-form-item label="顾客电话" prop="customerPhone">
<el-input v-model.trim="form.customerPhone" placeholder="请输入顾客电话" />
</el-form-item>
<el-form-item label="订单类型" prop="orderType">
<el-radio-group v-model="form.orderType">
<el-radio-button :label="1">正常订单</el-radio-button>
<el-radio-button :label="2">预订订单</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model.trim="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">确认开台</el-button>
</template>
</el-dialog>
</template>
Step 3: 修改订单管理页面
Integrate OpenRoomDialog into order/Index.vue
Step 4: Commit
git add src/views/cashier/order/ src/service/cashier/orderService.ts
git commit -m "feat(cashier): 完善订单管理开台功能"
Task 6: 营业报表优化
Files:
- Modify:
src/views/cashier/report/Index.vue
Context: 当前报表已展示日报数据和包间利用率,可以优化图表展示
Step 1: 添加图表展示
Install echarts if not already installed:
cd /Users/zhangsheng/rhkj/rui-framework/admin-ui
pnpm add echarts vue-echarts
Step 2: 优化报表页面
Add chart components for:
- 营业趋势图(折线图)
- 支付方式饼图
- 包间利用率柱状图
Step 3: Commit
git add src/views/cashier/report/ package.json pnpm-lock.yaml
git commit -m "feat(cashier): 优化营业报表图表展示"
Task 7: 验证和文档更新
Files:
- Modify:
docs/admin-ui-status.md
Step 1: 验证所有功能
Run:
cd /Users/zhangsheng/rhkj/rui-framework/admin-ui
pnpm build:cashier
Expected: 构建成功,无错误
Step 2: 更新状态文档
Update docs/admin-ui-status.md:
### 8. 收银系统模块
| 功能 | 页面 | 状态 | 说明 |
|------|------|------|------|
| 门店管理 | cashier/store/Index.vue | ✅ 完成 | 增删改查、状态切换 |
| 包间管理 | cashier/room/Index.vue | ✅ 完成 | 增删改查、状态切换 |
| 商品管理 | cashier/product/Index.vue | ✅ 完成 | 增删改查、状态切换 |
| 定价策略 | cashier/pricing/Index.vue | ✅ 完成 | 增删改查、套餐管理 |
| 订单管理 | cashier/order/Index.vue | ✅ 完成 | 开台、结账、退款、查询 |
| 营业报表 | cashier/report/Index.vue | ✅ 完成 | 日报、包间利用率、图表 |
Step 3: Commit
git add docs/admin-ui-status.md
git commit -m "docs: 更新收银系统功能状态"
验证清单
- 门店管理:新增、编辑、删除、查询、状态切换正常
- 包间管理:新增、编辑、删除、查询、状态切换正常
- 商品管理:新增、编辑、删除、查询、状态切换正常
- 定价策略:新增、编辑、删除、查询、套餐管理正常
- 订单管理:开台、结账、退款、查询正常
- 营业报表:数据展示、图表正常
- 所有页面无编译错误
- 构建产物正常
风险与依赖
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| 后端 API 字段与前端不一致 | 高 | 开发时对比后端实体字段,确保一致 |
| 包间类型数据未对接 | 中 | 先使用 ID 输入,后续优化为下拉选择 |
| 图表库引入增加包体积 | 低 | 按需引入 echarts 组件 |
| 开台功能需要实时状态更新 | 中 | 开台成功后刷新列表 |
后续扩展
- 包间类型管理 - 添加包间类型 CRUD 页面
- 设备管理 - 添加门店设备管理页面
- 会员管理 - 完善会员等级、积分管理
- 库存管理 - 添加商品库存预警功能
- 数据导出 - 为所有列表添加导出功能