新增CSS动画效果和交互样式优化

This commit is contained in:
2025-09-18 12:41:58 +08:00
parent ff9f0d48c6
commit 19ca52d06c
15 changed files with 4171 additions and 1 deletions

View File

@@ -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": {

View File

@@ -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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>