Files
tiantian-system/app/pages/admin/supply/purchase.vue
赵忠林 f9e1286ab1 refactor(developer-config): 移除开发者配置页面相关代码和文档
- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue
- 移除开发者文档页面及其导航与样式实现
- 清理开发者侧功能完善工作日志文件
- 删除全局.gitignore配置文件,清理无用忽略规则
- 优化应用配置页面的参数读取和路由结构,解决刷新404问题
- 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入
- 移除对后端配置加密字段的 secret 标记,修正加密异常问题
2026-04-09 07:35:34 +08:00

466 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
definePageMeta({ layout: 'admin' })
// 采购统计
const purchaseStats = ref([
{ label: '采购单总数', value: 156, icon: 'fa-file-alt', gradient: 'from-blue-500 to-purple-500', change: '+23%', up: true },
{ label: '待审批', value: 12, icon: 'fa-clock', gradient: 'from-orange-500 to-yellow-500', change: '+3', up: false },
{ label: '已完成', value: 138, icon: 'fa-check-circle', gradient: 'from-green-500 to-teal-500', change: '+18%', up: true },
{ label: '采购总额', value: '89.5', unit: '万', icon: 'fa-wallet', gradient: 'from-pink-500 to-rose-500', change: '+15%', up: true },
])
// 采购单列表
const purchaseOrders = ref([
{ id: 'PO-2026040901', supplier: '深圳市精密机械有限公司', material: '轴承组件 A型', quantity: 500, unit: '套', amount: 25000, status: 'pending', applicant: '张经理', date: '2026-04-09' },
{ id: 'PO-2026040802', supplier: '上海五金工具厂', material: '数控刀具套装', quantity: 20, unit: '套', amount: 36000, status: 'approved', applicant: '李主管', date: '2026-04-08' },
{ id: 'PO-2026040801', supplier: '东莞市金属材料公司', material: '铝合金板材', quantity: 200, unit: '张', amount: 48000, status: 'processing', applicant: '王经理', date: '2026-04-08' },
{ id: 'PO-2026040703', supplier: '苏州液压设备厂', material: '液压缸体 B型', quantity: 50, unit: '件', amount: 75000, status: 'completed', applicant: '赵主管', date: '2026-04-07' },
{ id: 'PO-2026040702', supplier: '广州润滑油脂公司', material: '工业润滑油', quantity: 100, unit: '桶', amount: 15000, status: 'completed', applicant: '张经理', date: '2026-04-07' },
{ id: 'PO-2026040601', supplier: '深圳市精密机械有限公司', material: '密封圈组件', quantity: 1000, unit: '个', amount: 8000, status: 'completed', applicant: '李主管', date: '2026-04-06' },
])
const statusMap: Record<string, { label: string; color: string; bg: string }> = {
pending: { label: '待审批', color: 'text-orange-600', bg: 'bg-orange-100' },
approved: { label: '已审批', color: 'text-blue-600', bg: 'bg-blue-100' },
processing: { label: '执行中', color: 'text-purple-600', bg: 'bg-purple-100' },
completed: { label: '已完成', color: 'text-green-600', bg: 'bg-green-100' },
rejected: { label: '已驳回', color: 'text-red-600', bg: 'bg-red-100' },
}
const statusFilter = ref('all')
const searchKeyword = ref('')
const filteredList = computed(() => {
return purchaseOrders.value.filter((item) => {
const matchStatus = statusFilter.value === 'all' || item.status === statusFilter.value
const matchSearch = !searchKeyword.value ||
item.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.supplier.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
item.material.toLowerCase().includes(searchKeyword.value.toLowerCase())
return matchStatus && matchSearch
})
})
// 供应商列表
const suppliers = ref([
{ name: '深圳市精密机械有限公司', contact: '陈经理', phone: '138****1234', items: 45, total: '120万', rating: 4.8 },
{ name: '上海五金工具厂', contact: '李总', phone: '139****5678', items: 32, total: '85万', rating: 4.6 },
{ name: '东莞市金属材料公司', contact: '王经理', phone: '137****9012', items: 28, total: '200万', rating: 4.9 },
{ name: '苏州液压设备厂', contact: '张工', phone: '136****3456', items: 15, total: '95万', rating: 4.7 },
])
const addFormVisible = ref(false)
const addForm = reactive({
supplier: '',
material: '',
quantity: '',
unit: '',
estimatedAmount: '',
deliveryDate: '',
remark: '',
})
function handleAdd() {
addFormVisible.value = false
message.success('采购单创建成功')
}
</script>
<template>
<div class="purchase-page">
<!-- 页面标题 -->
<div class="page-header mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-800">采购管理</h2>
<p class="text-gray-500 mt-1">管理采购订单跟踪供应商履约情况</p>
</div>
<a-button type="primary" @click="addFormVisible = true">
<template #icon><i class="fas fa-plus mr-1"></i></template>
新建采购单
</a-button>
</div>
<!-- 统计卡片 -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="stat in purchaseStats"
:key="stat.label"
class="glass rounded-2xl p-6 card-hover cursor-pointer"
>
<div class="flex items-center justify-between mb-4">
<div
class="w-12 h-12 rounded-xl flex items-center justify-center text-white"
:class="`bg-gradient-to-br ${stat.gradient}`"
>
<i :class="`fas ${stat.icon}`"></i>
</div>
<span
class="text-sm font-medium"
:class="stat.up ? 'text-green-500' : 'text-red-500'"
>
<i :class="stat.up ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ stat.change }}
</span>
</div>
<h3 class="text-3xl font-bold text-gray-800 mb-1">
{{ stat.value }}<span v-if="stat.unit" class="text-base text-gray-500 ml-1">{{ stat.unit }}</span>
</h3>
<p class="text-gray-500 text-sm">{{ stat.label }}</p>
</div>
</div>
<!-- 筛选栏 -->
<div class="glass rounded-2xl p-4 mb-6">
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-3">
<span class="text-gray-600 font-medium">状态筛选</span>
<a-radio-group v-model:value="statusFilter" button-style="solid">
<a-radio-button value="all">全部</a-radio-button>
<a-radio-button value="pending">待审批</a-radio-button>
<a-radio-button value="approved">已审批</a-radio-button>
<a-radio-button value="processing">执行中</a-radio-button>
<a-radio-button value="completed">已完成</a-radio-button>
</a-radio-group>
</div>
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索采购单号、供应商、物料..."
style="width: 300px"
allow-clear
/>
</div>
</div>
<!-- 采购单列表 -->
<div class="glass rounded-2xl p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-file-alt text-blue-500 mr-2"></i>
采购单列表
</h3>
<span class="text-sm text-gray-500"> {{ filteredList.length }} 条记录</span>
</div>
<a-table
:dataSource="filteredList"
:pagination="{ pageSize: 10 }"
rowKey="id"
:scroll="{ x: 1200 }"
>
<a-table-column title="采购单号" dataIndex="id" width="140" />
<a-table-column title="供应商" dataIndex="supplier" width="200">
<template #default="{ text }">
<span class="font-medium">{{ text }}</span>
</template>
</a-table-column>
<a-table-column title="采购物料" dataIndex="material" width="150" />
<a-table-column title="数量" dataIndex="quantity" width="100" align="center">
<template #default="{ record }">
{{ record.quantity }} {{ record.unit }}
</template>
</a-table-column>
<a-table-column title="金额(元)" dataIndex="amount" width="120" align="right">
<template #default="{ text }">
<span class="font-medium text-orange-600">¥{{ text.toLocaleString() }}</span>
</template>
</a-table-column>
<a-table-column title="状态" dataIndex="status" width="100" align="center">
<template #default="{ text }">
<span
:class="statusMap[text]?.color"
class="px-2 py-1 rounded-lg text-sm font-medium"
:class="statusMap[text]?.bg"
>
{{ statusMap[text]?.label }}
</span>
</template>
</a-table-column>
<a-table-column title="申请人" dataIndex="applicant" width="100" align="center" />
<a-table-column title="申请日期" dataIndex="date" width="120" />
<a-table-column title="操作" width="160" align="center" fixed="right">
<template #default>
<div class="flex items-center gap-2 justify-center">
<a-button type="link" size="small" title="查看详情">
<i class="fas fa-eye"></i>
</a-button>
<a-button type="link" size="small" title="审批" v-if="statusFilter === 'pending'">
<i class="fas fa-check"></i>
</a-button>
<a-button type="link" size="small" title="编辑">
<i class="fas fa-edit"></i>
</a-button>
</div>
</template>
</a-table-column>
</a-table>
</div>
<!-- 供应商管理 -->
<div class="glass rounded-2xl p-6 mt-6">
<div class="flex items-center justify-between mb-4">
<h3 class="font-bold text-lg text-gray-800">
<i class="fas fa-building text-green-500 mr-2"></i>
供应商列表
</h3>
<a-button type="link">
<i class="fas fa-plus mr-1"></i>添加供应商
</a-button>
</div>
<a-row :gutter="[16, 16]">
<a-col :xs="24" :sm="12" :lg="6" v-for="supplier in suppliers" :key="supplier.name">
<div class="supplier-card glass rounded-xl p-4 card-hover">
<div class="flex items-start justify-between mb-3">
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center text-white">
<i class="fas fa-building"></i>
</div>
<a-rate :default-value="supplier.rating" disabled size="small" />
</div>
<h4 class="font-bold text-gray-800 mb-1">{{ supplier.name }}</h4>
<p class="text-sm text-gray-500 mb-2">{{ supplier.contact }} | {{ supplier.phone }}</p>
<div class="flex justify-between text-sm">
<span class="text-gray-500">合作物料<span class="text-blue-600 font-medium">{{ supplier.items }}</span> </span>
<span class="text-gray-500">累计<span class="text-green-600 font-medium">{{ supplier.total }}</span></span>
</div>
</div>
</a-col>
</a-row>
</div>
<!-- 新建采购单弹窗 -->
<a-modal
v-model:open="addFormVisible"
title="新建采购单"
@ok="handleAdd"
ok-text="确认创建"
cancel-text="取消"
width="600px"
>
<a-form :model="addForm" layout="vertical">
<a-form-item label="供应商" required>
<a-select v-model:value="addForm.supplier" placeholder="请选择供应商">
<a-select-option v-for="s in suppliers" :key="s.name" :value="s.name">{{ s.name }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="采购物料" required>
<a-input v-model:value="addForm.material" placeholder="请输入物料名称" />
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="数量" required>
<a-input-number v-model:value="addForm.quantity" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="单位">
<a-input v-model:value="addForm.unit" placeholder="如:套、件、个" />
</a-form-item>
</a-col>
</a-row>
<a-form-item label="预估金额(元)">
<a-input-number v-model:value="addForm.estimatedAmount" style="width: 100%" :min="0" />
</a-form-item>
<a-form-item label="期望交货日期">
<a-date-picker v-model:value="addForm.deliveryDate" style="width: 100%" />
</a-form-item>
<a-form-item label="备注">
<a-textarea v-model:value="addForm.remark" :rows="3" placeholder="请输入备注信息" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<style scoped>
.purchase-page {
padding: 0;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.glass {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.grid {
display: grid;
}
.grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 1200px) {
.grid-cols-4 {
grid-template-columns: repeat(2, 1fr);
}
}
.mb-6 {
margin-bottom: 24px;
}
.mb-4 {
margin-bottom: 16px;
}
.mb-3 {
margin-bottom: 12px;
}
.mb-2 {
margin-bottom: 8px;
}
.mb-1 {
margin-bottom: 4px;
}
.mt-1 {
margin-top: 4px;
}
.mt-6 {
margin-top: 24px;
}
.ml-1 {
margin-left: 4px;
}
.mr-2 {
margin-right: 8px;
}
.mr-1 {
margin-right: 4px;
}
.text-2xl {
font-size: 24px;
}
.text-3xl {
font-size: 30px;
}
.text-lg {
font-size: 18px;
}
.text-sm {
font-size: 14px;
}
.text-xs {
font-size: 12px;
}
.text-base {
font-size: 16px;
}
.rounded-2xl {
border-radius: 16px;
}
.rounded-xl {
border-radius: 12px;
}
.p-6 {
padding: 24px;
}
.p-4 {
padding: 16px;
}
.gap-6 {
gap: 24px;
}
.gap-4 {
gap: 16px;
}
.gap-3 {
gap: 12px;
}
.gap-2 {
gap: 8px;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.items-start {
align-items: flex-start;
}
.justify-between {
justify-content: space-between;
}
.justify-end {
justify-content: flex-end;
}
.font-bold {
font-weight: 700;
}
.font-medium {
font-weight: 500;
}
.text-gray-800 {
color: #1f2937;
}
.text-gray-600 {
color: #4b5563;
}
.text-gray-500 {
color: #6b7280;
}
.text-orange-600 {
color: #ea580c;
}
.text-blue-600 {
color: #2563eb;
}
.text-green-600 {
color: #16a34a;
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
</style>