feat: 第三阶段业务模块(服务产康/月嫂/进销存)
- 新增3个migration(15张表): service/nanny/inventory - 新增15个Model + 13个Controller - 新增55条API路由(总计212条) - 新增3个前端API模块 + 11个管理页面 - vite build验证通过
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
// ─── Warehouses ──────────────────────────────────────────────────────────────
|
||||
export const warehouseApi = {
|
||||
getList: (params) => request.get('/inventory/warehouses', { params }),
|
||||
getDetail: (id) => request.get(`/inventory/warehouses/${id}`),
|
||||
create: (data) => request.post('/inventory/warehouses', data),
|
||||
update: (id, data) => request.put(`/inventory/warehouses/${id}`, data),
|
||||
delete: (id) => request.delete(`/inventory/warehouses/${id}`)
|
||||
}
|
||||
|
||||
// ─── Suppliers ───────────────────────────────────────────────────────────────
|
||||
export const supplierApi = {
|
||||
getList: (params) => request.get('/inventory/suppliers', { params }),
|
||||
getDetail: (id) => request.get(`/inventory/suppliers/${id}`),
|
||||
create: (data) => request.post('/inventory/suppliers', data),
|
||||
update: (id, data) => request.put(`/inventory/suppliers/${id}`, data),
|
||||
delete: (id) => request.delete(`/inventory/suppliers/${id}`)
|
||||
}
|
||||
|
||||
// ─── Materials ───────────────────────────────────────────────────────────────
|
||||
export const materialApi = {
|
||||
getList: (params) => request.get('/inventory/materials', { params }),
|
||||
getDetail: (id) => request.get(`/inventory/materials/${id}`),
|
||||
create: (data) => request.post('/inventory/materials', data),
|
||||
update: (id, data) => request.put(`/inventory/materials/${id}`, data),
|
||||
delete: (id) => request.delete(`/inventory/materials/${id}`)
|
||||
}
|
||||
|
||||
// ─── Purchase Orders ─────────────────────────────────────────────────────────
|
||||
export const purchaseOrderApi = {
|
||||
getList: (params) => request.get('/inventory/purchase-orders', { params }),
|
||||
getDetail: (id) => request.get(`/inventory/purchase-orders/${id}`),
|
||||
create: (data) => request.post('/inventory/purchase-orders', data),
|
||||
update: (id, data) => request.put(`/inventory/purchase-orders/${id}`, data),
|
||||
delete: (id) => request.delete(`/inventory/purchase-orders/${id}`),
|
||||
audit: (id, data) => request.put(`/inventory/purchase-orders/${id}/audit`, data)
|
||||
}
|
||||
|
||||
// ─── Stock Movements ─────────────────────────────────────────────────────────
|
||||
export const stockMovementApi = {
|
||||
getList: (params) => request.get('/inventory/stock-movements', { params }),
|
||||
getDetail: (id) => request.get(`/inventory/stock-movements/${id}`),
|
||||
create: (data) => request.post('/inventory/stock-movements', data),
|
||||
confirm: (id) => request.put(`/inventory/stock-movements/${id}/confirm`)
|
||||
}
|
||||
|
||||
// ─── Inventories ─────────────────────────────────────────────────────────────
|
||||
export const inventoryApi = {
|
||||
getList: (params) => request.get('/inventory/inventories', { params })
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
// ─── Nannies ─────────────────────────────────────────────────────────────────
|
||||
export const nannyApi = {
|
||||
getList: (params) => request.get('/nanny/nannies', { params }),
|
||||
getDetail: (id) => request.get(`/nanny/nannies/${id}`),
|
||||
create: (data) => request.post('/nanny/nannies', data),
|
||||
update: (id, data) => request.put(`/nanny/nannies/${id}`, data),
|
||||
delete: (id) => request.delete(`/nanny/nannies/${id}`)
|
||||
}
|
||||
|
||||
// ─── Nanny Orders ────────────────────────────────────────────────────────────
|
||||
export const nannyOrderApi = {
|
||||
getList: (params) => request.get('/nanny/orders', { params }),
|
||||
getDetail: (id) => request.get(`/nanny/orders/${id}`),
|
||||
create: (data) => request.post('/nanny/orders', data),
|
||||
update: (id, data) => request.put(`/nanny/orders/${id}`, data),
|
||||
delete: (id) => request.delete(`/nanny/orders/${id}`)
|
||||
}
|
||||
|
||||
// ─── Nanny Schedules ─────────────────────────────────────────────────────────
|
||||
export const nannyScheduleApi = {
|
||||
getList: (params) => request.get('/nanny/schedules', { params }),
|
||||
create: (data) => request.post('/nanny/schedules', data)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
// ─── Service Items ───────────────────────────────────────────────────────────
|
||||
export const serviceItemApi = {
|
||||
getList: (params) => request.get('/service/items', { params }),
|
||||
getDetail: (id) => request.get(`/service/items/${id}`),
|
||||
create: (data) => request.post('/service/items', data),
|
||||
update: (id, data) => request.put(`/service/items/${id}`, data),
|
||||
delete: (id) => request.delete(`/service/items/${id}`)
|
||||
}
|
||||
|
||||
// ─── Service Packages ────────────────────────────────────────────────────────
|
||||
export const servicePackageApi = {
|
||||
getList: (params) => request.get('/service/packages', { params }),
|
||||
getDetail: (id) => request.get(`/service/packages/${id}`),
|
||||
create: (data) => request.post('/service/packages', data),
|
||||
update: (id, data) => request.put(`/service/packages/${id}`, data),
|
||||
delete: (id) => request.delete(`/service/packages/${id}`)
|
||||
}
|
||||
|
||||
// ─── Service Orders ──────────────────────────────────────────────────────────
|
||||
export const serviceOrderApi = {
|
||||
getList: (params) => request.get('/service/orders', { params }),
|
||||
getDetail: (id) => request.get(`/service/orders/${id}`),
|
||||
create: (data) => request.post('/service/orders', data),
|
||||
update: (id, data) => request.put(`/service/orders/${id}`, data),
|
||||
delete: (id) => request.delete(`/service/orders/${id}`),
|
||||
pay: (id, data) => request.put(`/service/orders/${id}/pay`, data),
|
||||
complete: (id) => request.put(`/service/orders/${id}/complete`)
|
||||
}
|
||||
|
||||
// ─── Service Executions ──────────────────────────────────────────────────────
|
||||
export const serviceExecutionApi = {
|
||||
getList: (params) => request.get('/service/executions', { params }),
|
||||
create: (data) => request.post('/service/executions', data),
|
||||
start: (id) => request.put(`/service/executions/${id}/start`),
|
||||
finish: (id) => request.put(`/service/executions/${id}/finish`)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { materialApi } from '@/api/inventory.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ name: '', category: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, name: '', code: '', category: '', unit: '', spec: '', safety_stock: 0, status: 1, sort: 0 })
|
||||
const isEdit = ref(false)
|
||||
const formRules = { name: [{ required: true, message: '请输入物料名称', trigger: 'blur' }] }
|
||||
const categoryMap = { 1: '食材', 2: '日用品', 3: '母婴用品', 4: '医疗用品', 5: '其他' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await materialApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { name: '', category: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, name: '', code: '', category: '', unit: '', spec: '', safety_stock: 0, status: 1, sort: 0 }); dialogVisible.value = true }
|
||||
async function handleEdit(row) { isEdit.value = true; const res = await materialApi.getDetail(row.id); const d = res.data; Object.assign(form, { id: d.id, name: d.name, code: d.code || '', category: d.category || '', unit: d.unit || '', spec: d.spec || '', safety_stock: d.safety_stock || 0, status: d.status, sort: d.sort || 0 }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await materialApi.update(form.id, form); ElMessage.success('更新成功') }
|
||||
else { await materialApi.create(form); ElMessage.success('创建成功') }
|
||||
dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除物料「${row.name}」吗?`, '删除确认', { type: 'warning' })
|
||||
await materialApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.name" placeholder="物料名称" style="width:180px" clearable @keydown.enter="handleSearch" />
|
||||
<el-select v-model="searchForm.category" placeholder="分类" clearable style="width:120px">
|
||||
<el-option v-for="(v,k) in categoryMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option label="启用" :value="1" /><el-option label="禁用" :value="0" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增物料</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width:100%">
|
||||
<el-table-column prop="name" label="物料名称" min-width="120" />
|
||||
<el-table-column prop="code" label="物料编码" width="120" />
|
||||
<el-table-column label="分类" width="100" align="center"><template #default="{ row }">{{ categoryMap[row.category] }}</template></el-table-column>
|
||||
<el-table-column prop="unit" label="单位" width="70" align="center" />
|
||||
<el-table-column prop="spec" label="规格" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="safety_stock" label="安全库存" width="100" align="right" />
|
||||
<el-table-column label="状态" width="80" align="center"><template #default="{ row }"><el-tag :type="row.status?'success':'danger'" size="small">{{ row.status?'启用':'禁用' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[10,15,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑物料':'新增物料'" width="550px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="80px">
|
||||
<el-form-item label="物料名称" prop="name"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="物料编码"><el-input v-model="form.code" /></el-form-item>
|
||||
<el-form-item label="分类"><el-select v-model="form.category" style="width:100%"><el-option v-for="(v,k) in categoryMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="单位"><el-input v-model="form.unit" /></el-form-item>
|
||||
<el-form-item label="规格"><el-input v-model="form.spec" /></el-form-item>
|
||||
<el-form-item label="安全库存"><el-input-number v-model="form.safety_stock" :min="0" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sort" :min="0" style="width:100%" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,141 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { purchaseOrderApi } from '@/api/inventory.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ order_no: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 20 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, order_no: '', supplier_id: '', total_amount: 0, remark: '' })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
order_no: [{ required: true, message: '请输入采购单号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const statusMap = { 0: '待审批', 2: '已通过', 4: '已驳回' }
|
||||
const statusType = { 0: 'warning', 2: 'success', 4: 'danger' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await purchaseOrderApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { order_no: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
Object.assign(form, { id: null, order_no: '', supplier_id: '', total_amount: 0, remark: '' })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleEdit(row) {
|
||||
if (row.status >= 2) { ElMessage.warning('只能编辑待审批的采购单'); return }
|
||||
isEdit.value = true
|
||||
const res = await purchaseOrderApi.getDetail(row.id)
|
||||
const d = res.data
|
||||
Object.assign(form, { id: d.id, order_no: d.order_no, supplier_id: d.supplier_id || '', total_amount: d.total_amount, remark: d.remark || '' })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await purchaseOrderApi.update(form.id, form); ElMessage.success('更新成功') }
|
||||
else { await purchaseOrderApi.create(form); ElMessage.success('创建成功') }
|
||||
dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
if (row.status >= 2) { ElMessage.warning('只能删除待审批的采购单'); return }
|
||||
await ElMessageBox.confirm(`确定删除采购单「${row.order_no}」吗?`, '删除确认', { type: 'warning' })
|
||||
await purchaseOrderApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
async function handleAudit(row, status) {
|
||||
const msg = status === 2 ? '确认通过?' : '确认驳回?'
|
||||
await ElMessageBox.confirm(msg, '审批确认', { type: 'warning' })
|
||||
await purchaseOrderApi.audit(row.id, { status }); ElMessage.success('操作成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.order_no" placeholder="采购单号" style="width:180px" clearable @keydown.enter="handleSearch" />
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option v-for="(v, k) in statusMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增采购单</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width:100%">
|
||||
<el-table-column prop="order_no" label="采购单号" min-width="160" />
|
||||
<el-table-column label="供应商" min-width="140">
|
||||
<template #default="{ row }">{{ row.supplier?.name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_amount" label="总金额" min-width="110" align="right" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusType[row.status]" size="small">{{ statusMap[row.status] }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" min-width="160" />
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status===0" size="small" type="success" link @click="handleAudit(row, 2)">审批通过</el-button>
|
||||
<el-button v-if="row.status===0" size="small" type="warning" link @click="handleAudit(row, 4)">驳回</el-button>
|
||||
<el-button v-if="row.status<2" size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.status<2" size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[10,20,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑采购单' : '新增采购单'" width="550px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="采购单号" prop="order_no">
|
||||
<el-input v-model="form.order_no" />
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商ID">
|
||||
<el-input v-model="form.supplier_id" placeholder="供应商ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总金额">
|
||||
<el-input-number v-model="form.total_amount" :min="0" :precision="2" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,170 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { stockMovementApi, warehouseApi } from '@/api/inventory.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const warehouses = ref([])
|
||||
|
||||
const searchForm = reactive({ warehouse_id: '', type: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 20 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({
|
||||
id: null, warehouse_id: '', movement_no: '', type: '', direction: '', related_order: '', remark: ''
|
||||
})
|
||||
const formRules = {
|
||||
warehouse_id: [{ required: true, message: '请选择仓库', trigger: 'change' }],
|
||||
movement_no: [{ required: true, message: '请输入单据编号', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
|
||||
direction: [{ required: true, message: '请选择方向', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const typeMap = { 1: '采购入库', 2: '领用出库', 3: '退货入库', 4: '报损出库' }
|
||||
const directionMap = { 1: '入库', 2: '出库' }
|
||||
const directionType = { 1: 'success', 2: 'danger' }
|
||||
const statusMap = { 0: '待确认', 1: '已确认' }
|
||||
const statusType = { 0: 'warning', 1: 'success' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await stockMovementApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function fetchWarehouses() {
|
||||
try {
|
||||
const res = await warehouseApi.getList({ per_page: 500 })
|
||||
warehouses.value = res.data?.list || res.data?.data || []
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { warehouse_id: '', type: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() {
|
||||
Object.assign(form, { id: null, warehouse_id: '', movement_no: '', type: '', direction: '', related_order: '', remark: '' })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
await stockMovementApi.create(form)
|
||||
ElMessage.success('创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleConfirm(row) {
|
||||
await ElMessageBox.confirm('确认该出入库单?确认后将更新库存', '确认操作', { type: 'warning' })
|
||||
await stockMovementApi.confirm(row.id); ElMessage.success('确认成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
|
||||
onMounted(() => { fetchList(); fetchWarehouses() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-select v-model="searchForm.warehouse_id" placeholder="选择仓库" clearable filterable style="width:180px">
|
||||
<el-option v-for="w in warehouses" :key="w.id" :label="w.name" :value="w.id" />
|
||||
</el-select>
|
||||
<el-select v-model="searchForm.type" placeholder="类型" clearable style="width:140px">
|
||||
<el-option v-for="(v, k) in typeMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option v-for="(v, k) in statusMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增出入库</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width:100%">
|
||||
<el-table-column prop="movement_no" label="单据编号" min-width="160" />
|
||||
<el-table-column label="仓库" min-width="120">
|
||||
<template #default="{ row }">{{ row.warehouse?.name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="110" align="center">
|
||||
<template #default="{ row }"><el-tag size="small">{{ typeMap[row.type] || '-' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="方向" width="90" align="center">
|
||||
<template #default="{ row }"><el-tag :type="directionType[row.direction]" size="small">{{ directionMap[row.direction] || '-' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }"><el-tag :type="statusType[row.status]" size="small">{{ statusMap[row.status] }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" min-width="160" />
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status === 0" size="small" type="success" link @click="handleConfirm(row)">确认</el-button>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[10, 20, 50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="新增出入库" width="600px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="仓库" prop="warehouse_id">
|
||||
<el-select v-model="form.warehouse_id" placeholder="选择仓库" filterable style="width:100%">
|
||||
<el-option v-for="w in warehouses" :key="w.id" :label="w.name" :value="w.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单据编号" prop="movement_no">
|
||||
<el-input v-model="form.movement_no" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="form.type" placeholder="选择类型" style="width:100%">
|
||||
<el-option v-for="(v, k) in typeMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="方向" prop="direction">
|
||||
<el-select v-model="form.direction" placeholder="选择方向" style="width:100%">
|
||||
<el-option v-for="(v, k) in directionMap" :key="k" :label="v" :value="Number(k)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联单号">
|
||||
<el-input v-model="form.related_order" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { supplierApi } from '@/api/inventory.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ name: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, name: '', contact_person: '', contact_phone: '', address: '', bank_name: '', bank_account: '', status: 1, remark: '', sort: 0 })
|
||||
const isEdit = ref(false)
|
||||
const formRules = { name: [{ required: true, message: '请输入供应商名称', trigger: 'blur' }] }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await supplierApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { name: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
Object.assign(form, { id: null, name: '', contact_person: '', contact_phone: '', address: '', bank_name: '', bank_account: '', status: 1, remark: '', sort: 0 })
|
||||
dialogVisible.value = true
|
||||
}
|
||||
async function handleEdit(row) { isEdit.value = true; Object.assign(form, { ...row }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await supplierApi.update(form.id, form); ElMessage.success('更新成功') }
|
||||
else { await supplierApi.create(form); ElMessage.success('创建成功') }
|
||||
dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除供应商「${row.name}」吗?`, '删除确认', { type: 'warning' })
|
||||
await supplierApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.name" placeholder="供应商名称" style="width:180px" clearable @keydown.enter="handleSearch" />
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增供应商</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width:100%">
|
||||
<el-table-column prop="name" label="供应商名称" min-width="150" />
|
||||
<el-table-column prop="contact_person" label="联系人" width="100" />
|
||||
<el-table-column prop="contact_phone" label="联系电话" width="130" />
|
||||
<el-table-column prop="address" label="地址" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="80" align="center"><template #default="{ row }"><el-tag :type="row.status?'success':'danger'" size="small">{{ row.status?'启用':'停用' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[10,15,20,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑供应商':'新增供应商'" width="600px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
|
||||
<el-form-item label="供应商名称" prop="name"><el-input v-model="form.name" placeholder="请输入供应商名称" /></el-form-item>
|
||||
<el-form-item label="联系人"><el-input v-model="form.contact_person" placeholder="请输入联系人" /></el-form-item>
|
||||
<el-form-item label="联系电话"><el-input v-model="form.contact_phone" placeholder="请输入联系电话" /></el-form-item>
|
||||
<el-form-item label="地址"><el-input v-model="form.address" placeholder="请输入地址" /></el-form-item>
|
||||
<el-form-item label="开户银行"><el-input v-model="form.bank_name" placeholder="请输入开户银行" /></el-form-item>
|
||||
<el-form-item label="银行账号"><el-input v-model="form.bank_account" placeholder="请输入银行账号" /></el-form-item>
|
||||
<el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" /></el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sort" :min="0" :max="999" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,270 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { warehouseApi } from '@/api/inventory.js'
|
||||
|
||||
// ─── State ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('新增仓库')
|
||||
const submitLoading = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref(null)
|
||||
|
||||
const searchForm = reactive({ name: '', status: '' })
|
||||
const pagination = reactive({ page: 1, page_size: 20 })
|
||||
|
||||
// ─── Form ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
const form = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
address: '',
|
||||
contact_person: '',
|
||||
contact_phone: '',
|
||||
status: 1,
|
||||
sort: 0
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入仓库名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// ─── Methods ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await warehouseApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || []
|
||||
total.value = res.data?.total || 0
|
||||
} catch {
|
||||
//
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
Object.assign(searchForm, { name: '', status: '' })
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
dialogTitle.value = '新增仓库'
|
||||
Object.assign(form, {
|
||||
id: null, name: '', address: '', contact_person: '',
|
||||
contact_phone: '', status: 1, sort: 0
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleEdit(row) {
|
||||
isEdit.value = true
|
||||
dialogTitle.value = `编辑仓库 - ${row.name}`
|
||||
const res = await warehouseApi.getDetail(row.id)
|
||||
const warehouse = res.data
|
||||
Object.assign(form, {
|
||||
id: warehouse.id,
|
||||
name: warehouse.name,
|
||||
address: warehouse.address || '',
|
||||
contact_person: warehouse.contact_person || '',
|
||||
contact_phone: warehouse.contact_phone || '',
|
||||
status: warehouse.status,
|
||||
sort: warehouse.sort || 0
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await warehouseApi.update(form.id, form)
|
||||
ElMessage.success('仓库信息更新成功')
|
||||
} else {
|
||||
await warehouseApi.create(form)
|
||||
ElMessage.success('仓库创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} catch {
|
||||
//
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除仓库「${row.name}」吗?删除后该仓库下的数据将不可访问。`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'el-button--danger'
|
||||
}
|
||||
)
|
||||
await warehouseApi.delete(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
}
|
||||
|
||||
async function handleStatusToggle(row) {
|
||||
const newStatus = row.status === 1 ? 0 : 1
|
||||
const label = newStatus === 1 ? '启用' : '停用'
|
||||
try {
|
||||
await warehouseApi.update(row.id, { ...row, status: newStatus })
|
||||
row.status = newStatus
|
||||
ElMessage.success(`仓库已${label}`)
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(val) {
|
||||
pagination.page = val
|
||||
fetchList()
|
||||
}
|
||||
|
||||
function handleSizeChange(val) {
|
||||
pagination.page_size = val
|
||||
pagination.page = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- Search -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchForm.name"
|
||||
placeholder="搜索仓库名称"
|
||||
style="width: 220px"
|
||||
clearable
|
||||
@keydown.enter="handleSearch"
|
||||
>
|
||||
<template #prefix><el-icon><Search /></el-icon></template>
|
||||
</el-input>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width: 120px">
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
<el-button type="primary" :icon="'Search'" @click="handleSearch">查询</el-button>
|
||||
<el-button :icon="'Refresh'" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-container">
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" :icon="'Plus'" @click="handleAdd">新增仓库</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width: 100%">
|
||||
<el-table-column prop="name" label="仓库名称" min-width="160" />
|
||||
<el-table-column prop="address" label="地址" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="contact_person" label="联系人" width="120" />
|
||||
<el-table-column prop="contact_phone" label="联系电话" width="140" />
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
||||
{{ row.status === 1 ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
:type="row.status === 1 ? 'warning' : 'success'"
|
||||
link
|
||||
@click="handleStatusToggle(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.page_size"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
background
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="仓库名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入仓库名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人" prop="contact_person">
|
||||
<el-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="form.address" placeholder="请输入仓库地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="contact_phone">
|
||||
<el-input v-model="form.contact_phone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" :min="0" :max="9999" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
{{ isEdit ? '保存修改' : '确认创建' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { nannyApi } from '@/api/nanny.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ name: '', level: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, name: '', phone: '', id_card: '', level: '', specialties: [], experience_years: 0, daily_price: 0, status: 1, avatar: '', remark: '' })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const statusMap = { 1: '待岗', 2: '在岗', 3: '休假', 0: '离职' }
|
||||
const statusType = { 1: 'info', 2: 'success', 3: 'warning', 0: 'danger' }
|
||||
const levelMap = { 1: '初级', 2: '中级', 3: '高级', 4: '金牌' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await nannyApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { name: '', level: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, name: '', phone: '', id_card: '', level: '', specialties: [], experience_years: 0, daily_price: 0, status: 1, avatar: '', remark: '' }); dialogVisible.value = true }
|
||||
function handleEdit(row) { isEdit.value = true; Object.assign(form, { id: row.id, name: row.name, phone: row.phone, id_card: row.id_card || '', level: row.level, specialties: row.specialties || [], experience_years: row.experience_years, daily_price: row.daily_price, status: row.status, avatar: row.avatar || '', remark: row.remark || '' }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await nannyApi.update(form.id, form) } else { await nannyApi.create(form) }
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除月嫂「${row.name}」吗?`, '删除确认', { type: 'warning' })
|
||||
await nannyApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.name" placeholder="姓名" style="width:150px" clearable />
|
||||
<el-select v-model="searchForm.level" placeholder="等级" clearable style="width:120px"><el-option v-for="(v,k) in levelMap" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option v-for="(v,k) in statusMap" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增月嫂</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="name" label="姓名" width="100" />
|
||||
<el-table-column prop="phone" label="手机号" width="130" />
|
||||
<el-table-column label="等级" width="80" align="center"><template #default="{ row }">{{ levelMap[row.level] || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="experience_years" label="经验(年)" width="90" align="center" />
|
||||
<el-table-column prop="daily_price" label="日价(元)" width="100" align="right" />
|
||||
<el-table-column label="状态" width="80" align="center"><template #default="{ row }"><el-tag :type="statusType[row.status]" size="small">{{ statusMap[row.status] }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[15,30,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑月嫂':'新增月嫂'" width="550px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="姓名" prop="name"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phone"><el-input v-model="form.phone" /></el-form-item>
|
||||
<el-form-item label="身份证号"><el-input v-model="form.id_card" /></el-form-item>
|
||||
<el-form-item label="等级"><el-select v-model="form.level" style="width:100%"><el-option v-for="(v,k) in levelMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="经验(年)"><el-input-number v-model="form.experience_years" :min="0" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="日价(元)"><el-input-number v-model="form.daily_price" :min="0" :precision="2" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="状态"><el-select v-model="form.status" style="width:100%"><el-option v-for="(v,k) in statusMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,107 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { nannyOrderApi } from '@/api/nanny.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ order_no: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, customer_id: '', nanny_id: '', order_no: '', service_type: '', start_date: '', end_date: '', days: 0, price: 0, remark: '' })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
customer_id: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
||||
order_no: [{ required: true, message: '请输入订单号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const statusMap = { 0: '待匹配', 1: '已匹配', 2: '已签约', 3: '服务中', 4: '已完成', 5: '已取消' }
|
||||
const statusType = { 0: 'info', 1: 'warning', 2: 'primary', 3: 'success', 4: '', 5: 'danger' }
|
||||
const serviceTypeMap = { 1: '住家', 2: '白班' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await nannyOrderApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { order_no: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, customer_id: '', nanny_id: '', order_no: '', service_type: '', start_date: '', end_date: '', days: 0, price: 0, remark: '' }); dialogVisible.value = true }
|
||||
function handleEdit(row) { isEdit.value = true; Object.assign(form, { id: row.id, customer_id: row.customer_id, nanny_id: row.nanny_id || '', order_no: row.order_no, service_type: row.service_type, start_date: row.start_date || '', end_date: row.end_date || '', days: row.days, price: row.price, remark: row.remark || '' }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await nannyOrderApi.update(form.id, form) } else { await nannyOrderApi.create(form) }
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除订单「${row.order_no}」吗?`, '删除确认', { type: 'warning' })
|
||||
await nannyOrderApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.order_no" placeholder="订单号" style="width:180px" clearable />
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option v-for="(v,k) in statusMap" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增订单</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="order_no" label="订单号" min-width="160" />
|
||||
<el-table-column label="客户" min-width="100"><template #default="{ row }">{{ row.customer?.name || '-' }}</template></el-table-column>
|
||||
<el-table-column label="月嫂" width="100"><template #default="{ row }">{{ row.nanny?.name || '待匹配' }}</template></el-table-column>
|
||||
<el-table-column label="服务类型" width="90"><template #default="{ row }">{{ serviceTypeMap[row.service_type] || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="start_date" label="开始日期" width="110" />
|
||||
<el-table-column prop="days" label="天数" width="70" align="center" />
|
||||
<el-table-column prop="price" label="费用(元)" width="100" align="right" />
|
||||
<el-table-column label="状态" width="90" align="center"><template #default="{ row }"><el-tag :type="statusType[row.status]" size="small">{{ statusMap[row.status] }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status<4" size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.status<2" size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[15,30,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑订单':'新增订单'" width="550px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="客户ID" prop="customer_id"><el-input v-model="form.customer_id" /></el-form-item>
|
||||
<el-form-item label="月嫂ID"><el-input v-model="form.nanny_id" /></el-form-item>
|
||||
<el-form-item label="订单号" prop="order_no"><el-input v-model="form.order_no" :disabled="isEdit" /></el-form-item>
|
||||
<el-form-item label="服务类型"><el-select v-model="form.service_type" style="width:100%"><el-option v-for="(v,k) in serviceTypeMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="开始日期"><el-date-picker v-model="form.start_date" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="结束日期"><el-date-picker v-model="form.end_date" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="天数"><el-input-number v-model="form.days" :min="1" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="费用(元)"><el-input-number v-model="form.price" :min="0" :precision="2" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { nannyScheduleApi, nannyApi } from '@/api/nanny.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const nannies = ref([])
|
||||
|
||||
const searchForm = reactive({ nanny_id: '', start_date: '', end_date: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 31 })
|
||||
|
||||
const form = reactive({ nanny_id: '', nanny_order_id: '', schedule_date: '', type: 1, remark: '' })
|
||||
|
||||
const typeMap = { 1: '上班', 2: '休息', 3: '请假' }
|
||||
const typeType = { 1: 'success', 2: 'info', 3: 'warning' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await nannyScheduleApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function fetchNannies() { try { const res = await nannyApi.getList({ per_page: 200, status: 2 }); nannies.value = res.data?.list || res.data?.data || [] } catch {} }
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { nanny_id: '', start_date: '', end_date: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { Object.assign(form, { nanny_id: '', nanny_order_id: '', schedule_date: '', type: 1, remark: '' }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.nanny_id || !form.schedule_date) { ElMessage.warning('请填写必要信息'); return }
|
||||
submitLoading.value = true
|
||||
try {
|
||||
await nannyScheduleApi.create(form)
|
||||
ElMessage.success('排班成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
onMounted(() => { fetchList(); fetchNannies() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-select v-model="searchForm.nanny_id" placeholder="选择月嫂" clearable style="width:160px"><el-option v-for="n in nannies" :key="n.id" :label="n.name" :value="n.id" /></el-select>
|
||||
<el-date-picker v-model="searchForm.start_date" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
||||
<el-date-picker v-model="searchForm.end_date" type="date" value-format="YYYY-MM-DD" placeholder="结束日期" />
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增排班</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column label="月嫂" min-width="100"><template #default="{ row }">{{ row.nanny?.name || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="schedule_date" label="日期" width="120" />
|
||||
<el-table-column label="类型" width="90" align="center"><template #default="{ row }"><el-tag :type="typeType[row.type]" size="small">{{ typeMap[row.type] }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="关联订单" min-width="120"><template #default="{ row }">{{ row.order?.order_no || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" />
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" :total="total" layout="total,prev,pager,next" background @current-change="handlePageChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" title="新增排班" width="450px" destroy-on-close>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="月嫂"><el-select v-model="form.nanny_id" style="width:100%"><el-option v-for="n in nannies" :key="n.id" :label="n.name" :value="n.id" /></el-select></el-form-item>
|
||||
<el-form-item label="日期"><el-date-picker v-model="form.schedule_date" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="类型"><el-select v-model="form.type" style="width:100%"><el-option v-for="(v,k) in typeMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { serviceItemApi } from '@/api/service.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ name: '', category: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, name: '', category: '', price: 0, duration: 0, description: '', status: 1, sort: 0 })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
||||
price: [{ required: true, message: '请输入价格', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const statusMap = { 1: '启用', 0: '停用' }
|
||||
const categoryMap = { 1: '产后修复', 2: '中医调理', 3: '美容美体', 4: '其他' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await serviceItemApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { name: '', category: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, name: '', category: '', price: 0, duration: 0, description: '', status: 1, sort: 0 }); dialogVisible.value = true }
|
||||
function handleEdit(row) { isEdit.value = true; Object.assign(form, { id: row.id, name: row.name, category: row.category, price: row.price, duration: row.duration, description: row.description || '', status: row.status, sort: row.sort }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await serviceItemApi.update(form.id, form) } else { await serviceItemApi.create(form) }
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除「${row.name}」吗?`, '删除确认', { type: 'warning' })
|
||||
await serviceItemApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.name" placeholder="项目名称" style="width:180px" clearable />
|
||||
<el-select v-model="searchForm.category" placeholder="分类" clearable style="width:140px"><el-option v-for="(v,k) in categoryMap" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option label="启用" :value="1" /><el-option label="停用" :value="0" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增项目</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="name" label="项目名称" min-width="150" />
|
||||
<el-table-column label="分类" width="120"><template #default="{ row }">{{ categoryMap[row.category] || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="price" label="价格(元)" width="100" align="right" />
|
||||
<el-table-column prop="duration" label="时长(分)" width="100" align="center" />
|
||||
<el-table-column label="状态" width="80" align="center"><template #default="{ row }"><el-tag :type="row.status===1?'success':'info'" size="small">{{ statusMap[row.status] }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[15,30,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑项目':'新增项目'" width="500px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="项目名称" prop="name"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="分类"><el-select v-model="form.category" style="width:100%"><el-option v-for="(v,k) in categoryMap" :key="k" :label="v" :value="Number(k)" /></el-select></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="时长(分钟)"><el-input-number v-model="form.duration" :min="0" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="描述"><el-input v-model="form.description" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sort" :min="0" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { serviceOrderApi } from '@/api/service.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const searchForm = reactive({ order_no: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, customer_id: '', order_no: '', type: '', total_amount: 0, discount_amount: 0, actual_amount: 0, remark: '' })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
customer_id: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
||||
order_no: [{ required: true, message: '请输入订单号', trigger: 'blur' }],
|
||||
actual_amount: [{ required: true, message: '请输入实付金额', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const statusMap = { 0: '待付款', 1: '已付款', 2: '已完成', 3: '已取消' }
|
||||
const statusType = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
|
||||
const typeMap = { 1: '单项服务', 2: '套餐', 3: '产康', 4: '其他' }
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await serviceOrderApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { order_no: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, customer_id: '', order_no: '', type: '', total_amount: 0, discount_amount: 0, actual_amount: 0, remark: '' }); dialogVisible.value = true }
|
||||
function handleEdit(row) { isEdit.value = true; Object.assign(form, { id: row.id, customer_id: row.customer_id, order_no: row.order_no, type: row.type, total_amount: row.total_amount, discount_amount: row.discount_amount, actual_amount: row.actual_amount, remark: row.remark || '' }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await serviceOrderApi.update(form.id, form) } else { await serviceOrderApi.create(form) }
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除订单「${row.order_no}」吗?`, '删除确认', { type: 'warning' })
|
||||
await serviceOrderApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
async function handlePay(row) {
|
||||
await ElMessageBox.confirm('确认客户已付款?', '付款确认', { type: 'warning' })
|
||||
await serviceOrderApi.pay(row.id, { pay_method: 1 }); ElMessage.success('付款成功'); fetchList()
|
||||
}
|
||||
|
||||
async function handleComplete(row) {
|
||||
await ElMessageBox.confirm('确认订单已完成?', '完成确认', { type: 'warning' })
|
||||
await serviceOrderApi.complete(row.id); ElMessage.success('已完成'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => fetchList())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.order_no" placeholder="订单号" style="width:180px" clearable />
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option v-for="(v,k) in statusMap" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增订单</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="order_no" label="订单号" min-width="160" />
|
||||
<el-table-column label="客户" min-width="120"><template #default="{ row }">{{ row.customer?.name || '-' }}</template></el-table-column>
|
||||
<el-table-column label="类型" width="100"><template #default="{ row }">{{ typeMap[row.type] || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="actual_amount" label="实付(元)" width="100" align="right" />
|
||||
<el-table-column label="状态" width="90" align="center"><template #default="{ row }"><el-tag :type="statusType[row.status]" size="small">{{ statusMap[row.status] }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status===0" size="small" type="success" link @click="handlePay(row)">付款</el-button>
|
||||
<el-button v-if="row.status===1" size="small" type="primary" link @click="handleComplete(row)">完成</el-button>
|
||||
<el-button v-if="row.status<2" size="small" type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button v-if="row.status===0" size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[15,30,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑订单':'新增订单'" width="550px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="客户ID" prop="customer_id"><el-input v-model="form.customer_id" /></el-form-item>
|
||||
<el-form-item label="订单号" prop="order_no"><el-input v-model="form.order_no" :disabled="isEdit" /></el-form-item>
|
||||
<el-form-item label="类型"><el-select v-model="form.type" style="width:100%"><el-option v-for="(v,k) in typeMap" :key="k" :label="v" :value="Number(k)" /></el-select></el-form-item>
|
||||
<el-form-item label="总金额"><el-input-number v-model="form.total_amount" :min="0" :precision="2" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="优惠金额"><el-input-number v-model="form.discount_amount" :min="0" :precision="2" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="实付金额" prop="actual_amount"><el-input-number v-model="form.actual_amount" :min="0" :precision="2" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { servicePackageApi, serviceItemApi } from '@/api/service.js'
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const serviceItems = ref([])
|
||||
|
||||
const searchForm = reactive({ name: '', status: '' })
|
||||
const pagination = reactive({ page: 1, per_page: 15 })
|
||||
|
||||
const formRef = ref(null)
|
||||
const form = reactive({ id: null, name: '', items: [], original_price: 0, price: 0, description: '', status: 1, sort: 0 })
|
||||
const isEdit = ref(false)
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入套餐名称', trigger: 'blur' }],
|
||||
price: [{ required: true, message: '请输入价格', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
async function fetchList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await servicePackageApi.getList({ ...searchForm, ...pagination })
|
||||
tableData.value = res.data?.list || res.data?.data || []
|
||||
total.value = res.data?.total || 0
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function fetchItems() { try { const res = await serviceItemApi.getList({ per_page: 200, status: 1 }); serviceItems.value = res.data?.list || res.data?.data || [] } catch {} }
|
||||
|
||||
function handleSearch() { pagination.page = 1; fetchList() }
|
||||
function handleReset() { Object.assign(searchForm, { name: '', status: '' }); handleSearch() }
|
||||
|
||||
function handleAdd() { isEdit.value = false; Object.assign(form, { id: null, name: '', items: [], original_price: 0, price: 0, description: '', status: 1, sort: 0 }); dialogVisible.value = true }
|
||||
function handleEdit(row) { isEdit.value = true; Object.assign(form, { id: row.id, name: row.name, items: row.items || [], original_price: row.original_price, price: row.price, description: row.description || '', status: row.status, sort: row.sort }); dialogVisible.value = true }
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (isEdit.value) { await servicePackageApi.update(form.id, form) } else { await servicePackageApi.create(form) }
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功'); dialogVisible.value = false; fetchList()
|
||||
} finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除套餐「${row.name}」吗?`, '删除确认', { type: 'warning' })
|
||||
await servicePackageApi.delete(row.id); ElMessage.success('删除成功'); fetchList()
|
||||
}
|
||||
|
||||
function handlePageChange(val) { pagination.page = val; fetchList() }
|
||||
function handleSizeChange(val) { pagination.per_page = val; pagination.page = 1; fetchList() }
|
||||
onMounted(() => { fetchList(); fetchItems() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="search-bar">
|
||||
<el-input v-model="searchForm.name" placeholder="套餐名称" style="width:180px" clearable />
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width:120px"><el-option label="启用" :value="1" /><el-option label="停用" :value="0" /></el-select>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-actions"><el-button type="primary" @click="handleAdd">新增套餐</el-button></div>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="name" label="套餐名称" min-width="180" />
|
||||
<el-table-column prop="original_price" label="原价(元)" width="100" align="right" />
|
||||
<el-table-column prop="price" label="套餐价(元)" width="110" align="right" />
|
||||
<el-table-column label="包含项目" min-width="200"><template #default="{ row }">{{ Array.isArray(row.items) ? row.items.length + '个项目' : '-' }}</template></el-table-column>
|
||||
<el-table-column label="状态" width="80" align="center"><template #default="{ row }"><el-tag :type="row.status===1?'success':'info'" size="small">{{ row.status===1?'启用':'停用' }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" type="primary" link @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" link @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page" :total="total" :page-sizes="[15,30,50]" layout="total,sizes,prev,pager,next,jumper" background @current-change="handlePageChange" @size-change="handleSizeChange" />
|
||||
</div>
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit?'编辑套餐':'新增套餐'" width="600px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="formRules" label-width="90px">
|
||||
<el-form-item label="套餐名称" prop="name"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="原价"><el-input-number v-model="form.original_price" :min="0" :precision="2" style="width:100%" /></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="描述"><el-input v-model="form.description" type="textarea" :rows="3" /></el-form-item>
|
||||
<el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<el-form-item label="排序"><el-input-number v-model="form.sort" :min="0" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible=false">取消</el-button><el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user