添加后端基础架构和API功能

- 配置.env文件支持环境变量管理
- 集成MyBatis Plus ORM框架并配置数据库连接
- 实现JWT认证和Spring Security安全配置
- 添加全局异常处理机制和统一响应结果封装
- 配置CORS跨域资源共享支持
- 实现用户认证API包括登录、注册、刷新token功能
- 开发完整的药品管理系统包含分类、药品、库存管理
- 实现出入库管理功能包括供应商、客户管理
- 添加角色权限管理和菜单配置
- 实现仪表盘统计和过期预警功能
- 配置分页查询和数据验证功能
```
This commit is contained in:
2026-07-06 20:04:44 +08:00
parent 15da4fe492
commit 258bae75b2
102 changed files with 3054 additions and 128 deletions

View File

@@ -1,23 +1,54 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { getStockOutList, auditStockOut } from '@/api/stockOut'
import { getStockOutList, getStockOutById, auditStockOut } from '@/api/stockOut'
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const query = reactive({ page: 1, size: 10 })
const query = reactive({ page: 1, size: 10, recordNo: '' })
const tableData = ref([])
const total = ref(0)
const loading = ref(false)
const detailVisible = ref(false)
const detailRecord = ref({})
const detailItems = ref([])
function statusTag(s) { return { 0: 'warning', 1: 'success', 2: 'info' }[s] || 'info' }
function statusText(s) { return { 0: '待审核', 1: '出库', 2: '已取消' }[s] || '未知' }
function typeText(s) { return { 1: '销售出库', 2: '领用出库', 3: '报损出库', 4: '退货出库' }[s] || '未知' }
const statusMap = { 0: { type: 'warning', text: '待审核' }, 1: { type: 'success', text: '已出库' }, 2: { type: 'info', text: '已取消' } }
const typeMap = { 1: '销售出库', 2: '领用出库', 3: '报损出库', 4: '退货出库' }
async function loadData() {
loading.value = true
try {
const r = await getStockOutList(query)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
} finally { loading.value = false }
}
async function loadData() { try { const r = await getStockOutList(query); tableData.value = r.data?.records || []; total.value = r.data?.total || 0 } catch { /* */ } }
function handleCreate() { router.push('/stock-out/add') }
function handleView(row) { ElMessage.info('出库单号: ' + (row.recordNo || row.record_no)) }
async function handleAudit(row) { await auditStockOut(row.id, { status: 1 }); ElMessage.success('审核通过,库存已扣减'); loadData() }
async function handleView(row) {
try {
const r = await getStockOutById(row.id)
detailRecord.value = r.data || row
detailItems.value = r.data?.details || []
detailVisible.value = true
} catch { ElMessage.error('获取详情失败') }
}
async function handleAudit(row) {
await ElMessageBox.confirm(`确认审核通过出库单 ${row.recordNo}?审核后库存将自动扣减。`, '审核确认', { type: 'success' })
await auditStockOut(row.id, { status: 1 })
ElMessage.success('审核通过,库存已扣减')
loadData()
}
async function handleReject(row) {
await ElMessageBox.confirm(`确认驳回出库单 ${row.recordNo}`, '驳回确认', { type: 'warning' })
await auditStockOut(row.id, { status: 2 })
ElMessage.success('已驳回')
loadData()
}
onMounted(loadData)
</script>
@@ -25,31 +56,75 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="search-bar">
<el-input v-model="query.recordNo" placeholder="出库单号" clearable style="width:180px" />
<el-input v-model="query.recordNo" placeholder="出库单号" clearable style="width:200px" @keyup.enter="loadData" />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>
<el-button :icon="Refresh" @click="Object.keys(query).forEach(k=>query[k]=k==='page'?1:k==='size'?10:'');loadData()">重置</el-button>
<el-button :icon="Refresh" @click="query.recordNo='';loadData()">重置</el-button>
</div>
<div class="tool-bar">
<el-button type="primary" v-permission="'stock-out:add'" :icon="Plus" @click="handleCreate">+ 新建出库单</el-button>
<el-button type="primary" v-permission="'stock-out:add'" :icon="Plus" @click="handleCreate">新建出库单</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="recordNo" label="出库单号" width="150" />
<el-table-column prop="customerName" label="客户" min-width="160" />
<el-table-column prop="outType" label="类型" width="100"><template #default="{row}">{{typeText(row.outType)}}</template></el-table-column>
<el-table-column prop="totalAmount" label="总金额" width="110"><template #default="{row}">¥{{row.totalAmount}}</template></el-table-column>
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="recordNo" label="出库单号" width="160" />
<el-table-column prop="customerName" label="客户" min-width="160">
<template #default="{ row }">{{ row.customerName || '-' }}</template>
</el-table-column>
<el-table-column prop="outType" label="类型" width="110">
<template #default="{ row }">{{ typeMap[row.outType] || '未知' }}</template>
</el-table-column>
<el-table-column prop="totalAmount" label="总金额" width="120">
<template #default="{ row }">¥{{ row.totalAmount }}</template>
</el-table-column>
<el-table-column prop="outDate" label="出库日期" width="120" />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{row}"><el-tag :type="statusTag(row.status)" size="small">{{statusText(row.status)}}</el-tag></template>
<template #default="{ row }">
<el-tag :type="statusMap[row.status]?.type || 'info'" size="small">
{{ statusMap[row.status]?.text || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{row}">
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<div class="table-actions">
<el-button size="small" @click="handleView(row)">查看</el-button>
<el-button v-if="row.status===0" size="small" type="success" v-permission="'stock-out:audit'" @click="handleAudit(row)">审核</el-button>
<template v-if="row.status === 0">
<el-button size="small" type="success" v-permission="'stock-out:audit'" @click="handleAudit(row)">通过</el-button>
<el-button size="small" type="danger" @click="handleReject(row)">驳回</el-button>
</template>
</div>
</template>
</el-table-column>
</el-table>
<div style="display:flex;justify-content:flex-end;margin-top:16px"><el-pagination v-model:current-page="query.page" v-model:page-size="query.size" :total="total" :page-sizes="[10,20,50]" layout="total,sizes,prev,pager,next" @current-change="loadData" @size-change="query.page=1;loadData()" /></div>
<div style="display:flex;justify-content:flex-end;margin-top:16px">
<el-pagination
v-model:current-page="query.page" v-model:page-size="query.size"
:total="total" :page-sizes="[10,20,50]"
layout="total,sizes,prev,pager,next"
@current-change="loadData" @size-change="query.page=1;loadData()"
/>
</div>
<el-dialog v-model="detailVisible" title="出库单详情" width="750px">
<el-descriptions :column="3" border>
<el-descriptions-item label="出库单号">{{ detailRecord.recordNo }}</el-descriptions-item>
<el-descriptions-item label="客户">{{ detailRecord.customerName || '-' }}</el-descriptions-item>
<el-descriptions-item label="出库日期">{{ detailRecord.outDate }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ typeMap[detailRecord.outType] }}</el-descriptions-item>
<el-descriptions-item label="总金额">¥{{ detailRecord.totalAmount }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusMap[detailRecord.status]?.type" size="small">{{ statusMap[detailRecord.status]?.text }}</el-tag>
</el-descriptions-item>
</el-descriptions>
<h4 style="margin:16px 0 8px">药品明细</h4>
<el-table :data="detailItems" border size="small" max-height="300">
<el-table-column prop="medicineName" label="药品" min-width="160" />
<el-table-column prop="batchNo" label="批号" width="120" />
<el-table-column prop="quantity" label="数量" width="80" />
<el-table-column prop="unitPrice" label="单价" width="90"><template #default="{row}">¥{{row.unitPrice}}</template></el-table-column>
<el-table-column prop="totalPrice" label="小计" width="100"><template #default="{row}">¥{{row.totalPrice}}</template></el-table-column>
</el-table>
</el-dialog>
</div>
</template>