新增CSS动画效果和交互样式优化
This commit is contained in:
@@ -12,23 +12,89 @@
|
||||
"navigationBarTitleText": "订单监控"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/exception-handling",
|
||||
"style": {
|
||||
"navigationBarTitleText": "异常处理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/purchase-plan",
|
||||
"style": {
|
||||
"navigationBarTitleText": "采购计划"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/quality-inspection",
|
||||
"style": {
|
||||
"navigationBarTitleText": "质检管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/contract-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "合同管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/payment-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "支付管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/transport/transport-monitor",
|
||||
"style": {
|
||||
"navigationBarTitleText": "运输监控"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/transport/transport-track",
|
||||
"style": {
|
||||
"navigationBarTitleText": "运输轨迹"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/statistics/data-dashboard",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据统计"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/statistics/data-reports",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据报表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/system-settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "系统设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/user-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/supplier-management",
|
||||
"style": {
|
||||
"navigationBarTitleText": "供应商管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/system/message-center",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息中心"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -13,11 +13,56 @@ const quickActions = ref([
|
||||
title: '运输监控',
|
||||
path: '/pages/transport/transport-monitor'
|
||||
},
|
||||
{
|
||||
icon: 'exception',
|
||||
title: '异常处理',
|
||||
path: '/pages/order/exception-handling'
|
||||
},
|
||||
{
|
||||
icon: 'purchase',
|
||||
title: '采购计划',
|
||||
path: '/pages/order/purchase-plan'
|
||||
},
|
||||
{
|
||||
icon: 'quality',
|
||||
title: '质检管理',
|
||||
path: '/pages/order/quality-inspection'
|
||||
},
|
||||
{
|
||||
icon: 'contract',
|
||||
title: '合同管理',
|
||||
path: '/pages/order/contract-management'
|
||||
},
|
||||
{
|
||||
icon: 'payment',
|
||||
title: '支付管理',
|
||||
path: '/pages/order/payment-management'
|
||||
},
|
||||
{
|
||||
icon: 'statistics',
|
||||
title: '数据统计',
|
||||
path: '/pages/statistics/data-dashboard'
|
||||
},
|
||||
{
|
||||
icon: 'reports',
|
||||
title: '数据报表',
|
||||
path: '/pages/statistics/data-reports'
|
||||
},
|
||||
{
|
||||
icon: 'users',
|
||||
title: '用户管理',
|
||||
path: '/pages/system/user-management'
|
||||
},
|
||||
{
|
||||
icon: 'suppliers',
|
||||
title: '供应商管理',
|
||||
path: '/pages/system/supplier-management'
|
||||
},
|
||||
{
|
||||
icon: 'message',
|
||||
title: '消息中心',
|
||||
path: '/pages/system/message-center'
|
||||
},
|
||||
{
|
||||
icon: 'settings',
|
||||
title: '系统设置',
|
||||
@@ -68,6 +113,10 @@ const navigateTo = (path: string) => {
|
||||
<text class="recent-text">订单 #20249999 已完成</text>
|
||||
<text class="recent-time">1小时前</text>
|
||||
</view>
|
||||
<view class="recent-item">
|
||||
<text class="recent-text">供应商 某某牛场 资质即将到期</text>
|
||||
<text class="recent-time">2小时前</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
444
mini_program/staff-mp/src/pages/order/contract-management.vue
Normal file
444
mini_program/staff-mp/src/pages/order/contract-management.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<script setup lang="ts">
|
||||
// 合同管理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const contracts = ref([
|
||||
{
|
||||
id: '1',
|
||||
contractNo: 'HT2025001',
|
||||
supplier: '某某牛场',
|
||||
customer: '某某屠宰场',
|
||||
signDate: '2025-09-01',
|
||||
startDate: '2025-09-01',
|
||||
endDate: '2026-09-01',
|
||||
amount: 500000,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
contractNo: 'HT2025002',
|
||||
supplier: '某某牧场',
|
||||
customer: '某某加工厂',
|
||||
signDate: '2025-08-15',
|
||||
startDate: '2025-08-15',
|
||||
endDate: '2026-08-15',
|
||||
amount: 300000,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
contractNo: 'HT2025003',
|
||||
supplier: '某某养殖基地',
|
||||
customer: '某某配送中心',
|
||||
signDate: '2025-07-01',
|
||||
startDate: '2025-07-01',
|
||||
endDate: '2025-09-20',
|
||||
amount: 400000,
|
||||
status: 'expired'
|
||||
}
|
||||
]);
|
||||
|
||||
const newContract = ref({
|
||||
supplier: '',
|
||||
customer: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
amount: 0
|
||||
});
|
||||
|
||||
const showAddContract = ref(false);
|
||||
|
||||
const loadContracts = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取合同数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadContracts();
|
||||
});
|
||||
|
||||
const handleAddContract = () => {
|
||||
if (!newContract.value.supplier || !newContract.value.customer || !newContract.value.startDate) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加合同
|
||||
const contract = {
|
||||
id: String(contracts.value.length + 1),
|
||||
contractNo: `HT2025${String(contracts.value.length + 1).padStart(3, '0')}`,
|
||||
supplier: newContract.value.supplier,
|
||||
customer: newContract.value.customer,
|
||||
signDate: new Date().toISOString().split('T')[0],
|
||||
startDate: newContract.value.startDate,
|
||||
endDate: newContract.value.endDate || newContract.value.startDate,
|
||||
amount: newContract.value.amount,
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
contracts.value.unshift(contract);
|
||||
|
||||
// 重置表单
|
||||
newContract.value = {
|
||||
supplier: '',
|
||||
customer: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
amount: 0
|
||||
};
|
||||
|
||||
showAddContract.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '合同已创建',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (contractId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看合同详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleRenewContract = (contractId: string) => {
|
||||
uni.showModal({
|
||||
title: '续签合同',
|
||||
content: '确定要续签此合同吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const contract = contracts.value.find(c => c.id === contractId);
|
||||
if (contract) {
|
||||
contract.endDate = '2027-09-01'; // 模拟续签一年
|
||||
contract.status = 'active';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '合同已续签',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleTerminateContract = (contractId: string) => {
|
||||
uni.showModal({
|
||||
title: '终止合同',
|
||||
content: '确定要终止此合同吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const contract = contracts.value.find(c => c.id === contractId);
|
||||
if (contract) {
|
||||
contract.status = 'terminated';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '合同已终止',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteContract = (contractId: string) => {
|
||||
uni.showModal({
|
||||
title: '删除合同',
|
||||
content: '确定要删除此合同吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
contracts.value = contracts.value.filter(c => c.id !== contractId);
|
||||
uni.showToast({
|
||||
title: '合同已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">合同管理</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddContract = true">创建合同</button>
|
||||
</view>
|
||||
|
||||
<view class="contracts-list">
|
||||
<view v-for="contract in contracts" :key="contract.id" class="contract-item">
|
||||
<view class="contract-header">
|
||||
<text class="contract-no">{{ contract.contractNo }}</text>
|
||||
<text class="contract-status" :class="contract.status">
|
||||
{{
|
||||
contract.status === 'active' ? '生效中' :
|
||||
contract.status === 'expired' ? '已过期' : '已终止'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="contract-content">
|
||||
<view class="contract-info">
|
||||
<text>供应商: {{ contract.supplier }}</text>
|
||||
<text>客户: {{ contract.customer }}</text>
|
||||
<text>签订日期: {{ contract.signDate }}</text>
|
||||
<text>有效期: {{ contract.startDate }} 至 {{ contract.endDate }}</text>
|
||||
<text>合同金额: ¥{{ contract.amount.toLocaleString() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="contract-actions">
|
||||
<button size="mini" @click="handleViewDetail(contract.id)">详情</button>
|
||||
<button
|
||||
v-if="contract.status === 'active'"
|
||||
size="mini"
|
||||
@click="handleRenewContract(contract.id)"
|
||||
>
|
||||
续签
|
||||
</button>
|
||||
<button
|
||||
v-if="contract.status === 'active'"
|
||||
size="mini"
|
||||
type="warn"
|
||||
@click="handleTerminateContract(contract.id)"
|
||||
>
|
||||
终止
|
||||
</button>
|
||||
<button
|
||||
v-if="contract.status !== 'active'"
|
||||
size="mini"
|
||||
type="warn"
|
||||
@click="handleDeleteContract(contract.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加合同弹窗 -->
|
||||
<view v-if="showAddContract" class="modal-overlay" @click="showAddContract = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">创建合同</text>
|
||||
<text class="modal-close" @click="showAddContract = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">供应商</text>
|
||||
<input v-model="newContract.supplier" class="input" placeholder="请输入供应商" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">客户</text>
|
||||
<input v-model="newContract.customer" class="input" placeholder="请输入客户" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">开始日期</text>
|
||||
<input
|
||||
v-model="newContract.startDate"
|
||||
type="text"
|
||||
class="input"
|
||||
placeholder="请输入开始日期"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">结束日期</text>
|
||||
<input
|
||||
v-model="newContract.endDate"
|
||||
type="text"
|
||||
class="input"
|
||||
placeholder="请输入结束日期"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">合同金额(元)</text>
|
||||
<input
|
||||
v-model="newContract.amount"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入合同金额"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddContract = false">取消</button>
|
||||
<button type="primary" @click="handleAddContract">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.contracts-list {
|
||||
.contract-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.contract-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.contract-no {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.contract-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
color: #fff;
|
||||
|
||||
&.active {
|
||||
background: #3cc51f;
|
||||
}
|
||||
|
||||
&.expired {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&.terminated {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contract-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.contract-info {
|
||||
text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contract-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
353
mini_program/staff-mp/src/pages/order/exception-handling.vue
Normal file
353
mini_program/staff-mp/src/pages/order/exception-handling.vue
Normal file
@@ -0,0 +1,353 @@
|
||||
<script setup lang="ts">
|
||||
// 异常处理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const exceptions = ref([
|
||||
{
|
||||
id: '1',
|
||||
orderNo: 'ORDER2025001',
|
||||
type: '运输异常',
|
||||
description: '运输途中发生交通事故',
|
||||
status: 'pending',
|
||||
reporter: '张师傅',
|
||||
reportTime: '2025-09-15 10:30',
|
||||
priority: 'high'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
orderNo: 'ORDER2025002',
|
||||
type: '质量异常',
|
||||
description: '部分牛只出现应激反应',
|
||||
status: 'processing',
|
||||
reporter: '李四牧场',
|
||||
reportTime: '2025-09-15 09:15',
|
||||
priority: 'medium'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
orderNo: 'ORDER2025003',
|
||||
type: '交付异常',
|
||||
description: '收货方称重与预期不符',
|
||||
status: 'resolved',
|
||||
reporter: '王五屠宰场',
|
||||
reportTime: '2025-09-14 15:20',
|
||||
priority: 'low'
|
||||
}
|
||||
]);
|
||||
|
||||
const filterStatus = ref('all');
|
||||
const filterPriority = ref('all');
|
||||
|
||||
const filteredExceptions = ref([]);
|
||||
|
||||
const loadExceptions = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
applyFilters();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const applyFilters = () => {
|
||||
let result = [...exceptions.value];
|
||||
|
||||
if (filterStatus.value !== 'all') {
|
||||
result = result.filter(e => e.status === filterStatus.value);
|
||||
}
|
||||
|
||||
if (filterPriority.value !== 'all') {
|
||||
result = result.filter(e => e.priority === filterPriority.value);
|
||||
}
|
||||
|
||||
filteredExceptions.value = result;
|
||||
};
|
||||
|
||||
const handleStatusChange = (e: any) => {
|
||||
filterStatus.value = e.detail.value;
|
||||
applyFilters();
|
||||
};
|
||||
|
||||
const handlePriorityChange = (e: any) => {
|
||||
filterPriority.value = e.detail.value;
|
||||
applyFilters();
|
||||
};
|
||||
|
||||
const handleProcessException = (exceptionId: string) => {
|
||||
uni.showModal({
|
||||
title: '处理异常',
|
||||
content: '请输入处理意见',
|
||||
editable: true,
|
||||
placeholderText: '请输入处理意见',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const exception = exceptions.value.find(e => e.id === exceptionId);
|
||||
if (exception) {
|
||||
exception.status = 'processing';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '异常处理中',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleResolveException = (exceptionId: string) => {
|
||||
uni.showModal({
|
||||
title: '解决异常',
|
||||
content: '确认此异常已解决吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const exception = exceptions.value.find(e => e.id === exceptionId);
|
||||
if (exception) {
|
||||
exception.status = 'resolved';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '异常已解决',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (exceptionId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看异常详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadExceptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">异常处理</text>
|
||||
</view>
|
||||
|
||||
<view class="filter-section">
|
||||
<view class="filter-row">
|
||||
<view class="filter-item">
|
||||
<text class="label">状态</text>
|
||||
<picker :range="['全部', '待处理', '处理中', '已解决']" @change="handleStatusChange">
|
||||
<view class="picker">
|
||||
{{
|
||||
filterStatus.value === 'all' ? '全部' :
|
||||
filterStatus.value === 'pending' ? '待处理' :
|
||||
filterStatus.value === 'processing' ? '处理中' : '已解决'
|
||||
}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label">优先级</text>
|
||||
<picker :range="['全部', '高', '中', '低']" @change="handlePriorityChange">
|
||||
<view class="picker">
|
||||
{{
|
||||
filterPriority.value === 'all' ? '全部' :
|
||||
filterPriority.value === 'high' ? '高' :
|
||||
filterPriority.value === 'medium' ? '中' : '低'
|
||||
}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exceptions-list">
|
||||
<view v-for="exception in filteredExceptions" :key="exception.id" class="exception-item">
|
||||
<view class="exception-header">
|
||||
<text class="order-no">{{ exception.orderNo }}</text>
|
||||
<view class="exception-tags">
|
||||
<text class="priority" :class="exception.priority">{{
|
||||
exception.priority === 'high' ? '高优先级' :
|
||||
exception.priority === 'medium' ? '中优先级' : '低优先级'
|
||||
}}</text>
|
||||
<text class="status" :class="exception.status">{{
|
||||
exception.status === 'pending' ? '待处理' :
|
||||
exception.status === 'processing' ? '处理中' : '已解决'
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exception-content">
|
||||
<view class="exception-type">{{ exception.type }}</view>
|
||||
<view class="exception-description">{{ exception.description }}</view>
|
||||
<view class="exception-meta">
|
||||
<text>报告人: {{ exception.reporter }}</text>
|
||||
<text>报告时间: {{ exception.reportTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="exception-actions" v-if="exception.status !== 'resolved'">
|
||||
<button
|
||||
v-if="exception.status === 'pending'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleProcessException(exception.id)"
|
||||
>
|
||||
开始处理
|
||||
</button>
|
||||
<button
|
||||
v-if="exception.status === 'processing'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleResolveException(exception.id)"
|
||||
>
|
||||
标记解决
|
||||
</button>
|
||||
<button size="mini" @click="handleViewDetail(exception.id)">详情</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
.filter-item {
|
||||
flex: 1;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.exceptions-list {
|
||||
.exception-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.exception-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.order-no {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.exception-tags {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
.priority, .status {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.priority.high {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.priority.medium {
|
||||
background: #ff9900;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.priority.low {
|
||||
background: #3cc51f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status.pending {
|
||||
background: #ff9900;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status.processing {
|
||||
background: #1989fa;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.status.resolved {
|
||||
background: #3cc51f;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.exception-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.exception-type {
|
||||
font-size: 26rpx;
|
||||
color: #1989fa;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.exception-description {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.exception-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.exception-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
323
mini_program/staff-mp/src/pages/order/order-detail.vue
Normal file
323
mini_program/staff-mp/src/pages/order/order-detail.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<script setup lang="ts">
|
||||
// 订单详情页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const orderDetail = ref({
|
||||
id: '',
|
||||
orderNo: '',
|
||||
supplier: '',
|
||||
customer: '',
|
||||
status: '',
|
||||
quantity: 0,
|
||||
amount: 0,
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
cattleInfo: {
|
||||
breed: '',
|
||||
weight: 0,
|
||||
healthStatus: ''
|
||||
},
|
||||
transportInfo: {
|
||||
driver: '',
|
||||
phone: '',
|
||||
vehicleNo: '',
|
||||
departure: '',
|
||||
destination: ''
|
||||
}
|
||||
});
|
||||
|
||||
const orderLogs = ref([
|
||||
{ time: '2025-09-15 09:00', action: '订单创建', operator: '张三' },
|
||||
{ time: '2025-09-15 10:30', action: '订单审核通过', operator: '李四' },
|
||||
{ time: '2025-09-15 14:00', action: '开始装车', operator: '王五' }
|
||||
]);
|
||||
|
||||
// 获取订单ID
|
||||
onLoad((options) => {
|
||||
if (options?.id) {
|
||||
loadOrderDetail(options.id);
|
||||
}
|
||||
});
|
||||
|
||||
const loadOrderDetail = async (orderId: string) => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
orderDetail.value = {
|
||||
id: orderId,
|
||||
orderNo: 'ORDER2025001',
|
||||
supplier: '某某牛场',
|
||||
customer: '某某屠宰场',
|
||||
status: 'in_transit',
|
||||
quantity: 50,
|
||||
amount: 250000,
|
||||
createTime: '2025-09-15 09:00',
|
||||
updateTime: '2025-09-15 14:00',
|
||||
cattleInfo: {
|
||||
breed: '西门塔尔',
|
||||
weight: 500,
|
||||
healthStatus: '健康'
|
||||
},
|
||||
transportInfo: {
|
||||
driver: '赵师傅',
|
||||
phone: '13800138000',
|
||||
vehicleNo: '京A12345',
|
||||
departure: '北京市朝阳区某某牛场',
|
||||
destination: '北京市通州区某某屠宰场'
|
||||
}
|
||||
};
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleApprove = () => {
|
||||
uni.showModal({
|
||||
title: '确认审核',
|
||||
content: '确定要审核通过此订单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '审核通过',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleReject = () => {
|
||||
uni.showModal({
|
||||
title: '拒绝订单',
|
||||
content: '确定要拒绝此订单吗?',
|
||||
editable: true,
|
||||
placeholderText: '请输入拒绝原因',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '订单已拒绝',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCallDriver = (phone: string) => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">订单详情</text>
|
||||
</view>
|
||||
|
||||
<view class="order-info-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">订单号:</text>
|
||||
<text class="value">{{ orderDetail.orderNo }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">供应商:</text>
|
||||
<text class="value">{{ orderDetail.supplier }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">客户:</text>
|
||||
<text class="value">{{ orderDetail.customer }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">状态:</text>
|
||||
<text class="value status">{{ orderDetail.status }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">数量:</text>
|
||||
<text class="value">{{ orderDetail.quantity }}头</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">金额:</text>
|
||||
<text class="value">¥{{ orderDetail.amount }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间:</text>
|
||||
<text class="value">{{ orderDetail.createTime }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">更新时间:</text>
|
||||
<text class="value">{{ orderDetail.updateTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cattle-info-section">
|
||||
<view class="section-title">牛只信息</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">品种:</text>
|
||||
<text class="value">{{ orderDetail.cattleInfo.breed }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">平均重量:</text>
|
||||
<text class="value">{{ orderDetail.cattleInfo.weight }}kg</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">健康状况:</text>
|
||||
<text class="value">{{ orderDetail.cattleInfo.healthStatus }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="transport-info-section">
|
||||
<view class="section-title">运输信息</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">司机:</text>
|
||||
<text class="value">{{ orderDetail.transportInfo.driver }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">电话:</text>
|
||||
<text class="value">{{ orderDetail.transportInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">车牌号:</text>
|
||||
<text class="value">{{ orderDetail.transportInfo.vehicleNo }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">出发地:</text>
|
||||
<text class="value">{{ orderDetail.transportInfo.departure }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">目的地:</text>
|
||||
<text class="value">{{ orderDetail.transportInfo.destination }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-button">
|
||||
<button size="mini" @click="handleCallDriver(orderDetail.transportInfo.phone)">联系司机</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="logs-section">
|
||||
<view class="section-title">订单日志</view>
|
||||
<view class="logs-list">
|
||||
<view v-for="(log, index) in orderLogs" :key="index" class="log-item">
|
||||
<view class="log-time">{{ log.time }}</view>
|
||||
<view class="log-content">
|
||||
<text class="log-action">{{ log.action }}</text>
|
||||
<text class="log-operator">操作人: {{ log.operator }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section" v-if="orderDetail.status === 'pending'">
|
||||
<button type="primary" @click="handleApprove">审核通过</button>
|
||||
<button @click="handleReject">拒绝订单</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 10rpx;
|
||||
border-left: 6rpx solid #1989fa;
|
||||
}
|
||||
|
||||
.order-info-section,
|
||||
.cattle-info-section,
|
||||
.transport-info-section,
|
||||
.logs-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.info-item {
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
|
||||
&.status {
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.logs-list {
|
||||
.log-item {
|
||||
display: flex;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
width: 180rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
flex: 1;
|
||||
|
||||
.log-action {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.log-operator {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,6 +21,32 @@ const loadOrders = async () => {
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleViewDetail = (orderId: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/order-detail?id=${orderId}`
|
||||
});
|
||||
};
|
||||
|
||||
const handleApprove = (orderId: string) => {
|
||||
uni.showModal({
|
||||
title: '确认审核',
|
||||
content: '确定要审核通过此订单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 更新订单状态
|
||||
const order = orders.value.find(o => o.id === orderId);
|
||||
if (order) {
|
||||
order.status = 'confirmed';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '审核通过',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadOrders();
|
||||
</script>
|
||||
|
||||
@@ -42,7 +68,13 @@ loadOrders();
|
||||
<view v-for="order in orders" :key="order.id" class="order-item">
|
||||
<view class="order-header">
|
||||
<text class="order-no">{{ order.orderNo }}</text>
|
||||
<text :class="`status-${order.status}`">{{ order.status }}</text>
|
||||
<text :class="`status-${order.status}`">
|
||||
{{
|
||||
order.status === 'pending' ? '待确认' :
|
||||
order.status === 'confirmed' ? '已确认' :
|
||||
'运输中'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<text>供应商: {{ order.supplier }}</text>
|
||||
|
||||
432
mini_program/staff-mp/src/pages/order/payment-management.vue
Normal file
432
mini_program/staff-mp/src/pages/order/payment-management.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<script setup lang="ts">
|
||||
// 支付管理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const payments = ref([
|
||||
{
|
||||
id: '1',
|
||||
paymentNo: 'ZF2025001',
|
||||
orderNo: 'ORDER2025001',
|
||||
supplier: '某某牛场',
|
||||
amount: 250000,
|
||||
paymentMethod: '银行转账',
|
||||
status: 'paid',
|
||||
paymentDate: '2025-09-15',
|
||||
remarks: '已转账'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
paymentNo: 'ZF2025002',
|
||||
orderNo: 'ORDER2025002',
|
||||
supplier: '某某牧场',
|
||||
amount: 150000,
|
||||
paymentMethod: '现金支付',
|
||||
status: 'pending',
|
||||
paymentDate: '',
|
||||
remarks: '待支付'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
paymentNo: 'ZF2025003',
|
||||
orderNo: 'ORDER2025003',
|
||||
supplier: '某某养殖基地',
|
||||
amount: 400000,
|
||||
paymentMethod: '银行转账',
|
||||
status: 'partial',
|
||||
paymentDate: '2025-09-10',
|
||||
remarks: '已支付定金20万'
|
||||
}
|
||||
]);
|
||||
|
||||
const newPayment = ref({
|
||||
orderNo: '',
|
||||
supplier: '',
|
||||
amount: 0,
|
||||
paymentMethod: '银行转账',
|
||||
remarks: ''
|
||||
});
|
||||
|
||||
const showAddPayment = ref(false);
|
||||
|
||||
const loadPayments = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取支付数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadPayments();
|
||||
});
|
||||
|
||||
const handleAddPayment = () => {
|
||||
if (!newPayment.value.orderNo || !newPayment.value.supplier || !newPayment.value.amount) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加支付记录
|
||||
const payment = {
|
||||
id: String(payments.value.length + 1),
|
||||
paymentNo: `ZF2025${String(payments.value.length + 1).padStart(3, '0')}`,
|
||||
orderNo: newPayment.value.orderNo,
|
||||
supplier: newPayment.value.supplier,
|
||||
amount: newPayment.value.amount,
|
||||
paymentMethod: newPayment.value.paymentMethod,
|
||||
status: 'paid',
|
||||
paymentDate: new Date().toISOString().split('T')[0],
|
||||
remarks: newPayment.value.remarks
|
||||
};
|
||||
|
||||
payments.value.unshift(payment);
|
||||
|
||||
// 重置表单
|
||||
newPayment.value = {
|
||||
orderNo: '',
|
||||
supplier: '',
|
||||
amount: 0,
|
||||
paymentMethod: '银行转账',
|
||||
remarks: ''
|
||||
};
|
||||
|
||||
showAddPayment.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '支付记录已添加',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (paymentId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看支付详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirmPayment = (paymentId: string) => {
|
||||
uni.showModal({
|
||||
title: '确认支付',
|
||||
content: '确定要确认此笔支付吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const payment = payments.value.find(p => p.id === paymentId);
|
||||
if (payment) {
|
||||
payment.status = 'paid';
|
||||
payment.paymentDate = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
uni.showToast({
|
||||
title: '支付已确认',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeletePayment = (paymentId: string) => {
|
||||
uni.showModal({
|
||||
title: '删除支付记录',
|
||||
content: '确定要删除此支付记录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
payments.value = payments.value.filter(p => p.id !== paymentId);
|
||||
uni.showToast({
|
||||
title: '支付记录已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">支付管理</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddPayment = true">添加支付记录</button>
|
||||
</view>
|
||||
|
||||
<view class="payments-list">
|
||||
<view v-for="payment in payments" :key="payment.id" class="payment-item">
|
||||
<view class="payment-header">
|
||||
<text class="payment-no">{{ payment.paymentNo }}</text>
|
||||
<text class="payment-status" :class="payment.status">
|
||||
{{
|
||||
payment.status === 'pending' ? '待支付' :
|
||||
payment.status === 'paid' ? '已支付' : '部分支付'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="payment-content">
|
||||
<view class="payment-info">
|
||||
<text>订单号: {{ payment.orderNo }}</text>
|
||||
<text>供应商: {{ payment.supplier }}</text>
|
||||
<text>支付金额: ¥{{ payment.amount.toLocaleString() }}</text>
|
||||
<text>支付方式: {{ payment.paymentMethod }}</text>
|
||||
<text v-if="payment.paymentDate">支付日期: {{ payment.paymentDate }}</text>
|
||||
<text v-if="payment.remarks">备注: {{ payment.remarks }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="payment-actions">
|
||||
<button size="mini" @click="handleViewDetail(payment.id)">详情</button>
|
||||
<button
|
||||
v-if="payment.status !== 'paid'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleConfirmPayment(payment.id)"
|
||||
>
|
||||
确认支付
|
||||
</button>
|
||||
<button
|
||||
v-if="payment.status === 'pending'"
|
||||
size="mini"
|
||||
type="warn"
|
||||
@click="handleDeletePayment(payment.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加支付记录弹窗 -->
|
||||
<view v-if="showAddPayment" class="modal-overlay" @click="showAddPayment = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">添加支付记录</text>
|
||||
<text class="modal-close" @click="showAddPayment = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">订单号</text>
|
||||
<input v-model="newPayment.orderNo" class="input" placeholder="请输入订单号" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">供应商</text>
|
||||
<input v-model="newPayment.supplier" class="input" placeholder="请输入供应商" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">支付金额(元)</text>
|
||||
<input
|
||||
v-model="newPayment.amount"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入支付金额"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">支付方式</text>
|
||||
<picker
|
||||
:range="['银行转账', '现金支付', '支付宝', '微信支付']"
|
||||
@change="e => newPayment.paymentMethod = ['银行转账', '现金支付', '支付宝', '微信支付'][e.detail.value]"
|
||||
>
|
||||
<view class="picker">
|
||||
{{ newPayment.paymentMethod }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea
|
||||
v-model="newPayment.remarks"
|
||||
class="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddPayment = false">取消</button>
|
||||
<button type="primary" @click="handleAddPayment">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.payments-list {
|
||||
.payment-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.payment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.payment-no {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.payment-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
color: #fff;
|
||||
|
||||
&.pending {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&.paid {
|
||||
background: #3cc51f;
|
||||
}
|
||||
|
||||
&.partial {
|
||||
background: #1989fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payment-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.payment-info {
|
||||
text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payment-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input, .picker {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 120rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
429
mini_program/staff-mp/src/pages/order/purchase-plan.vue
Normal file
429
mini_program/staff-mp/src/pages/order/purchase-plan.vue
Normal file
@@ -0,0 +1,429 @@
|
||||
<script setup lang="ts">
|
||||
// 采购计划页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const purchasePlans = ref([
|
||||
{
|
||||
id: '1',
|
||||
planNo: 'PLAN2025001',
|
||||
supplier: '某某牛场',
|
||||
breed: '西门塔尔',
|
||||
quantity: 50,
|
||||
weight: 25000,
|
||||
expectedPrice: 500000,
|
||||
deliveryDate: '2025-09-20',
|
||||
status: 'approved'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
planNo: 'PLAN2025002',
|
||||
supplier: '某某牧场',
|
||||
breed: '夏洛莱',
|
||||
quantity: 30,
|
||||
weight: 15000,
|
||||
expectedPrice: 300000,
|
||||
deliveryDate: '2025-09-22',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
planNo: 'PLAN2025003',
|
||||
supplier: '某某养殖基地',
|
||||
breed: '安格斯',
|
||||
quantity: 40,
|
||||
weight: 20000,
|
||||
expectedPrice: 400000,
|
||||
deliveryDate: '2025-09-25',
|
||||
status: 'completed'
|
||||
}
|
||||
]);
|
||||
|
||||
const newPlan = ref({
|
||||
supplier: '',
|
||||
breed: '',
|
||||
quantity: 0,
|
||||
weight: 0,
|
||||
expectedPrice: 0,
|
||||
deliveryDate: ''
|
||||
});
|
||||
|
||||
const showAddPlan = ref(false);
|
||||
|
||||
const loadPurchasePlans = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取采购计划数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadPurchasePlans();
|
||||
});
|
||||
|
||||
const handleAddPlan = () => {
|
||||
if (!newPlan.value.supplier || !newPlan.value.breed || !newPlan.value.quantity) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加采购计划
|
||||
const plan = {
|
||||
id: String(purchasePlans.value.length + 1),
|
||||
planNo: `PLAN2025${String(purchasePlans.value.length + 1).padStart(3, '0')}`,
|
||||
supplier: newPlan.value.supplier,
|
||||
breed: newPlan.value.breed,
|
||||
quantity: newPlan.value.quantity,
|
||||
weight: newPlan.value.weight || newPlan.value.quantity * 500,
|
||||
expectedPrice: newPlan.value.expectedPrice || newPlan.value.quantity * 10000,
|
||||
deliveryDate: newPlan.value.deliveryDate,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
purchasePlans.value.unshift(plan);
|
||||
|
||||
// 重置表单
|
||||
newPlan.value = {
|
||||
supplier: '',
|
||||
breed: '',
|
||||
quantity: 0,
|
||||
weight: 0,
|
||||
expectedPrice: 0,
|
||||
deliveryDate: ''
|
||||
};
|
||||
|
||||
showAddPlan.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '采购计划已创建',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleApprovePlan = (planId: string) => {
|
||||
uni.showModal({
|
||||
title: '审批采购计划',
|
||||
content: '确定要审批通过此采购计划吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const plan = purchasePlans.value.find(p => p.id === planId);
|
||||
if (plan) {
|
||||
plan.status = 'approved';
|
||||
}
|
||||
uni.showToast({
|
||||
title: '审批通过',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (planId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看采购计划详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeletePlan = (planId: string) => {
|
||||
uni.showModal({
|
||||
title: '删除采购计划',
|
||||
content: '确定要删除此采购计划吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
purchasePlans.value = purchasePlans.value.filter(p => p.id !== planId);
|
||||
uni.showToast({
|
||||
title: '采购计划已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">采购计划</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddPlan = true">创建采购计划</button>
|
||||
</view>
|
||||
|
||||
<view class="plans-list">
|
||||
<view v-for="plan in purchasePlans" :key="plan.id" class="plan-item">
|
||||
<view class="plan-header">
|
||||
<text class="plan-no">{{ plan.planNo }}</text>
|
||||
<text class="plan-status" :class="plan.status">
|
||||
{{
|
||||
plan.status === 'pending' ? '待审批' :
|
||||
plan.status === 'approved' ? '已审批' : '已完成'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="plan-content">
|
||||
<view class="plan-info">
|
||||
<text>供应商: {{ plan.supplier }}</text>
|
||||
<text>品种: {{ plan.breed }}</text>
|
||||
<text>数量: {{ plan.quantity }}头</text>
|
||||
<text>重量: {{ plan.weight }}kg</text>
|
||||
<text>预计金额: ¥{{ plan.expectedPrice.toLocaleString() }}</text>
|
||||
<text>交付日期: {{ plan.deliveryDate }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="plan-actions">
|
||||
<button size="mini" @click="handleViewDetail(plan.id)">详情</button>
|
||||
<button
|
||||
v-if="plan.status === 'pending'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleApprovePlan(plan.id)"
|
||||
>
|
||||
审批
|
||||
</button>
|
||||
<button
|
||||
v-if="plan.status === 'pending'"
|
||||
size="mini"
|
||||
type="warn"
|
||||
@click="handleDeletePlan(plan.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加采购计划弹窗 -->
|
||||
<view v-if="showAddPlan" class="modal-overlay" @click="showAddPlan = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">创建采购计划</text>
|
||||
<text class="modal-close" @click="showAddPlan = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">供应商</text>
|
||||
<input v-model="newPlan.supplier" class="input" placeholder="请输入供应商" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">品种</text>
|
||||
<input v-model="newPlan.breed" class="input" placeholder="请输入品种" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">数量(头)</text>
|
||||
<input
|
||||
v-model="newPlan.quantity"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入数量"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">重量(kg)</text>
|
||||
<input
|
||||
v-model="newPlan.weight"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入重量"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">预计金额(元)</text>
|
||||
<input
|
||||
v-model="newPlan.expectedPrice"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入预计金额"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">交付日期</text>
|
||||
<input
|
||||
v-model="newPlan.deliveryDate"
|
||||
type="text"
|
||||
class="input"
|
||||
placeholder="请输入交付日期"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddPlan = false">取消</button>
|
||||
<button type="primary" @click="handleAddPlan">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.plans-list {
|
||||
.plan-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.plan-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.plan-no {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.plan-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
color: #fff;
|
||||
|
||||
&.pending {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&.approved {
|
||||
background: #1989fa;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background: #3cc51f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plan-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.plan-info {
|
||||
text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plan-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
399
mini_program/staff-mp/src/pages/order/quality-inspection.vue
Normal file
399
mini_program/staff-mp/src/pages/order/quality-inspection.vue
Normal file
@@ -0,0 +1,399 @@
|
||||
<script setup lang="ts">
|
||||
// 质检管理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const inspections = ref([
|
||||
{
|
||||
id: '1',
|
||||
orderNo: 'ORDER2025001',
|
||||
supplier: '某某牛场',
|
||||
inspector: '张质检员',
|
||||
inspectionDate: '2025-09-15',
|
||||
status: 'passed',
|
||||
qualifiedCount: 48,
|
||||
unqualifiedCount: 2,
|
||||
remarks: '2头牛有轻微应激反应'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
orderNo: 'ORDER2025002',
|
||||
supplier: '某某牧场',
|
||||
inspector: '李质检员',
|
||||
inspectionDate: '2025-09-14',
|
||||
status: 'pending',
|
||||
qualifiedCount: 0,
|
||||
unqualifiedCount: 0,
|
||||
remarks: ''
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
orderNo: 'ORDER2025003',
|
||||
supplier: '某某养殖基地',
|
||||
inspector: '王质检员',
|
||||
inspectionDate: '2025-09-13',
|
||||
status: 'failed',
|
||||
qualifiedCount: 35,
|
||||
unqualifiedCount: 5,
|
||||
remarks: '5头牛有疾病症状,不予通过'
|
||||
}
|
||||
]);
|
||||
|
||||
const newInspection = ref({
|
||||
orderNo: '',
|
||||
supplier: '',
|
||||
qualifiedCount: 0,
|
||||
unqualifiedCount: 0,
|
||||
remarks: ''
|
||||
});
|
||||
|
||||
const showAddInspection = ref(false);
|
||||
|
||||
const loadInspections = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取质检数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadInspections();
|
||||
});
|
||||
|
||||
const handleAddInspection = () => {
|
||||
if (!newInspection.value.orderNo || !newInspection.value.supplier) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加质检记录
|
||||
const inspection = {
|
||||
id: String(inspections.value.length + 1),
|
||||
orderNo: newInspection.value.orderNo,
|
||||
supplier: newInspection.value.supplier,
|
||||
inspector: '当前用户',
|
||||
inspectionDate: new Date().toISOString().split('T')[0],
|
||||
status: newInspection.value.unqualifiedCount > 0 ? 'failed' : 'passed',
|
||||
qualifiedCount: newInspection.value.qualifiedCount,
|
||||
unqualifiedCount: newInspection.value.unqualifiedCount,
|
||||
remarks: newInspection.value.remarks
|
||||
};
|
||||
|
||||
inspections.value.unshift(inspection);
|
||||
|
||||
// 重置表单
|
||||
newInspection.value = {
|
||||
orderNo: '',
|
||||
supplier: '',
|
||||
qualifiedCount: 0,
|
||||
unqualifiedCount: 0,
|
||||
remarks: ''
|
||||
};
|
||||
|
||||
showAddInspection.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '质检记录已添加',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (inspectionId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看质检详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteInspection = (inspectionId: string) => {
|
||||
uni.showModal({
|
||||
title: '删除质检记录',
|
||||
content: '确定要删除此质检记录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
inspections.value = inspections.value.filter(i => i.id !== inspectionId);
|
||||
uni.showToast({
|
||||
title: '质检记录已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">质检管理</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddInspection = true">添加质检记录</button>
|
||||
</view>
|
||||
|
||||
<view class="inspections-list">
|
||||
<view v-for="inspection in inspections" :key="inspection.id" class="inspection-item">
|
||||
<view class="inspection-header">
|
||||
<text class="order-no">{{ inspection.orderNo }}</text>
|
||||
<text class="inspection-status" :class="inspection.status">
|
||||
{{
|
||||
inspection.status === 'pending' ? '待质检' :
|
||||
inspection.status === 'passed' ? '通过' : '未通过'
|
||||
}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="inspection-content">
|
||||
<view class="inspection-info">
|
||||
<text>供应商: {{ inspection.supplier }}</text>
|
||||
<text>质检员: {{ inspection.inspector }}</text>
|
||||
<text>质检日期: {{ inspection.inspectionDate }}</text>
|
||||
<text>合格数量: {{ inspection.qualifiedCount }}头</text>
|
||||
<text>不合格数量: {{ inspection.unqualifiedCount }}头</text>
|
||||
<text v-if="inspection.remarks">备注: {{ inspection.remarks }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="inspection-actions">
|
||||
<button size="mini" @click="handleViewDetail(inspection.id)">详情</button>
|
||||
<button
|
||||
v-if="inspection.status === 'pending'"
|
||||
size="mini"
|
||||
type="warn"
|
||||
@click="handleDeleteInspection(inspection.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加质检记录弹窗 -->
|
||||
<view v-if="showAddInspection" class="modal-overlay" @click="showAddInspection = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">添加质检记录</text>
|
||||
<text class="modal-close" @click="showAddInspection = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">订单号</text>
|
||||
<input v-model="newInspection.orderNo" class="input" placeholder="请输入订单号" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">供应商</text>
|
||||
<input v-model="newInspection.supplier" class="input" placeholder="请输入供应商" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">合格数量(头)</text>
|
||||
<input
|
||||
v-model="newInspection.qualifiedCount"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入合格数量"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">不合格数量(头)</text>
|
||||
<input
|
||||
v-model="newInspection.unqualifiedCount"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="请输入不合格数量"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">备注</text>
|
||||
<textarea
|
||||
v-model="newInspection.remarks"
|
||||
class="textarea"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddInspection = false">取消</button>
|
||||
<button type="primary" @click="handleAddInspection">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.inspections-list {
|
||||
.inspection-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.inspection-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.order-no {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.inspection-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
color: #fff;
|
||||
|
||||
&.pending {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&.passed {
|
||||
background: #3cc51f;
|
||||
}
|
||||
|
||||
&.failed {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inspection-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.inspection-info {
|
||||
text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inspection-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 120rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
311
mini_program/staff-mp/src/pages/statistics/data-reports.vue
Normal file
311
mini_program/staff-mp/src/pages/statistics/data-reports.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<script setup lang="ts">
|
||||
// 数据报表页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const reportTypes = ref([
|
||||
{ id: '1', name: '订单统计报表', description: '按时间统计订单数量和金额' },
|
||||
{ id: '2', name: '供应商绩效报表', description: '统计各供应商订单完成情况' },
|
||||
{ id: '3', name: '运输时效报表', description: '统计各线路运输时效' },
|
||||
{ id: '4', name: '客户分析报表', description: '分析客户采购行为' }
|
||||
]);
|
||||
|
||||
const selectedReport = ref('1');
|
||||
const reportData = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const loadReportData = async () => {
|
||||
loading.value = true;
|
||||
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 根据不同报表类型返回不同数据
|
||||
switch (selectedReport.value) {
|
||||
case '1':
|
||||
reportData.value = [
|
||||
{ date: '2025-09-01', orders: 12, amount: 600000 },
|
||||
{ date: '2025-09-02', orders: 8, amount: 400000 },
|
||||
{ date: '2025-09-03', orders: 15, amount: 750000 },
|
||||
{ date: '2025-09-04', orders: 10, amount: 500000 },
|
||||
{ date: '2025-09-05', orders: 18, amount: 900000 }
|
||||
];
|
||||
break;
|
||||
case '2':
|
||||
reportData.value = [
|
||||
{ supplier: '某某牛场', orders: 25, completed: 23, amount: 1250000 },
|
||||
{ supplier: '某某牧场', orders: 18, completed: 17, amount: 900000 },
|
||||
{ supplier: '某某养殖基地', orders: 15, completed: 14, amount: 750000 }
|
||||
];
|
||||
break;
|
||||
case '3':
|
||||
reportData.value = [
|
||||
{ route: '北京-天津', avgTime: '2.5小时', onTimeRate: '92%' },
|
||||
{ route: '北京-石家庄', avgTime: '4.2小时', onTimeRate: '87%' },
|
||||
{ route: '北京-唐山', avgTime: '3.1小时', onTimeRate: '90%' }
|
||||
];
|
||||
break;
|
||||
case '4':
|
||||
reportData.value = [
|
||||
{ customer: '某某屠宰场', orders: 30, amount: 1500000, avgOrder: 50000 },
|
||||
{ customer: '某某加工厂', orders: 22, amount: 1100000, avgOrder: 50000 },
|
||||
{ customer: '某某配送中心', orders: 18, amount: 900000, avgOrder: 50000 }
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadReportData();
|
||||
});
|
||||
|
||||
const handleReportChange = (e: any) => {
|
||||
selectedReport.value = e.detail.value;
|
||||
loadReportData();
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
uni.showToast({
|
||||
title: '报表已导出,请在消息中心查看',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
uni.showToast({
|
||||
title: '请选择日期范围',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">数据报表</text>
|
||||
</view>
|
||||
|
||||
<view class="filter-section">
|
||||
<view class="filter-item">
|
||||
<text class="label">报表类型</text>
|
||||
<picker :range="reportTypes" range-key="name" :value="selectedReport" @change="handleReportChange">
|
||||
<view class="picker">
|
||||
{{ reportTypes.find(item => item.id === selectedReport)?.name || '请选择报表类型' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label">时间范围</text>
|
||||
<view class="date-range" @click="handleDateRangeChange">
|
||||
<text>2025-09-01 至 2025-09-05</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-actions">
|
||||
<button size="mini" @click="loadReportData">查询</button>
|
||||
<button size="mini" type="primary" @click="handleExport">导出报表</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="report-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">
|
||||
{{ reportTypes.find(item => item.id === selectedReport)?.name }}
|
||||
</text>
|
||||
<text class="section-desc">
|
||||
{{ reportTypes.find(item => item.id === selectedReport)?.description }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading">
|
||||
<text>数据加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="report-content">
|
||||
<scroll-view scroll-x class="table-container">
|
||||
<view class="table">
|
||||
<!-- 订单统计报表 -->
|
||||
<view v-if="selectedReport === '1'" class="table-header">
|
||||
<text class="table-cell">日期</text>
|
||||
<text class="table-cell">订单数</text>
|
||||
<text class="table-cell">订单金额</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in reportData" :key="index" class="table-row">
|
||||
<text class="table-cell">{{ item.date }}</text>
|
||||
<text class="table-cell">{{ item.orders }}</text>
|
||||
<text class="table-cell">¥{{ item.amount.toLocaleString() }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 供应商绩效报表 -->
|
||||
<view v-if="selectedReport === '2'" class="table-header">
|
||||
<text class="table-cell">供应商</text>
|
||||
<text class="table-cell">订单数</text>
|
||||
<text class="table-cell">完成数</text>
|
||||
<text class="table-cell">完成率</text>
|
||||
<text class="table-cell">订单金额</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in reportData" :key="index" class="table-row">
|
||||
<text class="table-cell">{{ item.supplier }}</text>
|
||||
<text class="table-cell">{{ item.orders }}</text>
|
||||
<text class="table-cell">{{ item.completed }}</text>
|
||||
<text class="table-cell">{{ Math.round(item.completed / item.orders * 100) }}%</text>
|
||||
<text class="table-cell">¥{{ item.amount.toLocaleString() }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 运输时效报表 -->
|
||||
<view v-if="selectedReport === '3'" class="table-header">
|
||||
<text class="table-cell">运输线路</text>
|
||||
<text class="table-cell">平均时长</text>
|
||||
<text class="table-cell">准时率</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in reportData" :key="index" class="table-row">
|
||||
<text class="table-cell">{{ item.route }}</text>
|
||||
<text class="table-cell">{{ item.avgTime }}</text>
|
||||
<text class="table-cell">{{ item.onTimeRate }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 客户分析报表 -->
|
||||
<view v-if="selectedReport === '4'" class="table-header">
|
||||
<text class="table-cell">客户</text>
|
||||
<text class="table-cell">订单数</text>
|
||||
<text class="table-cell">订单金额</text>
|
||||
<text class="table-cell">平均订单额</text>
|
||||
</view>
|
||||
<view v-for="(item, index) in reportData" :key="index" class="table-row">
|
||||
<text class="table-cell">{{ item.customer }}</text>
|
||||
<text class="table-cell">{{ item.orders }}</text>
|
||||
<text class="table-cell">¥{{ item.amount.toLocaleString() }}</text>
|
||||
<text class="table-cell">¥{{ item.avgOrder.toLocaleString() }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.filter-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker, .date-range {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-section {
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.section-header {
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding: 60rpx;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
padding: 20rpx;
|
||||
|
||||
.table-container {
|
||||
white-space: nowrap;
|
||||
|
||||
.table {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
322
mini_program/staff-mp/src/pages/system/message-center.vue
Normal file
322
mini_program/staff-mp/src/pages/system/message-center.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<script setup lang="ts">
|
||||
// 消息中心页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const messages = ref([
|
||||
{
|
||||
id: '1',
|
||||
title: '订单审核提醒',
|
||||
content: '您有1个新订单待审核,请及时处理。',
|
||||
type: 'order',
|
||||
time: '2025-09-15 10:30',
|
||||
isRead: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '运输异常报告',
|
||||
content: '订单ORDER2025001在运输途中发生异常,请查看处理。',
|
||||
type: 'transport',
|
||||
time: '2025-09-15 09:15',
|
||||
isRead: false
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '系统维护通知',
|
||||
content: '系统将于今晚00:00-02:00进行维护,届时可能无法正常使用。',
|
||||
type: 'system',
|
||||
time: '2025-09-14 18:00',
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '报表生成完成',
|
||||
content: '您请求的月度数据报表已生成完成,可前往查看。',
|
||||
type: 'report',
|
||||
time: '2025-09-14 16:45',
|
||||
isRead: true
|
||||
}
|
||||
]);
|
||||
|
||||
const filterType = ref('all');
|
||||
|
||||
const filteredMessages = ref([]);
|
||||
|
||||
const loadMessages = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
applyFilters();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const applyFilters = () => {
|
||||
if (filterType.value === 'all') {
|
||||
filteredMessages.value = [...messages.value];
|
||||
} else {
|
||||
filteredMessages.value = messages.value.filter(m => m.type === filterType.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterChange = (e: any) => {
|
||||
filterType.value = e.detail.value;
|
||||
applyFilters();
|
||||
};
|
||||
|
||||
const handleMarkAsRead = (messageId: string) => {
|
||||
const message = messages.value.find(m => m.id === messageId);
|
||||
if (message) {
|
||||
message.isRead = true;
|
||||
uni.showToast({
|
||||
title: '已标记为已读',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkAllAsRead = () => {
|
||||
messages.value.forEach(m => {
|
||||
m.isRead = true;
|
||||
});
|
||||
uni.showToast({
|
||||
title: '全部标记为已读',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteMessage = (messageId: string) => {
|
||||
uni.showModal({
|
||||
title: '删除消息',
|
||||
content: '确定要删除这条消息吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
messages.value = messages.value.filter(m => m.id !== messageId);
|
||||
applyFilters();
|
||||
uni.showToast({
|
||||
title: '消息已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewDetail = (messageId: string) => {
|
||||
const message = messages.value.find(m => m.id === messageId);
|
||||
if (message) {
|
||||
// 根据消息类型跳转到相应页面
|
||||
switch (message.type) {
|
||||
case 'order':
|
||||
uni.navigateTo({
|
||||
url: '/pages/order/order-monitor'
|
||||
});
|
||||
break;
|
||||
case 'transport':
|
||||
uni.navigateTo({
|
||||
url: '/pages/transport/transport-monitor'
|
||||
});
|
||||
break;
|
||||
case 'report':
|
||||
uni.navigateTo({
|
||||
url: '/pages/statistics/data-reports'
|
||||
});
|
||||
break;
|
||||
default:
|
||||
uni.showToast({
|
||||
title: '查看详情',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
// 标记为已读
|
||||
message.isRead = true;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadMessages();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">消息中心</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<view class="filter-section">
|
||||
<picker :range="['全部消息', '订单消息', '运输消息', '系统消息', '报表消息']" @change="handleFilterChange">
|
||||
<view class="filter-picker">
|
||||
{{
|
||||
filterType.value === 'all' ? '全部消息' :
|
||||
filterType.value === 'order' ? '订单消息' :
|
||||
filterType.value === 'transport' ? '运输消息' :
|
||||
filterType.value === 'system' ? '系统消息' : '报表消息'
|
||||
}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<button size="mini" @click="handleMarkAllAsRead">全部已读</button>
|
||||
</view>
|
||||
|
||||
<view class="messages-list">
|
||||
<view
|
||||
v-for="message in filteredMessages"
|
||||
:key="message.id"
|
||||
class="message-item"
|
||||
:class="{ 'unread': !message.isRead }"
|
||||
>
|
||||
<view class="message-header">
|
||||
<view class="message-type" :class="message.type">
|
||||
{{
|
||||
message.type === 'order' ? '订单' :
|
||||
message.type === 'transport' ? '运输' :
|
||||
message.type === 'system' ? '系统' : '报表'
|
||||
}}
|
||||
</view>
|
||||
<text class="message-time">{{ message.time }}</text>
|
||||
</view>
|
||||
|
||||
<view class="message-content">
|
||||
<view class="message-title">{{ message.title }}</view>
|
||||
<view class="message-text">{{ message.content }}</view>
|
||||
</view>
|
||||
|
||||
<view class="message-actions">
|
||||
<button size="mini" @click="handleViewDetail(message.id)">查看</button>
|
||||
<button
|
||||
v-if="!message.isRead"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleMarkAsRead(message.id)"
|
||||
>
|
||||
标记已读
|
||||
</button>
|
||||
<button size="mini" type="warn" @click="handleDeleteMessage(message.id)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="filteredMessages.length === 0" class="empty-state">
|
||||
<text>暂无消息</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.filter-section {
|
||||
.filter-picker {
|
||||
height: 50rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messages-list {
|
||||
.message-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
border-left: 6rpx solid #eee;
|
||||
|
||||
&.unread {
|
||||
border-left-color: #1989fa;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.message-type {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
color: #fff;
|
||||
|
||||
&.order {
|
||||
background: #1989fa;
|
||||
}
|
||||
|
||||
&.transport {
|
||||
background: #ff9900;
|
||||
}
|
||||
|
||||
&.system {
|
||||
background: #3cc51f;
|
||||
}
|
||||
|
||||
&.report {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.message-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.message-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.message-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60rpx;
|
||||
color: #999;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
387
mini_program/staff-mp/src/pages/system/supplier-management.vue
Normal file
387
mini_program/staff-mp/src/pages/system/supplier-management.vue
Normal file
@@ -0,0 +1,387 @@
|
||||
<script setup lang="ts">
|
||||
// 供应商管理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const suppliers = ref([
|
||||
{
|
||||
id: '1',
|
||||
name: '某某牛场',
|
||||
contact: '张经理',
|
||||
phone: '13800138001',
|
||||
address: '北京市朝阳区某某路123号',
|
||||
status: 'active',
|
||||
licenseExpiry: '2026-09-01',
|
||||
rating: 4.8,
|
||||
totalOrders: 45
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '某某牧场',
|
||||
contact: '李经理',
|
||||
phone: '13800138002',
|
||||
address: '北京市海淀区某某街456号',
|
||||
status: 'active',
|
||||
licenseExpiry: '2026-08-15',
|
||||
rating: 4.5,
|
||||
totalOrders: 32
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '某某养殖基地',
|
||||
contact: '王经理',
|
||||
phone: '13800138003',
|
||||
address: '北京市通州区某某路789号',
|
||||
status: 'inactive',
|
||||
licenseExpiry: '2025-12-31',
|
||||
rating: 4.2,
|
||||
totalOrders: 28
|
||||
}
|
||||
]);
|
||||
|
||||
const newSupplier = ref({
|
||||
name: '',
|
||||
contact: '',
|
||||
phone: '',
|
||||
address: ''
|
||||
});
|
||||
|
||||
const showAddSupplier = ref(false);
|
||||
|
||||
const loadSuppliers = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取供应商数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadSuppliers();
|
||||
});
|
||||
|
||||
const handleAddSupplier = () => {
|
||||
if (!newSupplier.value.name || !newSupplier.value.contact || !newSupplier.value.phone) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加供应商
|
||||
const supplier = {
|
||||
id: String(suppliers.value.length + 1),
|
||||
name: newSupplier.value.name,
|
||||
contact: newSupplier.value.contact,
|
||||
phone: newSupplier.value.phone,
|
||||
address: newSupplier.value.address,
|
||||
status: 'active',
|
||||
licenseExpiry: '2026-12-31',
|
||||
rating: 5.0,
|
||||
totalOrders: 0
|
||||
};
|
||||
|
||||
suppliers.value.push(supplier);
|
||||
|
||||
// 重置表单
|
||||
newSupplier.value = {
|
||||
name: '',
|
||||
contact: '',
|
||||
phone: '',
|
||||
address: ''
|
||||
};
|
||||
|
||||
showAddSupplier.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '供应商添加成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleStatus = (supplierId: string) => {
|
||||
const supplier = suppliers.value.find(s => s.id === supplierId);
|
||||
if (supplier) {
|
||||
supplier.status = supplier.status === 'active' ? 'inactive' : 'active';
|
||||
uni.showToast({
|
||||
title: `供应商已${supplier.status === 'active' ? '启用' : '禁用'}`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewDetail = (supplierId: string) => {
|
||||
uni.showToast({
|
||||
title: '查看供应商详情',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteSupplier = (supplierId: string) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此供应商吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
suppliers.value = suppliers.value.filter(s => s.id !== supplierId);
|
||||
uni.showToast({
|
||||
title: '供应商已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">供应商管理</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddSupplier = true">添加供应商</button>
|
||||
</view>
|
||||
|
||||
<view class="suppliers-list">
|
||||
<view v-for="supplier in suppliers" :key="supplier.id" class="supplier-item">
|
||||
<view class="supplier-info">
|
||||
<view class="supplier-name">{{ supplier.name }}</view>
|
||||
<view class="supplier-contact">{{ supplier.contact }} - {{ supplier.phone }}</view>
|
||||
<view class="supplier-address">{{ supplier.address }}</view>
|
||||
</view>
|
||||
<view class="supplier-meta">
|
||||
<view class="supplier-status" :class="supplier.status">
|
||||
{{ supplier.status === 'active' ? '正常' : '禁用' }}
|
||||
</view>
|
||||
<view class="supplier-rating">
|
||||
评分: {{ supplier.rating }}
|
||||
</view>
|
||||
<view class="supplier-orders">
|
||||
订单: {{ supplier.totalOrders }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="supplier-actions">
|
||||
<button size="mini" @click="handleViewDetail(supplier.id)">详情</button>
|
||||
<button size="mini" @click="handleToggleStatus(supplier.id)">
|
||||
{{ supplier.status === 'active' ? '禁用' : '启用' }}
|
||||
</button>
|
||||
<button size="mini" type="warn" @click="handleDeleteSupplier(supplier.id)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加供应商弹窗 -->
|
||||
<view v-if="showAddSupplier" class="modal-overlay" @click="showAddSupplier = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">添加供应商</text>
|
||||
<text class="modal-close" @click="showAddSupplier = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">供应商名称</text>
|
||||
<input v-model="newSupplier.name" class="input" placeholder="请输入供应商名称" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">联系人</text>
|
||||
<input v-model="newSupplier.contact" class="input" placeholder="请输入联系人" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">联系电话</text>
|
||||
<input v-model="newSupplier.phone" type="number" class="input" placeholder="请输入联系电话" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">地址</text>
|
||||
<input v-model="newSupplier.address" class="input" placeholder="请输入地址" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddSupplier = false">取消</button>
|
||||
<button type="primary" @click="handleAddSupplier">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.suppliers-list {
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.supplier-item {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.supplier-info {
|
||||
flex: 1;
|
||||
|
||||
.supplier-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.supplier-contact {
|
||||
font-size: 24rpx;
|
||||
color: #1989fa;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.supplier-address {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.supplier-meta {
|
||||
width: 150rpx;
|
||||
text-align: center;
|
||||
|
||||
.supplier-status {
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.active {
|
||||
color: #3cc51f;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
color: #ff9900;
|
||||
}
|
||||
}
|
||||
|
||||
.supplier-rating {
|
||||
font-size: 20rpx;
|
||||
color: #ff9900;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.supplier-orders {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.supplier-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
width: 180rpx;
|
||||
|
||||
button {
|
||||
&:first-child {
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
347
mini_program/staff-mp/src/pages/system/user-management.vue
Normal file
347
mini_program/staff-mp/src/pages/system/user-management.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<script setup lang="ts">
|
||||
// 用户管理页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const users = ref([
|
||||
{ id: '1', name: '张三', role: '采购人', phone: '13800138001', status: 'active', createTime: '2025-09-01' },
|
||||
{ id: '2', name: '李四', role: '供应商', phone: '13800138002', status: 'active', createTime: '2025-09-02' },
|
||||
{ id: '3', name: '王五', role: '司机', phone: '13800138003', status: 'active', createTime: '2025-09-03' },
|
||||
{ id: '4', name: '赵六', role: '员工', phone: '13800138004', status: 'inactive', createTime: '2025-09-04' }
|
||||
]);
|
||||
|
||||
const roles = ref(['采购人', '供应商', '司机', '员工']);
|
||||
|
||||
const newUser = ref({
|
||||
name: '',
|
||||
role: '',
|
||||
phone: ''
|
||||
});
|
||||
|
||||
const showAddUser = ref(false);
|
||||
|
||||
const loadUsers = async () => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取用户数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
const handleAddUser = () => {
|
||||
if (!newUser.value.name || !newUser.value.role || !newUser.value.phone) {
|
||||
uni.showToast({
|
||||
title: '请填写完整信息',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 模拟添加用户
|
||||
const user = {
|
||||
id: String(users.value.length + 1),
|
||||
name: newUser.value.name,
|
||||
role: newUser.value.role,
|
||||
phone: newUser.value.phone,
|
||||
status: 'active',
|
||||
createTime: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
|
||||
users.value.push(user);
|
||||
|
||||
// 重置表单
|
||||
newUser.value = {
|
||||
name: '',
|
||||
role: '',
|
||||
phone: ''
|
||||
};
|
||||
|
||||
showAddUser.value = false;
|
||||
|
||||
uni.showToast({
|
||||
title: '用户添加成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleStatus = (userId: string) => {
|
||||
const user = users.value.find(u => u.id === userId);
|
||||
if (user) {
|
||||
user.status = user.status === 'active' ? 'inactive' : 'active';
|
||||
uni.showToast({
|
||||
title: `用户已${user.status === 'active' ? '启用' : '禁用'}`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUser = (userId: string) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此用户吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
users.value = users.value.filter(u => u.id !== userId);
|
||||
uni.showToast({
|
||||
title: '用户已删除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">用户管理</text>
|
||||
</view>
|
||||
|
||||
<view class="actions-bar">
|
||||
<button type="primary" @click="showAddUser = true">添加用户</button>
|
||||
</view>
|
||||
|
||||
<view class="users-list">
|
||||
<view v-for="user in users" :key="user.id" class="user-item">
|
||||
<view class="user-info">
|
||||
<view class="user-name">{{ user.name }}</view>
|
||||
<view class="user-role">{{ user.role }}</view>
|
||||
<view class="user-phone">{{ user.phone }}</view>
|
||||
</view>
|
||||
<view class="user-meta">
|
||||
<view class="user-status" :class="user.status">
|
||||
{{ user.status === 'active' ? '正常' : '禁用' }}
|
||||
</view>
|
||||
<view class="user-time">{{ user.createTime }}</view>
|
||||
</view>
|
||||
<view class="user-actions">
|
||||
<button size="mini" @click="handleToggleStatus(user.id)">
|
||||
{{ user.status === 'active' ? '禁用' : '启用' }}
|
||||
</button>
|
||||
<button size="mini" type="warn" @click="handleDeleteUser(user.id)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加用户弹窗 -->
|
||||
<view v-if="showAddUser" class="modal-overlay" @click="showAddUser = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">添加用户</text>
|
||||
<text class="modal-close" @click="showAddUser = false">×</text>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="form-item">
|
||||
<text class="label">姓名</text>
|
||||
<input v-model="newUser.name" class="input" placeholder="请输入姓名" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">角色</text>
|
||||
<picker :range="roles" @change="e => newUser.role = roles[e.detail.value]">
|
||||
<view class="picker">
|
||||
{{ newUser.role || '请选择角色' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input v-model="newUser.phone" type="number" class="input" placeholder="请输入手机号" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<button @click="showAddUser = false">取消</button>
|
||||
<button type="primary" @click="handleAddUser">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
margin-bottom: 20rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.users-list {
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
|
||||
.user-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 24rpx;
|
||||
color: #1989fa;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.user-phone {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
width: 120rpx;
|
||||
text-align: center;
|
||||
|
||||
.user-status {
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.active {
|
||||
color: #3cc51f;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
color: #ff9900;
|
||||
}
|
||||
}
|
||||
|
||||
.user-time {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
width: 160rpx;
|
||||
|
||||
button {
|
||||
&:first-child {
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
border-radius: 8rpx;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
|
||||
.modal-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.picker {
|
||||
height: 70rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -19,6 +19,18 @@ const loadTransports = async () => {
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleCallDriver = (phone: string) => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewTrack = (transportId: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/transport/transport-track?id=${transportId}`
|
||||
});
|
||||
};
|
||||
|
||||
loadTransports();
|
||||
</script>
|
||||
|
||||
|
||||
264
mini_program/staff-mp/src/pages/transport/transport-track.vue
Normal file
264
mini_program/staff-mp/src/pages/transport/transport-track.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<script setup lang="ts">
|
||||
// 运输轨迹页面
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const trackPoints = ref([
|
||||
{ time: '2025-09-15 09:00', location: '北京市朝阳区某某牛场', status: '装车完成' },
|
||||
{ time: '2025-09-15 10:30', location: '北京市朝阳区某某路', status: '运输中' },
|
||||
{ time: '2025-09-15 12:15', location: '北京市通州区某某高速', status: '运输中' },
|
||||
{ time: '2025-09-15 14:00', location: '北京市通州区某某屠宰场', status: '已到达' }
|
||||
]);
|
||||
|
||||
const transportInfo = ref({
|
||||
orderNo: 'ORDER2025001',
|
||||
driver: '张师傅',
|
||||
phone: '13800138000',
|
||||
vehicleNo: '京A12345',
|
||||
departure: '北京市朝阳区某某牛场',
|
||||
destination: '北京市通州区某某屠宰场',
|
||||
departureTime: '2025-09-15 09:00',
|
||||
arrivalTime: '2025-09-15 14:00'
|
||||
});
|
||||
|
||||
// 获取运输ID
|
||||
onLoad((options) => {
|
||||
if (options?.id) {
|
||||
loadTransportTrack(options.id);
|
||||
}
|
||||
});
|
||||
|
||||
const loadTransportTrack = async (transportId: string) => {
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 实际项目中这里会从接口获取轨迹数据
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleCallDriver = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: transportInfo.value.phone
|
||||
});
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="header">
|
||||
<text class="title">运输轨迹</text>
|
||||
</view>
|
||||
|
||||
<view class="transport-info-section">
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="label">订单号:</text>
|
||||
<text class="value">{{ transportInfo.orderNo }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">司机:</text>
|
||||
<text class="value">{{ transportInfo.driver }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">电话:</text>
|
||||
<text class="value">{{ transportInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">车牌号:</text>
|
||||
<text class="value">{{ transportInfo.vehicleNo }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">出发地:</text>
|
||||
<text class="value">{{ transportInfo.departure }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">目的地:</text>
|
||||
<text class="value">{{ transportInfo.destination }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">出发时间:</text>
|
||||
<text class="value">{{ transportInfo.departureTime }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">到达时间:</text>
|
||||
<text class="value">{{ transportInfo.arrivalTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-button">
|
||||
<button size="mini" @click="handleCallDriver">联系司机</button>
|
||||
<button size="mini" @click="handleRefresh">刷新</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="map-section">
|
||||
<view class="map-container">
|
||||
<text class="map-placeholder">地图组件 - 显示运输轨迹</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="track-section">
|
||||
<view class="section-title">轨迹详情</view>
|
||||
<view class="track-timeline">
|
||||
<view v-for="(point, index) in trackPoints" :key="index" class="track-point">
|
||||
<view class="point-time">{{ point.time }}</view>
|
||||
<view class="point-marker">
|
||||
<view class="marker-icon"></view>
|
||||
<view class="marker-line" v-if="index < trackPoints.length - 1"></view>
|
||||
</view>
|
||||
<view class="point-content">
|
||||
<text class="point-location">{{ point.location }}</text>
|
||||
<text class="point-status">{{ point.status }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 20rpx 0;
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.transport-info-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.info-item {
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 20rpx;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.map-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.map-container {
|
||||
height: 300rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.map-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.track-section {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 10rpx;
|
||||
border-left: 6rpx solid #1989fa;
|
||||
}
|
||||
}
|
||||
|
||||
.track-timeline {
|
||||
.track-point {
|
||||
display: flex;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.point-time {
|
||||
width: 160rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
|
||||
.point-marker {
|
||||
position: relative;
|
||||
width: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.marker-icon {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
background: #1989fa;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.marker-line {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
width: 2rpx;
|
||||
height: 100%;
|
||||
background: #1989fa;
|
||||
}
|
||||
}
|
||||
|
||||
.point-content {
|
||||
flex: 1;
|
||||
padding-left: 20rpx;
|
||||
|
||||
.point-location {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.point-status {
|
||||
font-size: 24rpx;
|
||||
color: #1989fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user