master
kkk-ops 2 months ago
parent d8ffff6bd9
commit 15025df2f5

@ -1,5 +1,4 @@
<script>
export default {
onLaunch: function () {
console.log('App Launch')
@ -16,4 +15,4 @@ export default {
<style lang="scss">
@import "uview-plus/index.scss";
@import '@/static/scss/index.scss';
</style>
</style>

@ -1,7 +1,7 @@
// 应用全局配置
const config = {
// baseUrl: 'http://47.106.185.127:48080',127.0.0.1
baseUrl: 'http://47.106.185.127:48080',
baseUrl: 'http://localhost:48081',
// 应用信息
appInfo: {
// 应用名称

@ -15,8 +15,8 @@
{
"path": "pages/index",
"style": {
"navigationBarTitleText": "生产计划",
"enablePullDownRefresh": true
"navigationBarTitleText": "首页",
"enablePullDownRefresh": false
}
},
{
@ -28,8 +28,8 @@
{
"path": "pages/work",
"style": {
"navigationBarTitleText": "近三天投料记录",
"enablePullDownRefresh": true
"navigationBarTitleText": "管理",
"enablePullDownRefresh": false
}
},
@ -54,8 +54,8 @@
{
"path": "pages/report",
"style": {
"navigationBarTitleText": "生产报工",
"enablePullDownRefresh": true
"navigationBarTitleText": "报表",
"enablePullDownRefresh": false
}
}
],
@ -277,11 +277,142 @@
"path": "code/index"
}
]
},
{
"root": "pages_function/pages",
"pages": [
{
"path": "mold/index",
"style": {
"navigationBarTitleText": "模具查询",
"navigationStyle": "custom"
}
},
{
"path": "mold/detail",
"style": {
"navigationBarTitleText": "模具详情",
"navigationStyle": "custom"
}
},
{
"path": "equipment/index",
"style": {
"navigationBarTitleText": "设备查询",
"navigationStyle": "custom"
}
},
{
"path": "equipment/detail",
"style": {
"navigationBarTitleText": "设备详情",
"navigationStyle": "custom"
}
},
{
"path": "spare/index",
"style": {
"navigationBarTitleText": "备件查询",
"navigationStyle": "custom"
}
},
{
"path": "spare/detail",
"style": {
"navigationBarTitleText": "备件详情",
"navigationStyle": "custom"
}
},
{
"path": "keypart/index",
"style": {
"navigationBarTitleText": "关键件查询",
"navigationStyle": "custom"
}
},
{
"path": "keypart/detail",
"style": {
"navigationBarTitleText": "关键件详情",
"navigationStyle": "custom"
}
},
{
"path": "warehouse/index",
"style": {
"navigationBarTitleText": "仓库信息",
"navigationStyle": "custom"
}
},
{
"path": "inspection/index",
"style": {
"navigationBarTitleText": "检验类型",
"navigationStyle": "custom"
}
},
{
"path": "inspectionItem/index",
"style": {
"navigationBarTitleText": "检验项库",
"navigationStyle": "custom"
}
},
{
"path": "inspectionTemplate/index",
"style": {
"navigationBarTitleText": "检验模板",
"navigationStyle": "custom"
}
},
{
"path": "materialCategory/index",
"style": {
"navigationBarTitleText": "产品物料分类",
"navigationStyle": "custom"
}
},
{
"path": "materialInfo/index",
"style": {
"navigationBarTitleText": "产品物料信息",
"navigationStyle": "custom"
}
},
{
"path": "productBom/index",
"style": {
"navigationBarTitleText": "产品BOM",
"navigationStyle": "custom"
}
},
{
"path": "equipmentCategory/index",
"style": {
"navigationBarTitleText": "设备分类",
"navigationStyle": "custom"
}
},
{
"path": "equipmentLedger/index",
"style": {
"navigationBarTitleText": "设备台账",
"navigationStyle": "custom"
}
},
{
"path": "equipmentKeypart/index",
"style": {
"navigationBarTitleText": "设备关键件",
"navigationStyle": "custom"
}
}
]
}
],
"tabBar": {
"color": "#000000",
"selectedColor": "#000000",
"color": "#666666",
"selectedColor": "#1a3a5c",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [
@ -289,20 +420,20 @@
"pagePath": "pages/index",
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home_.png",
"text": "计划"
"text": "首页"
},
{
"pagePath": "pages/report",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "报工"
"text": "报表"
},
{
"pagePath": "pages/work",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "管理"
},
{
"pagePath": "pages/work",
"iconPath": "static/images/tabbar/work.png",
"selectedIconPath": "static/images/tabbar/work_.png",
"text": "投料"
},
{
"pagePath": "pages/mine",
"iconPath": "static/images/tabbar/mine.png",
@ -312,8 +443,8 @@
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "BESURE",
"navigationBarBackgroundColor": "#FFFFFF"
"navigationBarBackgroundColor": "#1a3a5c"
}
}

@ -1,232 +1,687 @@
<template>
<view >
<uni-notice-bar show-icon scrollable text="安全生产!有序生产!高效生产!" />
<uni-section title="开工中" type="line" title-color="#18bc37"> </uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in startList" :name="item.id">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{item.code}}</el-text>-
<el-text size="large">{{item.productName}}</el-text>
</template>
<el-row>
<el-col :span="12">
<el-text type="success">计划开始:{{ timestampToTime(item.planStartTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="success">计划数:{{ item.planNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="success" plain @click="updatePlan(item.id, item.code,'end')">完工</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-text type="warning">计划结束:{{ timestampToTime(item.planEndTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="warning">入库数:{{ item.finishNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="warning" plain @click="updatePlan(item.id,item.code,'pause')"></el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="20">
<el-text>备注:{{ item.remark }}</el-text>
</el-col>
<el-col :span="4">
<el-button type="info" plain @click="planProgress(item)"></el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<!-- 派工中-->
<uni-section title="派工中" type="line" title-color="#2979ff"></uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in paigongList" :name="item.id">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{item.code}}</el-text>-
<el-text size="large">{{item.productName}}</el-text>
</template>
<el-row>
<el-col :span="12">
<el-text type="success">计划开始:{{ timestampToTime(item.planStartTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="success">计划数:{{ item.planNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="info" plain @click="updatePlan(item.id,item.code, 'start')">开工</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-text type="warning">计划结束:{{ timestampToTime(item.planEndTime) }}</el-text>
</el-col>
</el-row>
<el-text>备注:{{ item.remark }}</el-text>
</el-collapse-item>
</el-collapse>
<!-- 计划中-->
<uni-section title="计划中" type="line" title-color="#f3a73f"></uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in planList" :name="item.id">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{item.code}}</el-text>-
<el-text size="large">{{item.productName}}</el-text>
</template>
<el-row>
<el-col :span="12">
<el-text type="success">计划开始:{{ timestampToTime(item.planStartTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="success">计划数:{{ item.planNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="primary" plain>派工</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-text type="warning">计划结束:{{ timestampToTime(item.planEndTime) }}</el-text>
</el-col>
</el-row>
<el-text>备注:{{ item.remark }}</el-text>
</el-collapse-item>
</el-collapse>
<!-- 暂停中 -->
<uni-section title="暂停中" type="line" title-color="#e43d33"></uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in pauseList" :name="item.id">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{item.code}}</el-text>-
<el-text size="large">{{item.productName}}</el-text>
</template>
<el-row>
<el-col :span="12">
<el-text type="success">计划开始:{{ timestampToTime(item.planStartTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="success">计划数:{{ item.planNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="success" plain @click="updatePlan(item.id,item.code, 'end')">完工</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-text type="warning">计划结束:{{ timestampToTime(item.planEndTime) }}</el-text>
</el-col>
<el-col :span="8">
<el-text type="warning">入库数:{{ item.finishNumber }}</el-text>
</el-col>
<el-col v-if="auth.hasPermi('mes:plan:update')" :span="4">
<el-button type="info" plain @click="updatePlan(item.id, item.code,'start')">开工</el-button>
</el-col>
</el-row>
<el-text>备注:{{ item.remark }}</el-text>
</el-collapse-item>
</el-collapse>
<!-- <view class="charts-box">-->
<!-- <qiun-data-charts :chartData="chartData" type="column"/>-->
<!-- </view>-->
<view class="page-container">
<scroll-view scroll-y class="main-scroll" @scroll="onScroll">
<view class="banner-section">
<view class="banner-bg">
<view class="banner-content">
<text class="banner-title">欢迎您使用</text>
<text class="banner-subtitle">必硕数字化智能中控平台</text>
</view>
<view class="banner-decoration">
<view class="deco-line"></view>
<view class="deco-dot"></view>
</view>
</view>
<view class="bell-icon" @click="showTodoList">
<text class="iconfont icon-bell"></text>
<view v-if="todoCount > 0" class="bell-badge">{{ todoCount }}</view>
</view>
</view>
<view class="content-section">
<view class="nav-section">
<view class="section-title">功能导航</view>
<view class="nav-grid">
<view
v-for="(item, index) in navList"
:key="index"
class="nav-item"
@click="handleNavClick(item)"
>
<view class="nav-icon" :style="{ backgroundColor: item.bgColor }">
<text class="nav-icon-text">{{ item.icon }}</text>
</view>
<text class="nav-text">{{ item.name }}</text>
</view>
</view>
</view>
<view class="stats-section">
<view class="section-title">生产整体概况</view>
<view class="stats-grid">
<view
v-for="(stat, index) in statsData"
:key="index"
class="stat-card"
:class="'stat-' + stat.type"
>
<text class="stat-value">{{ stat.value }}</text>
<text class="stat-label">{{ stat.label }}</text>
</view>
</view>
</view>
<view class="plan-section">
<view class="section-header">
<text class="section-title">生产计划</text>
<text class="section-more" @click="viewMorePlans"> </text>
</view>
<view class="plan-list">
<view
v-for="(plan, index) in planList"
:key="index"
class="plan-card"
@click="handlePlanClick(plan)"
>
<view class="plan-header">
<text class="plan-code">{{ plan.code }}</text>
<view class="plan-status" :class="'status-' + plan.statusType">
<text>{{ plan.status }}</text>
</view>
</view>
<view class="plan-body">
<view class="plan-row">
<text class="plan-label">产品名称</text>
<text class="plan-value">{{ plan.productName }}</text>
</view>
<view class="plan-row">
<text class="plan-label">生产线</text>
<text class="plan-value">{{ plan.lineName }}</text>
</view>
<view class="plan-row">
<text class="plan-label">计划数量</text>
<text class="plan-value plan-num">{{ plan.quantity }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<uni-popup ref="todoPopup" type="right" background-color="#fff">
<view class="todo-popup">
<view class="todo-header">
<view class="todo-back" @click="closeTodoList">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="todo-title">待办任务</text>
</view>
<scroll-view scroll-y class="todo-scroll">
<view v-if="todoList.length === 0" class="todo-empty">
<text class="empty-text">暂无待办任务</text>
</view>
<view v-else>
<view
v-for="(item, index) in todoList"
:key="index"
class="todo-item"
>
<view class="todo-dot"></view>
<view class="todo-content">
<text class="todo-name">{{ item.name }}</text>
<text class="todo-time">{{ item.time }}</text>
</view>
<view class="todo-priority" :class="'priority-' + item.priority">
<text>{{ item.priorityText }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {ref, onMounted} from 'vue';
import useUserStore from '@/store/modules/user'
import {getPage, getById, paigong, updateStatus, getByStatus} from "@/api/mes/plan"
import {timestampToTime} from "@/utils/dateUtil";
import {Tickets} from "@element-plus/icons-vue";
import modal from "@/plugins/modal";
import tab from "@/plugins/tab";
import {showConfirm} from "@/utils/common";
import auth from "@/plugins/auth";
const userStore = useUserStore()
const startList = ref([]);
const paigongList = ref([]);
const pauseList = ref([]);
const planList = ref([]);
const chartData = ref({});
onMounted(() => {
getServerData()
getPlanList()
});
function getServerData() {
//
setTimeout(() => {
let res = {
categories: ['2016', '2017', '2018', '2019', '2020', '2021'],
series: [
{
name: '目标值',
data: [35, 36, 31, 33, 13, 34],
},
{
name: '完成量',
data: [18, 27, 21, 24, 6, 28],
},
],
};
chartData.value = JSON.parse(JSON.stringify(res));
}, 500);
}
//
function getPlanList() {
getByStatus(0).then(response => {
planList.value = response.data
})
getByStatus(1).then(response => {
paigongList.value = response.data
})
getByStatus(2).then(response => {
startList.value = response.data
})
getByStatus(3).then(response => {
pauseList.value = response.data
})
}
/** 开工 */
function updatePlan(id, planCode, type){
let content = '确定['
if(type==='start')content= content+'开工]' + planCode +'?'
else if(type==='end')content= content+'完工]'+ planCode +'?'
else if(type==='pause')content= content+'暂停]'+ planCode +'?'
showConfirm(content).then(res => {
if (res.confirm) {
const data = {'id':id, 'code':type}
updateStatus(data).then(response => {
console.log(response)
getPlanList()
})
modal.msgSuccess("操作成功")
}
})
}
//
function planProgress(plan){
tab.navigateTo('/page_report/planProgress',plan)
import { ref, reactive } from 'vue';
const todoPopup = ref(null);
const todoCount = ref(5);
const navList = reactive([
{ name: '模具', icon: '🔧', bgColor: '#1a3a5c', path: '/pages_function/mold' },
{ name: '设备', icon: '⚙️', bgColor: '#2d5a87', path: '/pages_function/equipment' },
{ name: '关键件', icon: '🔩', bgColor: '#3d7ab5', path: '/pages_function/keypart' },
{ name: '备件', icon: '📦', bgColor: '#4a90c2', path: '/pages_function/spare' },
{ name: '出入库', icon: '📊', bgColor: '#5aa0d2', path: '/pages_function/warehouse' }
]);
const statsData = reactive([
{ label: '总数', value: 128, type: 'total' },
{ label: '未开工', value: 24, type: 'pending' },
{ label: '生产中', value: 86, type: 'running' },
{ label: '完工', value: 18, type: 'finished' }
]);
const planList = reactive([
{ code: 'PO-20240312-001', status: '生产中', statusType: 'running', productName: '汽车零部件A型', lineName: '一号生产线', quantity: 1000 },
{ code: 'PO-20240312-002', status: '未开工', statusType: 'pending', productName: '精密齿轮组件', lineName: '二号生产线', quantity: 500 },
{ code: 'PO-20240311-003', status: '完工', statusType: 'finished', productName: '电机外壳套件', lineName: '三号生产线', quantity: 2000 },
{ code: 'PO-20240311-004', status: '生产中', statusType: 'running', productName: '传动轴总成', lineName: '一号生产线', quantity: 800 }
]);
const todoList = reactive([
{ name: '生产计划审批', time: '2024-03-12 10:30', priority: 'high', priorityText: '紧急' },
{ name: '设备维护确认', time: '2024-03-12 11:00', priority: 'medium', priorityText: '一般' },
{ name: '物料领用审核', time: '2024-03-12 14:00', priority: 'medium', priorityText: '一般' },
{ name: '质量异常处理', time: '2024-03-12 15:30', priority: 'high', priorityText: '紧急' },
{ name: '完工报告确认', time: '2024-03-12 16:00', priority: 'low', priorityText: '普通' }
]);
function showTodoList() {
todoPopup.value.open();
}
function closeTodoList() {
todoPopup.value.close();
}
function handleNavClick(item) {
const navMap = {
'模具': '/pages_function/pages/mold/index',
'设备': '/pages_function/pages/equipment/index',
'备件': '/pages_function/pages/spare/index',
'关键件': '/pages_function/pages/keypart/index'
};
const url = navMap[item.name];
if (url) {
uni.navigateTo({ url });
} else {
uni.showToast({
title: `进入${item.name}模块`,
icon: 'none'
});
}
}
function handlePlanClick(plan) {
uni.showToast({
title: `查看计划: ${plan.code}`,
icon: 'none'
});
}
function viewMorePlans() {
uni.showToast({
title: '查看更多计划',
icon: 'none'
});
}
function onScroll(e) {
}
</script>
<style scoped>
<style lang="scss" scoped>
.page-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f0f2f5;
}
.main-scroll {
flex: 1;
height: 100%;
}
.title {
font-size: 36rpx;
color: #8f8f94;
.banner-section {
position: relative;
height: 320rpx;
}
.charts-box {
.banner-bg {
width: 100%;
height: 300px;
height: 100%;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 50%, #3d7ab5 100%);
position: relative;
overflow: visible;
&::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 300rpx;
height: 300rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
bottom: -30%;
left: 10%;
width: 200rpx;
height: 200rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 50%;
}
}
.banner-content {
position: relative;
z-index: 2;
padding: 60rpx 40rpx;
.banner-title {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 16rpx;
}
.banner-subtitle {
display: block;
font-size: 40rpx;
font-weight: bold;
color: #ffffff;
line-height: 1.4;
}
}
.banner-decoration {
position: absolute;
bottom: 40rpx;
left: 40rpx;
display: flex;
align-items: center;
.deco-line {
width: 60rpx;
height: 4rpx;
background: #ff8c00;
border-radius: 2rpx;
}
.deco-dot {
width: 12rpx;
height: 12rpx;
background: #ff8c00;
border-radius: 50%;
margin-left: 16rpx;
}
}
.bell-icon {
position: absolute;
top: 30rpx;
right: 30rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
.iconfont {
font-size: 48rpx;
color: #ff4d4f;
}
.bell-badge {
position: absolute;
top: -8rpx;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
background: #ff4d4f;
border-radius: 16rpx;
font-size: 20rpx;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
border: 2rpx solid #ffffff;
}
}
.content-section {
padding: 0 24rpx 24rpx;
margin-top: -40rpx;
position: relative;
z-index: 5;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.section-more {
font-size: 26rpx;
color: #666666;
}
}
.nav-section {
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.nav-grid {
display: flex;
justify-content: space-around;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.7;
}
}
.nav-icon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.nav-icon-text {
font-size: 44rpx;
}
}
.nav-text {
font-size: 26rpx;
color: #333333;
}
.stats-section {
margin-bottom: 24rpx;
}
.stats-grid {
display: flex;
justify-content: space-between;
}
.stat-card {
flex: 1;
background: #ffffff;
border-radius: 16rpx;
padding: 28rpx 16rpx;
margin: 0 8rpx;
text-align: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&:active {
transform: scale(0.98);
}
}
.stat-value {
display: block;
font-size: 48rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.stat-label {
display: block;
font-size: 24rpx;
color: #666666;
}
.stat-total {
border-left: 6rpx solid #1a3a5c;
.stat-value {
color: #1a3a5c;
}
}
.stat-pending {
border-left: 6rpx solid #ff8c00;
.stat-value {
color: #ff8c00;
}
}
.stat-running {
border-left: 6rpx solid #18bc37;
.stat-value {
color: #18bc37;
}
}
.stat-finished {
border-left: 6rpx solid #4a90c2;
.stat-value {
color: #4a90c2;
}
}
.plan-section {
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.plan-list {
display: flex;
flex-direction: column;
}
.plan-card {
background: #f8fafc;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
&:active {
background: #e8f4ff;
}
}
.plan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.plan-code {
font-size: 28rpx;
font-weight: 600;
color: #1a3a5c;
}
.plan-status {
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 22rpx;
}
.status-pending {
background: rgba(255, 140, 0, 0.15);
color: #ff8c00;
}
.status-running {
background: rgba(24, 188, 55, 0.15);
color: #18bc37;
}
.status-finished {
background: rgba(74, 144, 194, 0.15);
color: #4a90c2;
}
.plan-body {
display: flex;
flex-direction: column;
}
.plan-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
}
.plan-label {
font-size: 26rpx;
color: #999999;
}
.plan-value {
font-size: 26rpx;
color: #333333;
}
.plan-num {
font-weight: 600;
color: #1a3a5c;
}
.todo-popup {
width: 600rpx;
height: 100vh;
background: #ffffff;
}
.todo-header {
display: flex;
align-items: center;
padding: 40rpx 30rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.todo-back {
display: flex;
align-items: center;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.todo-title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: 600;
color: #ffffff;
margin-right: 80rpx;
}
.todo-scroll {
height: calc(100vh - 140rpx);
background: #f5f7fa;
}
.todo-empty {
display: flex;
align-items: center;
justify-content: center;
height: 400rpx;
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
.todo-item {
display: flex;
align-items: center;
background: #ffffff;
padding: 28rpx 30rpx;
margin-bottom: 2rpx;
&:active {
background: #f5f7fa;
}
}
.todo-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #1a3a5c;
margin-right: 20rpx;
}
.todo-content {
flex: 1;
.todo-name {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
}
.todo-time {
display: block;
font-size: 24rpx;
color: #999999;
}
}
.todo-priority {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 22rpx;
}
.priority-high {
background: rgba(255, 77, 79, 0.1);
color: #ff4d4f;
}
.priority-medium {
background: rgba(255, 140, 0, 0.1);
color: #ff8c00;
}
.priority-low {
background: rgba(24, 188, 55, 0.1);
color: #18bc37;
}
</style>

@ -3,7 +3,7 @@
<view class="logo-content align-center justify-center flex">
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
</image>
<text class="title">生产运营管理系统</text>
<text class="title">数字化智能中控平台</text>
</view>
<view class="login-form-content">
<view class="input-item flex align-center">
@ -25,12 +25,7 @@
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
</view>
</view>
<view class="xieyi text-center">
<text class="text-grey1">登录即代表同意</text>
<text class="text-blue">用户协议</text>
<text class="text-blue">隐私协议</text>
</view>
</view>
</template>
<script setup>

@ -81,7 +81,6 @@
</uni-popup>
</view>
</view>
</template>
<script setup>
@ -150,6 +149,13 @@ function handleHelp() {
uni.navigateTo({
url: '/pages_mine/pages/help/index'
});
}
function switchTab(index) {
const paths = ['/pages/index', '/pages/report', '/pages/work', '/pages/mine'];
if (index !== 3) {
uni.switchTab({ url: paths[index] });
}
};
function handleAbout() {
uni.navigateTo({
@ -179,7 +185,7 @@ page {
.header-section {
padding: 15px 15px 45px 15px;
background-color: #3c96f3;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
color: white;
.login-tip {

@ -1,321 +1,357 @@
<template>
<view class="container">
<view class="example">
<el-tabs v-loading="loading" v-model="activeName" class="demo-tabs" @tab-click="handleTabClick">
<el-tab-pane label="个人报工" name="first">
<uni-list :border="true">
<uni-list-item v-for="(item, index) in reportList">
<!-- 自定义 header -->
<template v-slot:header>
<div onclick="">
<text class="u-success"> {{timestampToTime(item.reportDate)}}</text>
<text class="u-primary">/</text>
{{item.userName}}
<text class="u-primary">/</text>
{{item.orgName}}
</div>
</template>
<!-- 自定义 body -->
<template v-slot:body>
</template>
<!-- 自定义 footer-->
<template v-slot:footer>
<el-button v-if="item.reportStatus === 0" type="primary" size="small" :icon="Edit" circle @click="editOrAddReport(item.id)"/>
<el-button v-if="item.reportStatus === 0" type="success" size="small" :icon="Check" circle @click="updateReport(item.id)"/>
<el-button v-if="item.reportStatus < 2" type="danger" size="small" :icon="Delete" circle @click="deleteReport(item.id)"/>
</template>
</uni-list-item>
</uni-list>
<uni-fab ref="fab" @fabClick="editOrAddReport" />
</el-tab-pane>
<el-tab-pane label="代报工" name="second" v-if="auth.hasPermi('mes:produce-report-detail:replace')">
<uni-fab :pattern="pattern" ref="fabReplace" @fabClick="addReplaceReport" />
<uni-forms ref="valiReplaceForm" :model="valiFormData">
<uni-row>
<uni-col :xs="14" :sm="12" :md="6" :lg="3" :xl="1">
<uni-forms-item label-width="50px" label-align="left" label="日期" name="reportDateString">
<uni-datetime-picker v-model="valiFormData.reportDateString" type="date" :clear-icon="true" @change="maskClick" />
</uni-forms-item>
</uni-col>
<uni-col :xs="10" :sm="8" :md="6" :lg="3" :xl="1">
<uni-forms-item label-width="50px" label-align="left" label="工人" name="userId">
<uni-data-select v-model="valiFormData.userId" :localdata="userList" placement="top" @change="handleReplaceReportChange()">
</uni-data-select>
</uni-forms-item>
</uni-col>
</uni-row>
<uni-forms-item label="工序" name="orgType">
<uni-data-checkbox v-model="valiFormData.orgType" :localdata="processTypes()" @change="handleReplaceReportChange()"/>
</uni-forms-item>
</uni-forms>
<uni-list :border="true">
<uni-list-item v-for="(item, index) in replaceReportList">
<!-- 自定义 header -->
<template v-slot:header>
<div onclick="">
<text class="u-success"> {{timestampToTime(item.reportDate)}}</text>
<text class="u-primary">/</text>
{{item.userName}}
<text class="u-primary">/</text>
{{item.orgName}}
</div>
</template>
<!-- 自定义 body -->
<template v-slot:body>
</template>
<!-- 自定义 footer-->
<template v-slot:footer>
<el-button v-if="item.reportStatus === 0" type="primary" size="small" :icon="Edit" circle @click="addReplaceReport(item.id)"/>
<el-button v-if="item.reportStatus === 0" type="success" size="small" :icon="Promotion" circle @click="updateReport(item.id,1,'提交')"/>
<el-button v-if="item.reportStatus === 1" type="success" size="small" :icon="Check" circle @click="updateReport(item.id,2,'通过')"/>
<el-button v-if="item.reportStatus < 2" type="danger" size="small" :icon="Delete" circle @click="deleteReport(item.id)"/>
</template>
</uni-list-item>
</uni-list>
</el-tab-pane>
<el-tab-pane label="个人报工审核" name="third" v-if="auth.hasPermi('mes:produce-report-detail:replace')">
<uni-forms ref="valiForm" :model="valiFormData">
<uni-row>
<uni-col :xs="14" :sm="12" :md="6" :lg="3" :xl="1">
<uni-forms-item label-width="50px" label-align="left" label="日期" name="reportDateString">
<uni-datetime-picker v-model="valiFormData.reportDateString" type="date" :clear-icon="true" @change="maskClick" />
</uni-forms-item>
</uni-col>
<uni-col :xs="10" :sm="8" :md="6" :lg="3" :xl="1">
<uni-forms-item label-width="50px" label-align="left" label="工人" name="userId">
<uni-data-select v-model="valiFormData.userId" :localdata="userList" placement="top" @change="handleOtherReportChange()">
</uni-data-select>
</uni-forms-item>
</uni-col>
</uni-row>
<uni-forms-item label="工序" name="orgType">
<uni-data-checkbox v-model="valiFormData.orgType" :localdata="processTypes()" @change="handleOtherReportChange()"/>
</uni-forms-item>
</uni-forms>
<view class="page-container">
<view class="header-section">
<text class="header-title">报表中心</text>
<text class="header-subtitle">数据驱动决策 · 智能分析</text>
</view>
<uni-list :border="true">
<uni-list-item v-for="(item, index) in otherReportList">
<!-- 自定义 header -->
<template v-slot:header>
<div onclick="">
<text class="u-success"> {{timestampToTime(item.reportDate)}}</text>
<text class="u-primary">/</text>
{{item.userName}}
<text class="u-primary">/</text>
{{item.orgName}}
</div>
</template>
<!-- 自定义 body -->
<template v-slot:body>
</template>
<!-- 自定义 footer-->
<template v-slot:footer>
<el-button v-if="item.reportStatus === 0" type="primary" size="small" :icon="Edit" circle @click="addReplaceReport(item.id)"/>
<el-button v-if="item.reportStatus === 0" type="success" size="small" :icon="Promotion" circle @click="updateReport(item.id,1,'提交')"/>
<el-button v-if="item.reportStatus === 1" type="success" size="small" :icon="Check" circle @click="updateReport(item.id,2,'通过')"/>
<el-button v-if="item.reportStatus === 1" type="warning" size="small" :icon="Close" circle @click="updateReport(item.id,3,'驳回')"/>
<el-button v-if="item.reportStatus < 2" type="danger" size="small" :icon="Delete" circle @click="deleteReport(item.id)"/>
</template>
</uni-list-item>
</uni-list>
<view>
<div></div>
<scroll-view scroll-y class="content-scroll">
<!-- 生产报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #1a3a5c;">
<text class="icon-text">🏭</text>
</view>
<text class="module-title">生产报表</text>
</view>
</el-tab-pane>
<el-tab-pane label="报工报表" name="four">报工报表</el-tab-pane>
</el-tabs>
</view>
</view>
</template>
<view class="function-grid">
<view class="function-item" @click="handleClick('生产日报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">生产日报</text>
</view>
<view class="function-item" @click="handleClick('生产周报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">生产周报</text>
</view>
<view class="function-item" @click="handleClick('生产月报')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📅</text>
</view>
<text class="function-name">生产月报</text>
</view>
<view class="function-item" @click="handleClick('产能分析')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">产能分析</text>
</view>
</view>
</view>
<script>
import {getMyList, getOtherList, getById, updateStatus,deleteByReportId } from "@/api/mes/report";
import {Check, Delete, Edit,Promotion,Close} from '@element-plus/icons-vue'
import tab from "@/plugins/tab";
import modal from "@/plugins/modal";
import auth from "@/plugins/auth";
import {showConfirm} from "@/utils/common";
import {getCurrentDate, timestampToTime} from "@/utils/dateUtil";
import {processTypes} from "@/api/system/dict/data";
import {getOtherPersonalUser} from "@/api/mes/organization";
export default {
computed: {
auth() {return auth},
Delete() {return Delete},
Promotion() {return Promotion},
Close() {return Close},
Edit() {return Edit},
Check() {return Check}
},
data() {
return {
loading: false,
reportList: [],
otherReportList: [],
replaceReportList: [],
userList: [],
pattern: {
color: '#7A7E83',
backgroundColor: '#fff',
selectedColor: '#007AFF',
buttonColor: '#e66126',
iconColor: '#fff'
},
activeName: 'first',
valiFormData: {
reportDate: [],
reportDateString: getCurrentDate(),
userId: '',
orgType: 'chengxing',
reportType: '个人',
},
};
},
onLoad() {
this.loading = true
this.getList();
this.getUserList()
this.loading = false
},
onPullDownRefresh() {
console.log('refresh');
setTimeout(function () {
uni.stopPullDownRefresh();
}, 1000);
},
methods: {
processTypes() {
return processTypes
},
handleTabClick(tab) {
this.loading = true
console.log(tab.paneName)
//
if(tab.paneName==='third'){
this.getOtherReportList()
}
//
else if(tab.paneName==='second'){
this.getReplaceReportList();
}
else {
//
this.getList();
}
this.loading = false
},
timestampToTime,
getOtherReportList(){
let date = this.valiFormData.reportDateString
if(date.length<5)date = getCurrentDate()
this.valiFormData.reportDate = [date+' 00:00:00', date+' 23:59:59']
this.valiFormData.reportType = '个人'
getOtherList(this.valiFormData).then(response => {
this.otherReportList = response.data
})
},
getReplaceReportList(){
let date = this.valiFormData.reportDateString
if(date.length<5)date = getCurrentDate()
this.valiFormData.reportDate = [date+' 00:00:00', date+' 23:59:59']
this.valiFormData.reportType = '代报工'
getOtherList(this.valiFormData).then(response => {
this.replaceReportList = response.data
})
},
getUserList() {
getOtherPersonalUser().then(response => {
this.userList = response.data
})
},
getList() {
getMyList().then(response => {
this.reportList = response.data
})
},
deleteReport(id) {
showConfirm("确认删除该报工信息吗?").then(res => {
this.loading = true
if (res.confirm) {
deleteByReportId(id).then(response => {
modal.msgSuccess("操作成功")
if(this.activeName==='first')this.getList()
if(this.activeName==='third')this.getOtherReportList()
if(this.activeName==='second')this.getReplaceReportList()
})
}
this.loading = false
})
},
updateReport(id,status,type) {
showConfirm("确认"+type+"该报工信息吗?").then(res => {
this.loading = true
if (res.confirm) {
updateStatus(id,status).then(response => {
modal.msgSuccess("操作成功")
if(this.activeName==='first')this.getList()
if(this.activeName==='third')this.getOtherReportList()
if(this.activeName==='second')this.getReplaceReportList()
})
}
this.loading = false
})
},
editOrAddReport(id){
if(id===null || id===undefined)tab.navigateTo('/page_report/reportForm')
else tab.navigateTo("",id)
},
addReplaceReport(id){
if(id===null || id===undefined)tab.navigateTo('/page_report/replaceForm')
else tab.navigateTo('/page_report/replaceForm',id)
},
maskClick(e){
this.valiFormData.reportDateString = e
if(this.activeName==='first')this.getList()
if(this.activeName==='third')this.getOtherReportList()
if(this.activeName==='second')this.getReplaceReportList()
},
handleOtherReportChange(){
this.loading = true
//
this.getOtherReportList()
this.loading = false
},
handleReplaceReportChange(){
this.loading = true
//
this.getReplaceReportList()
this.loading = false
}
<!-- 仓储报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #2d5a87;">
<text class="icon-text">📦</text>
</view>
<text class="module-title">仓储报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('库存盘点')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">库存盘点</text>
</view>
<view class="function-item" @click="handleClick('出入库统计')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🚚</text>
</view>
<text class="function-name">出入库统计</text>
</view>
<view class="function-item" @click="handleClick('库存周转')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🔄</text>
</view>
<text class="function-name">库存周转</text>
</view>
</view>
</view>
}
};
<!-- 质量报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #3d7ab5;">
<text class="icon-text"></text>
</view>
<text class="module-title">质量报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('合格率统计')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">合格率统计</text>
</view>
<view class="function-item" @click="handleClick('不良品分析')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">不良品分析</text>
</view>
<view class="function-item" @click="handleClick('质量趋势')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">质量趋势</text>
</view>
<view class="function-item" @click="handleClick('质检报告')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">质检报告</text>
</view>
</view>
</view>
<!-- 数据采集 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #4a90c2;">
<text class="icon-text">📡</text>
</view>
<text class="module-title">数据采集</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('实时数据')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">实时数据</text>
</view>
<view class="function-item" @click="handleClick('历史数据')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner">📚</text>
</view>
<text class="function-name">历史数据</text>
</view>
<view class="function-item" @click="handleClick('数据导出')">
<view class="function-icon" style="background: rgba(74, 144, 194, 0.1);">
<text class="icon-inner">📤</text>
</view>
<text class="function-name">数据导出</text>
</view>
</view>
</view>
<!-- 能耗报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #ff8c00;">
<text class="icon-text"></text>
</view>
<text class="module-title">能耗报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('用电统计')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🔌</text>
</view>
<text class="function-name">用电统计</text>
</view>
<view class="function-item" @click="handleClick('能耗分析')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">能耗分析</text>
</view>
<view class="function-item" @click="handleClick('节能报告')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🌱</text>
</view>
<text class="function-name">节能报告</text>
</view>
</view>
</view>
<!-- 设备报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #18bc37;">
<text class="icon-text">🔧</text>
</view>
<text class="module-title">设备报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('设备效率')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">设备效率</text>
</view>
<view class="function-item" @click="handleClick('故障统计')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🔩</text>
</view>
<text class="function-name">故障统计</text>
</view>
<view class="function-item" @click="handleClick('维护记录')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🔨</text>
</view>
<text class="function-name">维护记录</text>
</view>
<view class="function-item" @click="handleClick('开机率')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">开机率</text>
</view>
</view>
</view>
<!-- 模具报表 -->
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #5aa0d2;">
<text class="icon-text">🔩</text>
</view>
<text class="module-title">模具报表</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('模具寿命')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">模具寿命</text>
</view>
<view class="function-item" @click="handleClick('使用统计')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">使用统计</text>
</view>
<view class="function-item" @click="handleClick('保养记录')">
<view class="function-icon" style="background: rgba(90, 160, 210, 0.1);">
<text class="icon-inner">🔧</text>
</view>
<text class="function-name">保养记录</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
function handleClick(name) {
uni.showToast({
title: `查看${name}`,
icon: 'none'
});
}
</script>
<style lang="scss" scoped>
.example {
padding: 15px;
background-color: #fff;
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.addSite {
display: flex;
justify-content: space-around;
width: 600rpx;
line-height: 100rpx;
position: absolute;
bottom: 30rpx;
left: 80rpx;
background-color: red;
border-radius: 60rpx;
font-size: 30rpx;
.add{
display: flex;
align-items: center;
color: #ffffff;
.icon{
margin-right: 10rpx;
}
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx;
padding-top: 80rpx;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-subtitle {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-scroll {
padding: 24rpx;
}
.module-section {
background: #ffffff;
border-radius: 20rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.module-header {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
background: #fafbfc;
border-bottom: 1rpx solid #e8eaed;
.module-icon {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.icon-text {
font-size: 36rpx;
}
}
.module-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
}
.function-grid {
display: flex;
flex-wrap: wrap;
padding: 24rpx;
gap: 20rpx;
}
.function-item {
width: calc(25% - 15rpx);
background: #f8fafc;
border-radius: 16rpx;
padding: 28rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
&:active {
background: #e8f4ff;
transform: scale(0.98);
}
.function-icon {
width: 80rpx;
height: 80rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.icon-inner {
font-size: 40rpx;
}
}
.function-name {
font-size: 24rpx;
color: #333333;
text-align: center;
}
}
</style>

@ -1,159 +1,619 @@
<template>
<view >
<uni-notice-bar show-icon scrollable
text="安全生产!有序生产!高效生产!" />
<!-- 已提交-->
<uni-section title="已提交" type="line" title-color="#18bc37"></uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in finishList" :name="item.id" @click="handleFinishClick(item.id,index)">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{timestampToTime(item.feedingTime)}}</el-text>-
<el-text size="large">{{item.feedingRecordCode}}</el-text>-
<el-text type="warning">{{ findTextByValue(pipelineTypes, item.feedingPipeline)}}</el-text>
</template>
<el-row>
<el-col :span="6">投料类型:
<el-text type="warning">{{findTextByValue(feedingTypes, item.feedingType) }}</el-text>
</el-col>
<el-col :span="8">记录人:
<el-text type="success">{{ item.userName }}</el-text>
</el-col>
<el-col :span="8" v-if="item.feedingType !=='org'">/kg:
<el-text>{{ item.weight }} </el-text>
</el-col>
</el-row>
<el-row v-for="(item2, index2) in finishList[index].detailList">
<el-col :span="8">原料:
<el-text type="warning">{{item2.itemName}}</el-text>
</el-col>
<el-col :span="8">数量:
<el-text type="success">{{ item2.weight }}</el-text>
</el-col>
<el-col :span="8">单位:
<el-text>{{item2.unitName }} </el-text>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<!-- 草稿状态-->
<uni-section title="草稿" type="line" title-color="#f3a73f"></uni-section>
<el-collapse accordion>
<el-collapse-item v-for="(item, index) in draftList" :name="item.id" @click="handleDraftClick(item.id,index)">
<template #title>
<el-icon><Tickets /></el-icon>
<el-text type="success">{{timestampToTime(item.feedingTime)}}</el-text>-
<el-text size="large">{{item.feedingRecordCode}}</el-text>-
<el-text type="warning">{{ findTextByValue(pipelineTypes, item.feedingPipeline)}}</el-text>
</template>
<el-row>
<el-col :span="6">投料类型:
<el-text type="warning">{{findTextByValue(feedingTypes, item.feedingType) }}</el-text>
</el-col>
<el-col :span="8">记录人:
<el-text type="success">{{ item.userName }}</el-text>
</el-col>
<el-col :span="4" v-if="item.feedingType !=='org'">/kg:
<el-text>{{ item.weight }} </el-text>
</el-col>
<el-col :span="3">
<el-button type="info" @click="handleUpdate(item.id)"></el-button>
</el-col>
<el-col :span="3">
<el-button type="danger" @click="handleDelete(item.id)"></el-button>
</el-col>
</el-row>
<el-row v-for="(item2, index2) in draftList[index].detailList">
<el-col :span="8">原料:
<el-text type="warning">{{item2.itemName}}</el-text>
</el-col>
<el-col :span="8">数量:
<el-text type="success">{{ item2.weight }}</el-text>
</el-col>
<el-col :span="8">单位:
<el-text>{{item2.unitName }} </el-text>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<uni-fab ref="fab" :pattern="pattern" @fabClick="handleAdd" />
<view class="page-container">
<view class="header-section">
<text class="header-title">管理中心</text>
<text class="header-subtitle">系统配置与管理</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="menuSearchKeyword"
class="search-input"
type="text"
placeholder="搜索菜单"
placeholder-class="input-placeholder"
@input="handleMenuSearch"
@confirm="handleMenuSearch"
/>
<view v-if="menuSearchKeyword" class="clear-btn" @click="clearMenuSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll" :scroll-top="scrollTop" scrollIntoView="top">
<view id="top" class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #1a3a5c;">
<text class="icon-text"></text>
</view>
<text class="module-title">生产管控</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">生产计划</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('生产任务')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">生产任务</text>
</view>
<view class="function-item" @click="handleClick('生产计划')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">生产计划</text>
</view>
<view class="function-item" @click="handleClick('生产投料')">
<view class="function-icon" style="background: rgba(26, 58, 92, 0.1);">
<text class="icon-inner">🏭</text>
</view>
<text class="function-name">生产投料</text>
</view>
</view>
</view>
<view class="divider"></view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">仓储管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('仓库信息')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🏢</text>
</view>
<text class="function-name">仓库信息</text>
</view>
<view class="function-item" @click="handleClick('出入库')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">🚚</text>
</view>
<text class="function-name">出入库</text>
</view>
<view class="function-item" @click="handleClick('库存报表')">
<view class="function-icon" style="background: rgba(45, 90, 135, 0.1);">
<text class="icon-inner">📈</text>
</view>
<text class="function-name">库存报表</text>
</view>
</view>
</view>
<view class="divider"></view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">质量管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('检验类型')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">检验类型</text>
</view>
<view class="function-item" @click="handleClick('检验项库')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📚</text>
</view>
<text class="function-name">检验项库</text>
</view>
<view class="function-item" @click="handleClick('检验模板')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📄</text>
</view>
<text class="function-name">检验模板</text>
</view>
<view class="function-item" @click="handleClick('检验记录')">
<view class="function-icon" style="background: rgba(61, 122, 181, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">检验记录</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #ff8c00;">
<text class="icon-text">🔧</text>
</view>
<text class="module-title">设备运维</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">设备管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('设备分类')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">设备分类</text>
</view>
<view class="function-item" @click="handleClick('设备台账')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">设备台账</text>
</view>
<view class="function-item" @click="handleClick('设备关键件')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🔩</text>
</view>
<text class="function-name">设备关键件</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">点检保养</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('点检项库')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">点检项库</text>
</view>
<view class="function-item" @click="handleClick('点检模板')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📄</text>
</view>
<text class="function-name">点检模板</text>
</view>
<view class="function-item" @click="handleClick('点检任务')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">点检任务</text>
</view>
<view class="function-item" @click="handleClick('点检记录')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📜</text>
</view>
<text class="function-name">点检记录</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">维修管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('维修项目')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">🔧</text>
</view>
<text class="function-name">维修项目</text>
</view>
<view class="function-item" @click="handleClick('维修单')">
<view class="function-icon" style="background: rgba(255, 140, 0, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">维修单</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #9c27b0;">
<text class="icon-text">🧱</text>
</view>
<text class="module-title">模具管理</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">模具管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('模具类型')">
<view class="function-icon" style="background: rgba(156, 39, 176, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">模具类型</text>
</view>
<view class="function-item" @click="handleClick('模具台账')">
<view class="function-icon" style="background: rgba(156, 39, 176, 0.1);">
<text class="icon-inner">📊</text>
</view>
<text class="function-name">模具台账</text>
</view>
<view class="function-item" @click="handleClick('模具出库')">
<view class="function-icon" style="background: rgba(156, 39, 176, 0.1);">
<text class="icon-inner">📤</text>
</view>
<text class="function-name">模具出库</text>
</view>
<view class="function-item" @click="handleClick('模具入库')">
<view class="function-icon" style="background: rgba(156, 39, 176, 0.1);">
<text class="icon-inner">📥</text>
</view>
<text class="function-name">模具入库</text>
</view>
<view class="function-item" @click="handleClick('上下模')">
<view class="function-icon" style="background: rgba(156, 39, 176, 0.1);">
<text class="icon-inner">🔄</text>
</view>
<text class="function-name">上下模</text>
</view>
</view>
</view>
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">点检保养</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('点检项库')">
<view class="function-icon" style="background: rgba(0, 188, 212, 0.1);">
<text class="icon-inner">📝</text>
</view>
<text class="function-name">点检项库</text>
</view>
<view class="function-item" @click="handleClick('点检模板')">
<view class="function-icon" style="background: rgba(0, 188, 212, 0.1);">
<text class="icon-inner">📄</text>
</view>
<text class="function-name">点检模板</text>
</view>
<view class="function-item" @click="handleClick('点检任务')">
<view class="function-icon" style="background: rgba(0, 188, 212, 0.1);">
<text class="icon-inner"></text>
</view>
<text class="function-name">点检任务</text>
</view>
<view class="function-item" @click="handleClick('点检记录')">
<view class="function-icon" style="background: rgba(0, 188, 212, 0.1);">
<text class="icon-inner">📜</text>
</view>
<text class="function-name">点检记录</text>
</view>
</view>
</view>
</view>
</view>
<view class="module-section">
<view class="module-header">
<view class="module-icon" style="background: #18bc37;">
<text class="icon-text">📦</text>
</view>
<text class="module-title">基础数据</text>
</view>
<view class="submodule-list">
<view class="submodule-group">
<view class="submodule-header">
<text class="submodule-name">产品管理</text>
</view>
<view class="function-grid">
<view class="function-item" @click="handleClick('产品物料分类')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🏷</text>
</view>
<text class="function-name">产品物料分类</text>
</view>
<view class="function-item" @click="handleClick('产品物料信息')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">📋</text>
</view>
<text class="function-name">产品物料信息</text>
</view>
<view class="function-item" @click="handleClick('产品BOM')">
<view class="function-icon" style="background: rgba(24, 188, 55, 0.1);">
<text class="icon-inner">🌳</text>
</view>
<text class="function-name">产品BOM</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="go-top-btn" @click="goTop">
<text class="go-top-icon"></text>
</view>
</view>
</template>
<script setup>
import {ref, onMounted} from 'vue';
import {getRecordList, updateStatus, deleteById, getDetailByRecordId} from "@/api/mes/record"
import {timestampToTime,timestampToDateTime} from "@/utils/dateUtil";
import { pipelineTypes,feedingTypes, findTextByValue} from "@/api/system/dict/data";
import {Tickets} from "@element-plus/icons-vue";
import modal from "@/plugins/modal";
import tab from "@/plugins/tab";
import {showConfirm} from "@/utils/common";
//
const finishList = ref([]);
//稿
const draftList = ref([]);
const pattern = {
color: '#7A7E83',
backgroundColor: '#fff',
selectedColor: '#007AFF',
buttonColor: '#f4c7c7',
iconColor: '#fff'
}
onMounted(() => {
getList()
});
//
function getList() {
getRecordList("2").then(response => {
finishList.value = response.data
})
getRecordList("1").then(response => {
draftList.value = response.data
})
}
function handleFinishClick(id, index){
getDetailByRecordId(id).then(response => {
finishList.value[index].detailList = response.data
})
}
function handleDraftClick(id, index){
getDetailByRecordId(id).then(response => {
draftList.value[index].detailList = response.data
})
}
/** 提交 */
function handleUpdate(id){
showConfirm("确认提交投料记录吗?").then(res => {
if (res.confirm) {
updateStatus(id, 2).then(response => {
modal.msgSuccess("操作成功")
getList()
})
}
})
}
/** 提交 */
function handleDelete(id){
showConfirm("确认删除投料记录吗?").then(res => {
if (res.confirm) {
deleteById(id).then(response => {
modal.msgSuccess("操作成功")
getList()
})
}
})
import { ref } from 'vue';
const scrollTop = ref(0);
const menuSearchKeyword = ref('');
function goTop() {
scrollTop.value = scrollTop.value === 0 ? 0.1 : 0;
}
//
function handleAdd(){
tab.navigateTo('/page_record/feedingRecordForm')
function handleMenuSearch() {
if (menuSearchKeyword.value.trim() === '') {
return;
}
uni.showToast({
title: `搜索: ${menuSearchKeyword.value}`,
icon: 'none'
});
}
function clearMenuSearch() {
menuSearchKeyword.value = '';
}
function handleClick(name) {
const routeMap = {
'仓库信息': '/pages_function/pages/warehouse/index',
'检验类型': '/pages_function/pages/inspection/index',
'检验项库': '/pages_function/pages/inspectionItem/index',
'检验模板': '/pages_function/pages/inspectionTemplate/index',
'产品物料分类': '/pages_function/pages/materialCategory/index',
'产品物料信息': '/pages_function/pages/materialInfo/index',
'产品BOM': '/pages_function/pages/productBom/index',
'设备分类': '/pages_function/pages/equipmentCategory/index',
'设备台账': '/pages_function/pages/equipmentLedger/index',
'设备关键件': '/pages_function/pages/equipmentKeypart/index',
'模具类型': '',
'模具台账': '',
'模具出库': '',
'模具入库': '',
'上下模': '',
'点检项库': '',
'点检模板': '',
'点检任务': '',
'点检记录': '',
'维修项目': '',
'维修单': ''
};
const url = routeMap[name];
if (url) {
uni.navigateTo({ url });
} else {
uni.showToast({
title: `进入${name}`,
icon: 'none'
});
}
}
</script>
<style scoped>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx;
padding-top: 80rpx;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-subtitle {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.search-section {
background: #ffffff;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
padding: 24rpx;
}
.module-section {
background: #ffffff;
border-radius: 20rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.module-header {
display: flex;
align-items: center;
padding: 28rpx 24rpx;
background: #fafbfc;
border-bottom: 1rpx solid #e8eaed;
.module-icon {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
.icon-text {
font-size: 36rpx;
}
}
.module-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
}
.submodule-list {
padding: 20rpx;
}
.submodule-group {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.submodule-header {
margin-bottom: 16rpx;
.submodule-name {
font-size: 28rpx;
font-weight: 500;
color: #666666;
padding-left: 16rpx;
border-left: 6rpx solid #1a3a5c;
}
}
.divider {
height: 2rpx;
background: #e8eaed;
margin: 24rpx 0;
}
.function-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.function-item {
width: calc(25% - 15rpx);
background: #f8fafc;
border-radius: 16rpx;
padding: 28rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
&:active {
background: #e8f4ff;
transform: scale(0.98);
}
.function-icon {
width: 80rpx;
height: 80rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
.icon-inner {
font-size: 40rpx;
}
}
.function-name {
font-size: 24rpx;
color: #333333;
text-align: center;
}
}
.go-top-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 88rpx;
height: 88rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.go-top-icon {
font-size: 44rpx;
color: #ffffff;
font-weight: bold;
}
}
/* 自定义 TabBar */
</style>

@ -0,0 +1,317 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">设备详情</text>
<text class="header-code">{{ equipmentData.code }}</text>
</view>
<view class="status-badge" :class="'status-' + equipmentData.statusType">
<text>{{ equipmentData.status }}</text>
</view>
</view>
<view class="content-section">
<view class="info-card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">设备名称</text>
<text class="info-value">{{ equipmentData.name }}</text>
</view>
<view class="info-row">
<text class="info-label">设备类型</text>
<text class="info-value">{{ equipmentData.type }}</text>
</view>
<view class="info-row">
<text class="info-label">规格型号</text>
<text class="info-value">{{ equipmentData.spec }}</text>
</view>
<view class="info-row">
<text class="info-label">所属产线</text>
<text class="info-value">{{ equipmentData.lineName }}</text>
</view>
<view class="info-row">
<text class="info-label">安装位置</text>
<text class="info-value">{{ equipmentData.location }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">运行信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">运行时长</text>
<text class="info-value highlight">{{ equipmentData.runTime }}</text>
</view>
<view class="info-row">
<text class="info-label">开机率</text>
<text class="info-value">{{ equipmentData.availability }}</text>
</view>
<view class="info-row">
<text class="info-label">上次保养</text>
<text class="info-value">{{ equipmentData.lastMaintenance }}</text>
</view>
<view class="info-row">
<text class="info-label">下次保养</text>
<text class="info-value warning">{{ equipmentData.nextMaintenance }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">维护记录</view>
<view class="record-list">
<view v-for="(record, index) in maintenanceRecords" :key="index" class="record-item">
<view class="record-dot"></view>
<view class="record-content">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ record.time }}</text>
</view>
<view class="record-type" :class="'type-' + record.type">
<text>{{ record.typeText }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
const equipmentData = reactive({
code: '',
name: '数控加工中心',
type: '加工设备',
spec: 'VMC-850',
lineName: '一号生产线',
location: 'A区-01工位',
status: '运行中',
statusType: 'running',
runTime: '2,560 小时',
availability: '92%',
lastMaintenance: '2024-03-01',
nextMaintenance: '2024-04-01'
});
const maintenanceRecords = reactive([
{ title: '定期保养', time: '2024-03-01', type: 'maintenance', typeText: '保养' },
{ title: '故障维修', time: '2024-02-15', type: 'repair', typeText: '维修' },
{ title: '定期保养', time: '2024-01-15', type: 'maintenance', typeText: '保养' },
{ title: '设备调试', time: '2024-01-01', type: 'debug', typeText: '调试' }
]);
function goBack() {
uni.navigateBack();
}
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
if (options.code) {
equipmentData.code = decodeURIComponent(options.code);
}
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-code {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.status-badge {
position: absolute;
top: 60rpx;
right: 30rpx;
padding: 12rpx 24rpx;
border-radius: 24rpx;
font-size: 24rpx;
}
.status-running {
background: rgba(24, 188, 55, 0.2);
color: #18bc37;
}
.status-stop {
background: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
}
.status-maintain {
background: rgba(255, 140, 0, 0.2);
color: #ff8c00;
}
.content-section {
padding: 0 30rpx 30rpx;
margin-top: -40rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
&.highlight {
font-weight: 600;
color: #1a3a5c;
}
&.warning {
color: #ff8c00;
font-weight: 500;
}
}
.record-list {
display: flex;
flex-direction: column;
}
.record-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.record-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #1a3a5c;
margin-right: 20rpx;
}
.record-content {
flex: 1;
.record-title {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
}
.record-time {
display: block;
font-size: 24rpx;
color: #999999;
}
}
.record-type {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 22rpx;
}
.type-maintenance {
background: rgba(24, 188, 55, 0.1);
color: #18bc37;
}
.type-repair {
background: rgba(255, 77, 79, 0.1);
color: #ff4d4f;
}
.type-debug {
background: rgba(74, 144, 194, 0.1);
color: #4a90c2;
}
</style>

@ -0,0 +1,415 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">设备查询</text>
<text class="header-desc">请选择查询方式</text>
</view>
</view>
<view class="content-section">
<view class="scan-section">
<view class="scan-area" @click="startScan">
<view class="scan-icon">
<text class="icon-text">📷</text>
</view>
<text class="scan-title">自动扫描</text>
<text class="scan-desc">点击启动扫描设备编码</text>
</view>
<view v-if="isScanning" class="scanning-overlay">
<view class="scanning-animation">
<view class="scan-line"></view>
<view class="scan-corners">
<view class="corner corner-tl"></view>
<view class="corner corner-tr"></view>
<view class="corner corner-bl"></view>
<view class="corner corner-br"></view>
</view>
</view>
<text class="scanning-text">正在扫描中...</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: scanProgress + '%' }"></view>
</view>
</view>
</view>
<view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
<view class="input-section">
<view class="input-label">手动输入设备编码</view>
<view class="input-wrapper">
<input
v-model="equipmentCode"
class="code-input"
type="text"
placeholder="请输入设备编码"
placeholder-class="input-placeholder"
/>
</view>
<view class="confirm-btn" :class="{ active: equipmentCode.length > 0 }" @click="confirmInput">
<text class="btn-text">确定</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const equipmentCode = ref('');
const isScanning = ref(false);
const scanProgress = ref(0);
function goBack() {
uni.navigateBack();
}
function startScan() {
if (isScanning.value) return;
isScanning.value = true;
scanProgress.value = 0;
const duration = 2000;
const interval = 50;
const steps = duration / interval;
let currentStep = 0;
const timer = setInterval(() => {
currentStep++;
scanProgress.value = Math.min((currentStep / steps) * 100, 100);
if (currentStep >= steps) {
clearInterval(timer);
setTimeout(() => {
isScanning.value = false;
navigateToDetail('EQ-SCAN-001');
}, 200);
}
}, interval);
}
function confirmInput() {
if (equipmentCode.value.trim().length === 0) {
uni.showToast({
title: '请输入设备编码',
icon: 'none'
});
return;
}
navigateToDetail(equipmentCode.value.trim());
}
function navigateToDetail(code) {
uni.navigateTo({
url: `/pages_function/pages/equipment/detail?code=${encodeURIComponent(code)}`
});
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-section {
padding: 40rpx 30rpx;
margin-top: -40rpx;
}
.scan-section {
position: relative;
background: #ffffff;
border-radius: 24rpx;
padding: 60rpx 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.scan-area {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.8;
}
}
.scan-icon {
width: 160rpx;
height: 160rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #3d7ab5 100%);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
.icon-text {
font-size: 72rpx;
}
}
.scan-title {
font-size: 34rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 12rpx;
}
.scan-desc {
font-size: 26rpx;
color: #999999;
}
.scanning-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(26, 58, 92, 0.95);
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.scanning-animation {
width: 300rpx;
height: 300rpx;
position: relative;
margin-bottom: 40rpx;
}
.scan-line {
position: absolute;
top: 0;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background: linear-gradient(90deg, transparent, #ff8c00, transparent);
animation: scanMove 1.5s ease-in-out infinite;
}
@keyframes scanMove {
0% {
top: 20rpx;
}
50% {
top: 260rpx;
}
100% {
top: 20rpx;
}
}
.scan-corners {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border-color: #ff8c00;
border-style: solid;
border-width: 0;
}
.corner-tl {
top: 0;
left: 0;
border-top-width: 6rpx;
border-left-width: 6rpx;
border-top-left-radius: 12rpx;
}
.corner-tr {
top: 0;
right: 0;
border-top-width: 6rpx;
border-right-width: 6rpx;
border-top-right-radius: 12rpx;
}
.corner-bl {
bottom: 0;
left: 0;
border-bottom-width: 6rpx;
border-left-width: 6rpx;
border-bottom-left-radius: 12rpx;
}
.corner-br {
bottom: 0;
right: 0;
border-bottom-width: 6rpx;
border-right-width: 6rpx;
border-bottom-right-radius: 12rpx;
}
.scanning-text {
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
}
.progress-bar {
width: 400rpx;
height: 8rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #ff8c00;
border-radius: 4rpx;
transition: width 0.05s linear;
}
.divider {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.divider-line {
flex: 1;
height: 2rpx;
background: #e8eaed;
}
.divider-text {
padding: 0 30rpx;
font-size: 28rpx;
color: #999999;
}
}
.input-section {
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.input-label {
font-size: 30rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
}
.input-wrapper {
margin-bottom: 30rpx;
}
.code-input {
width: 100%;
height: 96rpx;
background: #f5f7fa;
border-radius: 16rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #333333;
border: 2rpx solid transparent;
&:focus {
border-color: #1a3a5c;
background: #ffffff;
}
}
.input-placeholder {
color: #c0c4cc;
}
.confirm-btn {
height: 96rpx;
background: #c0c4cc;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
&:active {
opacity: 0.8;
transform: scale(0.98);
}
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
}
</style>

@ -0,0 +1,573 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">设备分类</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入分类编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="category-list">
<view
v-for="(item, index) in categoryList"
:key="index"
class="category-card"
>
<view class="card-header">
<view class="header-left">
<text class="category-name">{{ item.name }}</text>
<text class="category-code">编码: {{ item.code }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">排序</text>
<text class="card-value">{{ item.sort }}</text>
</view>
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ isEdit ? '编辑分类' : '新增分类' }}</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<view class="form-content">
<view class="form-item">
<text class="form-label">分类编码 <text class="required">*</text></text>
<input
v-model="formData.code"
class="form-input"
type="text"
placeholder="请输入分类编码"
/>
</view>
<view class="form-item">
<text class="form-label">分类名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入分类名称"
/>
</view>
<view class="form-item">
<text class="form-label">排序</text>
<input
v-model="formData.sort"
class="form-input"
type="number"
placeholder="请输入排序号"
/>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const searchKeyword = ref('');
const isEdit = ref(false);
const formData = reactive({
code: '',
name: '',
sort: '',
remark: ''
});
const categoryList = reactive([
{ code: 'EQ001', name: '生产设备', sort: 1, remark: '生产线主要设备' },
{ code: 'EQ002', name: '检测设备', sort: 2, remark: '质量检测用设备' },
{ code: 'EQ003', name: '辅助设备', sort: 3, remark: '辅助生产设备' },
{ code: 'EQ004', name: '动力设备', sort: 4, remark: '电力、空压等设备' },
{ code: 'EQ005', name: '运输设备', sort: 5, remark: '物料运输设备' },
{ code: 'EQ006', name: '办公设备', sort: 6, remark: '办公用设备' }
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
isEdit.value = true;
formData.code = item.code;
formData.name = item.name;
formData.sort = item.sort;
formData.remark = item.remark;
addPopup.value.open();
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
isEdit.value = false;
resetForm();
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.code = '';
formData.name = '';
formData.sort = '';
formData.remark = '';
}
function handleSave() {
if (!formData.code || !formData.name) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.category-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
padding-bottom: 120rpx;
}
.category-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.category-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.category-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 600rpx;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
</style>

@ -0,0 +1,589 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">设备关键件</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入关键件编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="keypart-list">
<view
v-for="(item, index) in keypartList"
:key="index"
class="keypart-card"
>
<view class="card-header">
<view class="header-left">
<text class="keypart-name">{{ item.name }}</text>
<text class="keypart-code">编码: {{ item.code }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">描述</text>
<text class="card-value">{{ item.description || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">数量</text>
<text class="card-value">{{ item.count }}</text>
</view>
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ isEdit ? '编辑关键件' : '新增关键件' }}</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<view class="form-content">
<view class="form-item">
<text class="form-label">关键件编码 <text class="required">*</text></text>
<input
v-model="formData.code"
class="form-input"
type="text"
placeholder="请输入关键件编码"
/>
</view>
<view class="form-item">
<text class="form-label">关键件名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入关键件名称"
/>
</view>
<view class="form-item">
<text class="form-label">描述</text>
<textarea
v-model="formData.description"
class="form-textarea"
placeholder="请输入描述"
:maxlength="200"
/>
</view>
<view class="form-item">
<text class="form-label">数量</text>
<input
v-model="formData.count"
class="form-input"
type="number"
placeholder="请输入数量"
/>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const searchKeyword = ref('');
const isEdit = ref(false);
const formData = reactive({
code: '',
name: '',
description: '',
count: '',
remark: ''
});
const keypartList = reactive([
{ code: 'KP001', name: '电机', description: 'Y2系列三相异步电机', count: 2, remark: '设备核心动力部件' },
{ code: 'KP002', name: '变频器', description: '37kW变频器', count: 1, remark: '电机调速控制' },
{ code: 'KP003', name: 'PLC控制器', description: '西门子S7-1200', count: 1, remark: '设备控制系统' },
{ code: 'KP004', name: '伺服驱动器', description: '2kW伺服驱动器', count: 2, remark: '精确位置控制' },
{ code: 'KP005', name: '滚珠丝杠', description: 'F4004滚珠丝杠', count: 2, remark: '线性传动部件' },
{ code: 'KP006', name: '直线导轨', description: 'HGR20直线导轨', count: 4, remark: '滑动支撑部件' }
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
isEdit.value = true;
formData.code = item.code;
formData.name = item.name;
formData.description = item.description;
formData.count = item.count;
formData.remark = item.remark;
addPopup.value.open();
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
isEdit.value = false;
resetForm();
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.code = '';
formData.name = '';
formData.description = '';
formData.count = '';
formData.remark = '';
}
function handleSave() {
if (!formData.code || !formData.name) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.keypart-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
padding-bottom: 120rpx;
}
.keypart-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.keypart-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.keypart-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 600rpx;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,551 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">检验类型</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入检验类型编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="inspection-list">
<view
v-for="(item, index) in inspectionList"
:key="index"
class="inspection-card"
>
<view class="card-header">
<view class="header-left">
<text class="inspection-name">{{ item.name }}</text>
<text class="inspection-code">编码: {{ item.code }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">新增检验类型</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<view class="form-content">
<view class="form-item">
<text class="form-label">检验类型编码 <text class="required">*</text></text>
<input
v-model="formData.code"
class="form-input"
type="text"
placeholder="请输入检验类型编码"
/>
</view>
<view class="form-item">
<text class="form-label">检验类型名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入检验类型名称"
/>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const searchKeyword = ref('');
const formData = reactive({
code: '',
name: '',
remark: ''
});
const inspectionList = reactive([
{ code: 'IT001', name: '来料检验', remark: '原材料入库前检验' },
{ code: 'IT002', name: '过程检验', remark: '生产过程中检验' },
{ code: 'IT003', name: '成品检验', remark: '产品完工后检验' },
{ code: 'IT004', name: '出货检验', remark: '产品出货前检验' },
{ code: 'IT005', name: '退货检验', remark: '退货产品检验' }
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
uni.showToast({
title: `编辑${item.name}`,
icon: 'none'
});
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.code = '';
formData.name = '';
formData.remark = '';
}
function handleSave() {
if (!formData.code || !formData.name) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.inspection-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
}
.inspection-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.inspection-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.inspection-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 600rpx;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
</style>

@ -0,0 +1,820 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">检验项库</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入检验项名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="item-list">
<view
v-for="(item, index) in itemList"
:key="index"
class="item-card"
>
<view class="card-header">
<view class="header-left">
<text class="item-name">{{ item.name }}</text>
<text class="item-type">{{ item.zjTypeName }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">作业方式</text>
<text class="card-value">{{ item.tool || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">标准值</text>
<text class="card-value">{{ item.standardVal || '-' }} {{ item.unitName || '' }}</text>
</view>
<view class="card-row">
<text class="card-label">上限值</text>
<text class="card-value">{{ item.upperVal || '-' }} {{ item.unitName || '' }}</text>
</view>
<view class="card-row">
<text class="card-label">下限值</text>
<text class="card-value">{{ item.lowerVal || '-' }} {{ item.unitName || '' }}</text>
</view>
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">新增检验项</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="form-scroll">
<view class="form-content">
<view class="form-item">
<text class="form-label">检验项名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入检验项名称"
/>
</view>
<view class="form-item">
<text class="form-label">检验类型 <text class="required">*</text></text>
<view class="form-picker" @click="showTypePicker">
<text :class="{ 'placeholder-text': !formData.zjTypeName }">
{{ formData.zjTypeName || '请选择检验类型' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">作业方式</text>
<input
v-model="formData.tool"
class="form-input"
type="text"
placeholder="请输入作业方式"
/>
</view>
<view class="form-row">
<view class="form-item half">
<text class="form-label">标准值</text>
<input
v-model="formData.standardVal"
class="form-input"
type="text"
placeholder="请输入"
/>
</view>
<view class="form-item half">
<text class="form-label">单位</text>
<input
v-model="formData.unitName"
class="form-input"
type="text"
placeholder="请输入"
/>
</view>
</view>
<view class="form-row">
<view class="form-item half">
<text class="form-label">上限值</text>
<input
v-model="formData.upperVal"
class="form-input"
type="text"
placeholder="请输入"
/>
</view>
<view class="form-item half">
<text class="form-label">下限值</text>
<input
v-model="formData.lowerVal"
class="form-input"
type="text"
placeholder="请输入"
/>
</view>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
</scroll-view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="typePicker" type="bottom" background-color="#fff">
<view class="picker-content">
<view class="picker-header">
<text class="picker-title">选择检验类型</text>
<view class="picker-close" @click="hideTypePicker">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="picker-list">
<view
v-for="(item, index) in typeList"
:key="index"
class="picker-item"
@click="selectType(item)"
>
<text class="picker-text">{{ item.name }}</text>
<text v-if="formData.zjTypeName === item.name" class="picker-check"></text>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const typePicker = ref(null);
const searchKeyword = ref('');
const formData = reactive({
name: '',
zjTypeName: '',
tool: '',
standardVal: '',
upperVal: '',
lowerVal: '',
unitName: '',
remark: ''
});
const typeList = reactive([
{ name: '来料检验' },
{ name: '过程检验' },
{ name: '成品检验' },
{ name: '出货检验' },
{ name: '退货检验' }
]);
const itemList = reactive([
{
name: '外观检查',
zjTypeName: '来料检验',
tool: '目视',
standardVal: '合格',
upperVal: '',
lowerVal: '',
unitName: '',
remark: '检查产品外观是否有缺陷'
},
{
name: '尺寸测量',
zjTypeName: '来料检验',
tool: '卡尺',
standardVal: '100',
upperVal: '102',
lowerVal: '98',
unitName: 'mm',
remark: '测量产品关键尺寸'
},
{
name: '重量检测',
zjTypeName: '过程检验',
tool: '电子秤',
standardVal: '500',
upperVal: '510',
lowerVal: '490',
unitName: 'g',
remark: '检测产品重量'
},
{
name: '硬度测试',
zjTypeName: '成品检验',
tool: '硬度计',
standardVal: '60',
upperVal: '65',
lowerVal: '55',
unitName: 'HRC',
remark: '测试产品硬度'
},
{
name: '电气性能',
zjTypeName: '出货检验',
tool: '万用表',
standardVal: '220',
upperVal: '230',
lowerVal: '210',
unitName: 'V',
remark: '检测电气性能'
}
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
uni.showToast({
title: `编辑${item.name}`,
icon: 'none'
});
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.name = '';
formData.zjTypeName = '';
formData.tool = '';
formData.standardVal = '';
formData.upperVal = '';
formData.lowerVal = '';
formData.unitName = '';
formData.remark = '';
}
function showTypePicker() {
typePicker.value.open();
}
function hideTypePicker() {
typePicker.value.close();
}
function selectType(item) {
formData.zjTypeName = item.name;
hideTypePicker();
}
function handleSave() {
if (!formData.name || !formData.zjTypeName) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.item-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
}
.item-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.item-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.item-type {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 650rpx;
max-height: 80vh;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-scroll {
max-height: 55vh;
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-row {
display: flex;
gap: 20rpx;
.form-item {
flex: 1;
}
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-picker {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
.placeholder-text {
color: #999999;
}
.picker-arrow {
font-size: 32rpx;
color: #999999;
}
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
padding: 0 32rpx 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
.picker-content {
background: #ffffff;
border-radius: 20rpx 20rpx 0 0;
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.picker-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
.picker-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #999999;
}
}
.picker-list {
max-height: 600rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 2rpx solid #f0f2f5;
&:last-child {
border-bottom: none;
}
&:active {
background: #f5f7fa;
}
}
.picker-text {
font-size: 28rpx;
color: #333333;
}
.picker-check {
font-size: 32rpx;
color: #1a3a5c;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,312 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">关键件详情</text>
<text class="header-code">{{ keypartData.code }}</text>
</view>
<view class="status-badge" :class="'status-' + keypartData.statusType">
<text>{{ keypartData.status }}</text>
</view>
</view>
<view class="content-section">
<view class="info-card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">关键件名称</text>
<text class="info-value">{{ keypartData.name }}</text>
</view>
<view class="info-row">
<text class="info-label">关键件类型</text>
<text class="info-value">{{ keypartData.type }}</text>
</view>
<view class="info-row">
<text class="info-label">规格型号</text>
<text class="info-value">{{ keypartData.spec }}</text>
</view>
<view class="info-row">
<text class="info-label">所属设备</text>
<text class="info-value">{{ keypartData.equipment }}</text>
</view>
<view class="info-row">
<text class="info-label">安装位置</text>
<text class="info-value">{{ keypartData.location }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">维护信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">更换周期</text>
<text class="info-value highlight">{{ keypartData.replaceCycle }}</text>
</view>
<view class="info-row">
<text class="info-label">上次更换</text>
<text class="info-value">{{ keypartData.lastReplace }}</text>
</view>
<view class="info-row">
<text class="info-label">下次更换</text>
<text class="info-value warning">{{ keypartData.nextReplace }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">维护记录</view>
<view class="record-list">
<view v-for="(record, index) in maintenanceRecords" :key="index" class="record-item">
<view class="record-dot"></view>
<view class="record-content">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ record.time }}</text>
</view>
<view class="record-type" :class="'type-' + record.type">
<text>{{ record.typeText }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
const keypartData = reactive({
code: '',
name: '主轴轴承',
type: '旋转部件',
spec: 'NSK-7014CTYNSULP4',
equipment: '数控加工中心-VMC850',
location: '主轴箱内部',
status: '正常',
statusType: 'normal',
replaceCycle: '2000小时',
lastReplace: '2024-01-15',
nextReplace: '2024-04-15'
});
const maintenanceRecords = reactive([
{ title: '定期更换', time: '2024-01-15', type: 'replace', typeText: '更换' },
{ title: '状态检查', time: '2024-02-15', type: 'check', typeText: '检查' },
{ title: '润滑保养', time: '2024-02-01', type: 'maintain', typeText: '保养' },
{ title: '定期更换', time: '2023-07-15', type: 'replace', typeText: '更换' }
]);
function goBack() {
uni.navigateBack();
}
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
if (options.code) {
keypartData.code = decodeURIComponent(options.code);
}
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-code {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.status-badge {
position: absolute;
top: 60rpx;
right: 30rpx;
padding: 12rpx 24rpx;
border-radius: 24rpx;
font-size: 24rpx;
}
.status-normal {
background: rgba(24, 188, 55, 0.2);
color: #18bc37;
}
.status-warning {
background: rgba(255, 140, 0, 0.2);
color: #ff8c00;
}
.status-danger {
background: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
}
.content-section {
padding: 0 30rpx 30rpx;
margin-top: -40rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
&.highlight {
font-weight: 600;
color: #1a3a5c;
}
&.warning {
color: #ff8c00;
font-weight: 500;
}
}
.record-list {
display: flex;
flex-direction: column;
}
.record-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.record-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #1a3a5c;
margin-right: 20rpx;
}
.record-content {
flex: 1;
.record-title {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
}
.record-time {
display: block;
font-size: 24rpx;
color: #999999;
}
}
.record-type {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 22rpx;
}
.type-replace {
background: rgba(24, 188, 55, 0.1);
color: #18bc37;
}
.type-check {
background: rgba(74, 144, 194, 0.1);
color: #4a90c2;
}
.type-maintain {
background: rgba(255, 140, 0, 0.1);
color: #ff8c00;
}
</style>

@ -0,0 +1,415 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">关键件查询</text>
<text class="header-desc">请选择查询方式</text>
</view>
</view>
<view class="content-section">
<view class="scan-section">
<view class="scan-area" @click="startScan">
<view class="scan-icon">
<text class="icon-text">📷</text>
</view>
<text class="scan-title">自动扫描</text>
<text class="scan-desc">点击启动扫描关键件编码</text>
</view>
<view v-if="isScanning" class="scanning-overlay">
<view class="scanning-animation">
<view class="scan-line"></view>
<view class="scan-corners">
<view class="corner corner-tl"></view>
<view class="corner corner-tr"></view>
<view class="corner corner-bl"></view>
<view class="corner corner-br"></view>
</view>
</view>
<text class="scanning-text">正在扫描中...</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: scanProgress + '%' }"></view>
</view>
</view>
</view>
<view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
<view class="input-section">
<view class="input-label">手动输入关键件编码</view>
<view class="input-wrapper">
<input
v-model="keypartCode"
class="code-input"
type="text"
placeholder="请输入关键件编码"
placeholder-class="input-placeholder"
/>
</view>
<view class="confirm-btn" :class="{ active: keypartCode.length > 0 }" @click="confirmInput">
<text class="btn-text">确定</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const keypartCode = ref('');
const isScanning = ref(false);
const scanProgress = ref(0);
function goBack() {
uni.navigateBack();
}
function startScan() {
if (isScanning.value) return;
isScanning.value = true;
scanProgress.value = 0;
const duration = 2000;
const interval = 50;
const steps = duration / interval;
let currentStep = 0;
const timer = setInterval(() => {
currentStep++;
scanProgress.value = Math.min((currentStep / steps) * 100, 100);
if (currentStep >= steps) {
clearInterval(timer);
setTimeout(() => {
isScanning.value = false;
navigateToDetail('KP-SCAN-001');
}, 200);
}
}, interval);
}
function confirmInput() {
if (keypartCode.value.trim().length === 0) {
uni.showToast({
title: '请输入关键件编码',
icon: 'none'
});
return;
}
navigateToDetail(keypartCode.value.trim());
}
function navigateToDetail(code) {
uni.navigateTo({
url: `/pages_function/pages/keypart/detail?code=${encodeURIComponent(code)}`
});
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-section {
padding: 40rpx 30rpx;
margin-top: -40rpx;
}
.scan-section {
position: relative;
background: #ffffff;
border-radius: 24rpx;
padding: 60rpx 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.scan-area {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.8;
}
}
.scan-icon {
width: 160rpx;
height: 160rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #3d7ab5 100%);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
.icon-text {
font-size: 72rpx;
}
}
.scan-title {
font-size: 34rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 12rpx;
}
.scan-desc {
font-size: 26rpx;
color: #999999;
}
.scanning-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(26, 58, 92, 0.95);
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.scanning-animation {
width: 300rpx;
height: 300rpx;
position: relative;
margin-bottom: 40rpx;
}
.scan-line {
position: absolute;
top: 0;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background: linear-gradient(90deg, transparent, #ff8c00, transparent);
animation: scanMove 1.5s ease-in-out infinite;
}
@keyframes scanMove {
0% {
top: 20rpx;
}
50% {
top: 260rpx;
}
100% {
top: 20rpx;
}
}
.scan-corners {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border-color: #ff8c00;
border-style: solid;
border-width: 0;
}
.corner-tl {
top: 0;
left: 0;
border-top-width: 6rpx;
border-left-width: 6rpx;
border-top-left-radius: 12rpx;
}
.corner-tr {
top: 0;
right: 0;
border-top-width: 6rpx;
border-right-width: 6rpx;
border-top-right-radius: 12rpx;
}
.corner-bl {
bottom: 0;
left: 0;
border-bottom-width: 6rpx;
border-left-width: 6rpx;
border-bottom-left-radius: 12rpx;
}
.corner-br {
bottom: 0;
right: 0;
border-bottom-width: 6rpx;
border-right-width: 6rpx;
border-bottom-right-radius: 12rpx;
}
.scanning-text {
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
}
.progress-bar {
width: 400rpx;
height: 8rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #ff8c00;
border-radius: 4rpx;
transition: width 0.05s linear;
}
.divider {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.divider-line {
flex: 1;
height: 2rpx;
background: #e8eaed;
}
.divider-text {
padding: 0 30rpx;
font-size: 28rpx;
color: #999999;
}
}
.input-section {
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.input-label {
font-size: 30rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
}
.input-wrapper {
margin-bottom: 30rpx;
}
.code-input {
width: 100%;
height: 96rpx;
background: #f5f7fa;
border-radius: 16rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #333333;
border: 2rpx solid transparent;
&:focus {
border-color: #1a3a5c;
background: #ffffff;
}
}
.input-placeholder {
color: #c0c4cc;
}
.confirm-btn {
height: 96rpx;
background: #c0c4cc;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
&:active {
opacity: 0.8;
transform: scale(0.98);
}
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
}
</style>

@ -0,0 +1,608 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">产品物料分类</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入分类编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="category-list">
<view
v-for="(item, index) in categoryList"
:key="index"
class="category-card"
>
<view class="card-header">
<view class="header-left">
<text class="category-name">{{ item.name }}</text>
<text class="category-code">编码: {{ item.code }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">状态</text>
<text class="card-value" :class="item.status === '启用' ? 'status-active' : 'status-inactive'">
{{ item.status }}
</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">新增分类</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<view class="form-content">
<view class="form-item">
<text class="form-label">分类编码 <text class="required">*</text></text>
<input
v-model="formData.code"
class="form-input"
type="text"
placeholder="请输入分类编码"
/>
</view>
<view class="form-item">
<text class="form-label">分类名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入分类名称"
/>
</view>
<view class="form-item">
<text class="form-label">状态 <text class="required">*</text></text>
<view class="status-selector">
<view
class="status-option"
:class="{ active: formData.status === '启用' }"
@click="selectStatus('启用')"
>
<view class="status-dot" :class="{ active: formData.status === '启用' }"></view>
<text>启用</text>
</view>
<view
class="status-option"
:class="{ active: formData.status === '禁用' }"
@click="selectStatus('禁用')"
>
<view class="status-dot" :class="{ active: formData.status === '禁用' }"></view>
<text>禁用</text>
</view>
</view>
</view>
</view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const searchKeyword = ref('');
const formData = reactive({
code: '',
name: '',
status: '启用'
});
const categoryList = reactive([
{ code: 'CAT001', name: '原材料', status: '启用' },
{ code: 'CAT002', name: '半成品', status: '启用' },
{ code: 'CAT003', name: '成品', status: '启用' },
{ code: 'CAT004', name: '辅料', status: '启用' },
{ code: 'CAT005', name: '包材', status: '启用' },
{ code: 'CAT006', name: '废料', status: '禁用' }
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
uni.showToast({
title: `编辑${item.name}`,
icon: 'none'
});
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.code = '';
formData.name = '';
formData.status = '启用';
}
function selectStatus(status) {
formData.status = status;
}
function handleSave() {
if (!formData.code || !formData.name) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.category-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
}
.category-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.category-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.category-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.status-active {
color: #18bc37;
}
.status-inactive {
color: #999999;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 600rpx;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.status-selector {
display: flex;
gap: 20rpx;
}
.status-option {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
gap: 12rpx;
&.active {
background: rgba(26, 58, 92, 0.1);
border-color: #1a3a5c;
text {
color: #1a3a5c;
}
}
text {
font-size: 28rpx;
color: #666666;
}
}
.status-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #999999;
&.active {
background: #18bc37;
}
}
.form-footer {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
</style>

@ -0,0 +1,902 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">产品物料信息</text>
</view>
<view class="filter-section">
<view class="filter-dropdown" @click="showFilterPicker">
<text :class="{ 'filter-placeholder': !selectedParentCategory }">
{{ selectedParentCategory || '请选择父分类筛选' }}
</text>
<text class="filter-arrow"></text>
</view>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="material-list">
<view
v-for="(item, index) in filteredMaterialList"
:key="index"
class="material-card"
>
<view class="card-header">
<view class="header-left">
<text class="material-name">{{ item.name }}</text>
<text class="material-code">编码: {{ item.barCode }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">规格</text>
<text class="card-value">{{ item.standard || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">分类</text>
<text class="card-value">{{ item.subCategoryName }}</text>
</view>
<view class="card-row">
<text class="card-label">单位</text>
<text class="card-value">{{ item.unitName || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">新增物料</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="form-scroll">
<view class="form-content">
<view class="form-item">
<text class="form-label">物料编码 <text class="required">*</text></text>
<input
v-model="formData.barCode"
class="form-input"
type="text"
placeholder="请输入物料编码"
/>
</view>
<view class="form-item">
<text class="form-label">物料名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入物料名称"
/>
</view>
<view class="form-item">
<text class="form-label">规格</text>
<input
v-model="formData.standard"
class="form-input"
type="text"
placeholder="请输入规格"
/>
</view>
<view class="form-item">
<text class="form-label">父分类 <text class="required">*</text></text>
<view class="form-picker" @click="showParentPicker">
<text :class="{ 'placeholder-text': !formData.parentCategory }">
{{ formData.parentCategory || '请选择父分类' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">子分类 <text class="required">*</text></text>
<view class="form-picker" @click="showSubCategoryPicker">
<text :class="{ 'placeholder-text': !formData.subCategoryName }">
{{ formData.subCategoryName || '请选择子分类' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">单位</text>
<input
v-model="formData.unitName"
class="form-input"
type="text"
placeholder="请输入单位"
/>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
</scroll-view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="parentPicker" type="bottom" background-color="#fff">
<view class="picker-content">
<view class="picker-header">
<text class="picker-title">选择父分类</text>
<view class="picker-close" @click="hideParentPicker">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="picker-list">
<view
v-for="(item, index) in parentCategoryList"
:key="index"
class="picker-item"
@click="selectParentCategoryForm(item)"
>
<text class="picker-text">{{ item }}</text>
<text v-if="formData.parentCategory === item" class="picker-check"></text>
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="subCategoryPicker" type="bottom" background-color="#fff">
<view class="picker-content">
<view class="picker-header">
<text class="picker-title">选择子分类</text>
<view class="picker-close" @click="hideSubCategoryPicker">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="picker-list">
<view
v-for="(item, index) in currentSubCategories"
:key="index"
class="picker-item"
@click="selectSubCategory(item)"
>
<text class="picker-text">{{ item }}</text>
<text v-if="formData.subCategoryName === item" class="picker-check"></text>
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="filterPicker" type="bottom" background-color="#fff">
<view class="picker-content">
<view class="picker-header">
<text class="picker-title">选择父分类</text>
<view class="picker-close" @click="clearFilter">
<text class="clear-text">清空</text>
</view>
</view>
<scroll-view scroll-y class="picker-list">
<view
v-for="(item, index) in parentCategoryList"
:key="index"
class="picker-item"
@click="selectParentCategory(item)"
>
<text class="picker-text">{{ item }}</text>
<text v-if="selectedParentCategory === item" class="picker-check"></text>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
const addPopup = ref(null);
const parentPicker = ref(null);
const subCategoryPicker = ref(null);
const filterPicker = ref(null);
const searchKeyword = ref('');
const selectedParentCategory = ref('');
const formData = reactive({
barCode: '',
name: '',
standard: '',
parentCategory: '',
subCategoryName: '',
unitName: '',
remark: ''
});
const parentCategoryList = ['产品', '原料', '工具', '耗材', '备件', '办公室用品', '五金类'];
const subCategoryMap = {
'产品': ['电子产品', '机械产品', '化工产品'],
'原料': ['金属材料', '塑料原料', '化工原料'],
'工具': ['测量工具', '加工工具', '安装工具'],
'耗材': ['包装耗材', '清洁耗材', '劳保耗材'],
'备件': ['机械备件', '电气备件', '标准件'],
'办公室用品': ['文具', '打印耗材', '办公设备'],
'五金类': ['紧固件', '传动件', '密封件']
};
const currentSubCategories = computed(() => {
if (!formData.parentCategory) return [];
return subCategoryMap[formData.parentCategory] || [];
});
const materialList = reactive([
{ barCode: 'M001', name: '螺丝M6*20', standard: 'M6*20', parentCategory: '五金类', subCategoryName: '紧固件', unitName: '个', remark: '标准件' },
{ barCode: 'M002', name: '轴承6205', standard: '6205', parentCategory: '备件', subCategoryName: '机械备件', unitName: '套', remark: '' },
{ barCode: 'M003', name: '包装箱', standard: '50*40*30cm', parentCategory: '耗材', subCategoryName: '包装耗材', unitName: '个', remark: '纸箱' },
{ barCode: 'M004', name: '游标卡尺', standard: '0-150mm', parentCategory: '工具', subCategoryName: '测量工具', unitName: '把', remark: '精度0.02mm' },
{ barCode: 'M005', name: '钢板Q235', standard: '10mm*1000mm*2000mm', parentCategory: '原料', subCategoryName: '金属材料', unitName: 'kg', remark: 'Q235材质' },
{ barCode: 'M006', name: '打印机A4纸', standard: 'A4', parentCategory: '办公室用品', subCategoryName: '打印耗材', unitName: '包', remark: '500张/包' },
{ barCode: 'M007', name: '电机Y2-132S', standard: 'Y2-132S', parentCategory: '产品', subCategoryName: '机械产品', unitName: '台', remark: '5.5kW' },
{ barCode: 'M008', name: '润滑油', standard: 'ISO VG68', parentCategory: '耗材', subCategoryName: '润滑耗材', unitName: 'L', remark: '68号' }
]);
const filteredMaterialList = computed(() => {
let list = materialList;
if (selectedParentCategory.value) {
list = list.filter(item => item.parentCategory === selectedParentCategory.value);
}
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item =>
item.barCode.toLowerCase().includes(keyword) ||
item.name.toLowerCase().includes(keyword)
);
}
return list;
});
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function selectParentCategory(category) {
selectedParentCategory.value = category;
filterPicker.value.close();
}
function clearFilter() {
selectedParentCategory.value = '';
filterPicker.value.close();
}
function showFilterPicker() {
filterPicker.value.open();
}
function handleEdit(item) {
uni.showToast({
title: `编辑${item.name}`,
icon: 'none'
});
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.barCode = '';
formData.name = '';
formData.standard = '';
formData.parentCategory = '';
formData.subCategoryName = '';
formData.unitName = '';
formData.remark = '';
}
function showParentPicker() {
parentPicker.value.open();
}
function hideParentPicker() {
parentPicker.value.close();
}
function selectParentCategoryForm(category) {
formData.parentCategory = category;
formData.subCategoryName = '';
parentPicker.value.close();
}
function showSubCategoryPicker() {
if (!formData.parentCategory) {
uni.showToast({
title: '请先选择父分类',
icon: 'none'
});
return;
}
subCategoryPicker.value.open();
}
function hideSubCategoryPicker() {
subCategoryPicker.value.close();
}
function selectSubCategory(category) {
formData.subCategoryName = category;
subCategoryPicker.value.close();
}
function handleSave() {
if (!formData.barCode || !formData.name) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
if (!formData.parentCategory || !formData.subCategoryName) {
uni.showToast({
title: '请选择分类',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.filter-section {
background: #ffffff;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
}
.filter-dropdown {
display: flex;
justify-content: space-between;
align-items: center;
height: 72rpx;
padding: 0 24rpx;
background: #f5f7fa;
border-radius: 12rpx;
.filter-placeholder {
color: #999999;
}
.filter-arrow {
font-size: 32rpx;
color: #999999;
}
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 340rpx);
}
.material-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
}
.material-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.material-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.material-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 650rpx;
max-height: 80vh;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-scroll {
max-height: 60vh;
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-picker {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
.placeholder-text {
color: #999999;
}
.picker-arrow {
font-size: 32rpx;
color: #999999;
}
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
padding: 0 32rpx 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
.picker-content {
background: #ffffff;
border-radius: 20rpx 20rpx 0 0;
}
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.picker-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
}
.picker-close {
display: flex;
align-items: center;
.clear-text {
font-size: 28rpx;
color: #ff4d4f;
}
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #999999;
}
}
.picker-list {
max-height: 600rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 2rpx solid #f0f2f5;
&:last-child {
border-bottom: none;
}
&:active {
background: #f5f7fa;
}
}
.picker-text {
font-size: 28rpx;
color: #333333;
}
.picker-check {
font-size: 32rpx;
color: #1a3a5c;
}
</style>

@ -0,0 +1,318 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">模具详情</text>
<text class="header-code">{{ moldData.code }}</text>
</view>
<view class="status-badge" :class="'status-' + moldData.statusType">
<text>{{ moldData.status }}</text>
</view>
</view>
<view class="content-section">
<view class="info-card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">模具名称</text>
<text class="info-value">{{ moldData.name }}</text>
</view>
<view class="info-row">
<text class="info-label">模具类型</text>
<text class="info-value">{{ moldData.type }}</text>
</view>
<view class="info-row">
<text class="info-label">规格型号</text>
<text class="info-value">{{ moldData.spec }}</text>
</view>
<view class="info-row">
<text class="info-label">所属产线</text>
<text class="info-value">{{ moldData.lineName }}</text>
</view>
<view class="info-row">
<text class="info-label">存放位置</text>
<text class="info-value">{{ moldData.location }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">使用信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">累计产量</text>
<text class="info-value highlight">{{ moldData.totalOutput }}</text>
</view>
<view class="info-row">
<text class="info-label">使用寿命</text>
<text class="info-value">{{ moldData.lifeSpan }}</text>
</view>
<view class="info-row">
<text class="info-label">上次保养</text>
<text class="info-value">{{ moldData.lastMaintenance }}</text>
</view>
<view class="info-row">
<text class="info-label">下次保养</text>
<text class="info-value warning">{{ moldData.nextMaintenance }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">维护记录</view>
<view class="record-list">
<view v-for="(record, index) in maintenanceRecords" :key="index" class="record-item">
<view class="record-dot"></view>
<view class="record-content">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ record.time }}</text>
</view>
<view class="record-type" :class="'type-' + record.type">
<text>{{ record.typeText }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
const moldData = reactive({
code: '',
name: '汽车外壳注塑模具',
type: '注塑模具',
spec: 'MJ-2024-A001',
lineName: '一号生产线',
location: 'A区-03货架-02层',
status: '正常',
statusType: 'normal',
totalOutput: '125,680 件',
lifeSpan: '85%',
lastMaintenance: '2024-03-01',
nextMaintenance: '2024-04-01'
});
const maintenanceRecords = reactive([
{ title: '定期保养', time: '2024-03-01', type: 'maintenance', typeText: '保养' },
{ title: '模具维修', time: '2024-02-15', type: 'repair', typeText: '维修' },
{ title: '定期保养', time: '2024-01-15', type: 'maintenance', typeText: '保养' },
{ title: '模具更换', time: '2024-01-01', type: 'replace', typeText: '更换' }
]);
function goBack() {
uni.navigateBack();
}
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
if (options.code) {
moldData.code = decodeURIComponent(options.code);
}
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-code {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.status-badge {
padding: 12rpx 24rpx;
border-radius: 24rpx;
font-size: 24rpx;
}
.status-normal {
background: rgba(24, 188, 55, 0.2);
color: #18bc37;
}
.status-warning {
background: rgba(255, 140, 0, 0.2);
color: #ff8c00;
}
.status-error {
background: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
}
.content-section {
padding: 0 30rpx 30rpx;
margin-top: -40rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
&.highlight {
font-weight: 600;
color: #1a3a5c;
}
&.warning {
color: #ff8c00;
font-weight: 500;
}
}
.record-list {
display: flex;
flex-direction: column;
}
.record-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.record-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #1a3a5c;
margin-right: 20rpx;
}
.record-content {
flex: 1;
.record-title {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
}
.record-time {
display: block;
font-size: 24rpx;
color: #999999;
}
}
.record-type {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 22rpx;
}
.type-maintenance {
background: rgba(24, 188, 55, 0.1);
color: #18bc37;
}
.type-repair {
background: rgba(255, 140, 0, 0.1);
color: #ff8c00;
}
.type-replace {
background: rgba(74, 144, 194, 0.1);
color: #4a90c2;
}
</style>

@ -0,0 +1,415 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">模具查询</text>
<text class="header-desc">请选择查询方式</text>
</view>
</view>
<view class="content-section">
<view class="scan-section">
<view class="scan-area" @click="startScan">
<view class="scan-icon">
<text class="icon-text">📷</text>
</view>
<text class="scan-title">自动扫描</text>
<text class="scan-desc">点击启动扫描模具编码</text>
</view>
<view v-if="isScanning" class="scanning-overlay">
<view class="scanning-animation">
<view class="scan-line"></view>
<view class="scan-corners">
<view class="corner corner-tl"></view>
<view class="corner corner-tr"></view>
<view class="corner corner-bl"></view>
<view class="corner corner-br"></view>
</view>
</view>
<text class="scanning-text">正在扫描中...</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: scanProgress + '%' }"></view>
</view>
</view>
</view>
<view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
<view class="input-section">
<view class="input-label">手动输入模具编码</view>
<view class="input-wrapper">
<input
v-model="moldCode"
class="code-input"
type="text"
placeholder="请输入模具编码"
placeholder-class="input-placeholder"
/>
</view>
<view class="confirm-btn" :class="{ active: moldCode.length > 0 }" @click="confirmInput">
<text class="btn-text">确定</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const moldCode = ref('');
const isScanning = ref(false);
const scanProgress = ref(0);
function goBack() {
uni.navigateBack();
}
function startScan() {
if (isScanning.value) return;
isScanning.value = true;
scanProgress.value = 0;
const duration = 2000;
const interval = 50;
const steps = duration / interval;
let currentStep = 0;
const timer = setInterval(() => {
currentStep++;
scanProgress.value = Math.min((currentStep / steps) * 100, 100);
if (currentStep >= steps) {
clearInterval(timer);
setTimeout(() => {
isScanning.value = false;
navigateToDetail('MOLD-SCAN-001');
}, 200);
}
}, interval);
}
function confirmInput() {
if (moldCode.value.trim().length === 0) {
uni.showToast({
title: '请输入模具编码',
icon: 'none'
});
return;
}
navigateToDetail(moldCode.value.trim());
}
function navigateToDetail(code) {
uni.navigateTo({
url: `/pages_function/pages/mold/detail?code=${encodeURIComponent(code)}`
});
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-section {
padding: 40rpx 30rpx;
margin-top: -40rpx;
}
.scan-section {
position: relative;
background: #ffffff;
border-radius: 24rpx;
padding: 60rpx 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.scan-area {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.8;
}
}
.scan-icon {
width: 160rpx;
height: 160rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #3d7ab5 100%);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
.icon-text {
font-size: 72rpx;
}
}
.scan-title {
font-size: 34rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 12rpx;
}
.scan-desc {
font-size: 26rpx;
color: #999999;
}
.scanning-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(26, 58, 92, 0.95);
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.scanning-animation {
width: 300rpx;
height: 300rpx;
position: relative;
margin-bottom: 40rpx;
}
.scan-line {
position: absolute;
top: 0;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background: linear-gradient(90deg, transparent, #ff8c00, transparent);
animation: scanMove 1.5s ease-in-out infinite;
}
@keyframes scanMove {
0% {
top: 20rpx;
}
50% {
top: 260rpx;
}
100% {
top: 20rpx;
}
}
.scan-corners {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border-color: #ff8c00;
border-style: solid;
border-width: 0;
}
.corner-tl {
top: 0;
left: 0;
border-top-width: 6rpx;
border-left-width: 6rpx;
border-top-left-radius: 12rpx;
}
.corner-tr {
top: 0;
right: 0;
border-top-width: 6rpx;
border-right-width: 6rpx;
border-top-right-radius: 12rpx;
}
.corner-bl {
bottom: 0;
left: 0;
border-bottom-width: 6rpx;
border-left-width: 6rpx;
border-bottom-left-radius: 12rpx;
}
.corner-br {
bottom: 0;
right: 0;
border-bottom-width: 6rpx;
border-right-width: 6rpx;
border-bottom-right-radius: 12rpx;
}
.scanning-text {
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
}
.progress-bar {
width: 400rpx;
height: 8rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #ff8c00;
border-radius: 4rpx;
transition: width 0.05s linear;
}
.divider {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.divider-line {
flex: 1;
height: 2rpx;
background: #e8eaed;
}
.divider-text {
padding: 0 30rpx;
font-size: 28rpx;
color: #999999;
}
}
.input-section {
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.input-label {
font-size: 30rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
}
.input-wrapper {
margin-bottom: 30rpx;
}
.code-input {
width: 100%;
height: 96rpx;
background: #f5f7fa;
border-radius: 16rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #333333;
border: 2rpx solid transparent;
&:focus {
border-color: #1a3a5c;
background: #ffffff;
}
}
.input-placeholder {
color: #c0c4cc;
}
.confirm-btn {
height: 96rpx;
background: #c0c4cc;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
&:active {
opacity: 0.8;
transform: scale(0.98);
}
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,302 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">备件详情</text>
<text class="header-code">{{ spareData.code }}</text>
</view>
<view class="status-badge" :class="'status-' + spareData.statusType">
<text>{{ spareData.status }}</text>
</view>
</view>
<view class="content-section">
<view class="info-card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">备件名称</text>
<text class="info-value">{{ spareData.name }}</text>
</view>
<view class="info-row">
<text class="info-label">备件类型</text>
<text class="info-value">{{ spareData.type }}</text>
</view>
<view class="info-row">
<text class="info-label">规格型号</text>
<text class="info-value">{{ spareData.spec }}</text>
</view>
<view class="info-row">
<text class="info-label">存放位置</text>
<text class="info-value">{{ spareData.location }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">库存信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">库存数量</text>
<text class="info-value highlight">{{ spareData.stock }}</text>
</view>
<view class="info-row">
<text class="info-label">最低库存</text>
<text class="info-value">{{ spareData.minStock }}</text>
</view>
<view class="info-row">
<text class="info-label">供应商</text>
<text class="info-value">{{ spareData.supplier }}</text>
</view>
<view class="info-row">
<text class="info-label">最后入库时间</text>
<text class="info-value">{{ spareData.lastInTime }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">出入库记录</view>
<view class="record-list">
<view v-for="(record, index) in stockRecords" :key="index" class="record-item">
<view class="record-dot"></view>
<view class="record-content">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ record.time }}</text>
</view>
<view class="record-type" :class="'type-' + record.type">
<text>{{ record.typeText }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
const spareData = reactive({
code: '',
name: '轴承组件',
type: '机械备件',
spec: 'SKF-6205-2RS',
location: 'A仓库-01货架',
status: '充足',
statusType: 'normal',
stock: '150',
minStock: '50',
supplier: 'SKF中国',
lastInTime: '2024-03-10'
});
const stockRecords = reactive([
{ title: '入库 - 采购入库', time: '2024-03-10 10:30', type: 'in', typeText: '入库' },
{ title: '出库 - 维修领用', time: '2024-03-08 14:20', type: 'out', typeText: '出库' },
{ title: '入库 - 采购入库', time: '2024-03-05 09:15', type: 'in', typeText: '入库' },
{ title: '出库 - 维修领用', time: '2024-03-01 16:45', type: 'out', typeText: '出库' }
]);
function goBack() {
uni.navigateBack();
}
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
if (options.code) {
spareData.code = decodeURIComponent(options.code);
}
});
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-code {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.status-badge {
position: absolute;
top: 60rpx;
right: 30rpx;
padding: 12rpx 24rpx;
border-radius: 24rpx;
font-size: 24rpx;
}
.status-normal {
background: rgba(24, 188, 55, 0.2);
color: #18bc37;
}
.status-low {
background: rgba(255, 140, 0, 0.2);
color: #ff8c00;
}
.status-empty {
background: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
}
.content-section {
padding: 0 30rpx 30rpx;
margin-top: -40rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
&.highlight {
font-weight: 600;
color: #1a3a5c;
}
}
.record-list {
display: flex;
flex-direction: column;
}
.record-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f7fa;
&:last-child {
border-bottom: none;
}
}
.record-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #1a3a5c;
margin-right: 20rpx;
}
.record-content {
flex: 1;
.record-title {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 8rpx;
}
.record-time {
display: block;
font-size: 24rpx;
color: #999999;
}
}
.record-type {
padding: 8rpx 16rpx;
border-radius: 8rpx;
font-size: 22rpx;
}
.type-in {
background: rgba(24, 188, 55, 0.1);
color: #18bc37;
}
.type-out {
background: rgba(255, 77, 79, 0.1);
color: #ff4d4f;
}
</style>

@ -0,0 +1,415 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<view class="header-content">
<text class="header-title">备件查询</text>
<text class="header-desc">请选择查询方式</text>
</view>
</view>
<view class="content-section">
<view class="scan-section">
<view class="scan-area" @click="startScan">
<view class="scan-icon">
<text class="icon-text">📷</text>
</view>
<text class="scan-title">自动扫描</text>
<text class="scan-desc">点击启动扫描备件编码</text>
</view>
<view v-if="isScanning" class="scanning-overlay">
<view class="scanning-animation">
<view class="scan-line"></view>
<view class="scan-corners">
<view class="corner corner-tl"></view>
<view class="corner corner-tr"></view>
<view class="corner corner-bl"></view>
<view class="corner corner-br"></view>
</view>
</view>
<text class="scanning-text">正在扫描中...</text>
<view class="progress-bar">
<view class="progress-fill" :style="{ width: scanProgress + '%' }"></view>
</view>
</view>
</view>
<view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
<view class="input-section">
<view class="input-label">手动输入备件编码</view>
<view class="input-wrapper">
<input
v-model="spareCode"
class="code-input"
type="text"
placeholder="请输入备件编码"
placeholder-class="input-placeholder"
/>
</view>
<view class="confirm-btn" :class="{ active: spareCode.length > 0 }" @click="confirmInput">
<text class="btn-text">确定</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const spareCode = ref('');
const isScanning = ref(false);
const scanProgress = ref(0);
function goBack() {
uni.navigateBack();
}
function startScan() {
if (isScanning.value) return;
isScanning.value = true;
scanProgress.value = 0;
const duration = 2000;
const interval = 50;
const steps = duration / interval;
let currentStep = 0;
const timer = setInterval(() => {
currentStep++;
scanProgress.value = Math.min((currentStep / steps) * 100, 100);
if (currentStep >= steps) {
clearInterval(timer);
setTimeout(() => {
isScanning.value = false;
navigateToDetail('SP-SCAN-001');
}, 200);
}
}, interval);
}
function confirmInput() {
if (spareCode.value.trim().length === 0) {
uni.showToast({
title: '请输入备件编码',
icon: 'none'
});
return;
}
navigateToDetail(spareCode.value.trim());
}
function navigateToDetail(code) {
uni.navigateTo({
url: `/pages_function/pages/spare/detail?code=${encodeURIComponent(code)}`
});
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 40rpx 30rpx 80rpx;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
margin-bottom: 20rpx;
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-content {
.header-title {
display: block;
font-size: 44rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 16rpx;
}
.header-desc {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
}
.content-section {
padding: 40rpx 30rpx;
margin-top: -40rpx;
}
.scan-section {
position: relative;
background: #ffffff;
border-radius: 24rpx;
padding: 60rpx 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.scan-area {
display: flex;
flex-direction: column;
align-items: center;
&:active {
opacity: 0.8;
}
}
.scan-icon {
width: 160rpx;
height: 160rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #3d7ab5 100%);
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
.icon-text {
font-size: 72rpx;
}
}
.scan-title {
font-size: 34rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 12rpx;
}
.scan-desc {
font-size: 26rpx;
color: #999999;
}
.scanning-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(26, 58, 92, 0.95);
border-radius: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.scanning-animation {
width: 300rpx;
height: 300rpx;
position: relative;
margin-bottom: 40rpx;
}
.scan-line {
position: absolute;
top: 0;
left: 20rpx;
right: 20rpx;
height: 4rpx;
background: linear-gradient(90deg, transparent, #ff8c00, transparent);
animation: scanMove 1.5s ease-in-out infinite;
}
@keyframes scanMove {
0% {
top: 20rpx;
}
50% {
top: 260rpx;
}
100% {
top: 20rpx;
}
}
.scan-corners {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border-color: #ff8c00;
border-style: solid;
border-width: 0;
}
.corner-tl {
top: 0;
left: 0;
border-top-width: 6rpx;
border-left-width: 6rpx;
border-top-left-radius: 12rpx;
}
.corner-tr {
top: 0;
right: 0;
border-top-width: 6rpx;
border-right-width: 6rpx;
border-top-right-radius: 12rpx;
}
.corner-bl {
bottom: 0;
left: 0;
border-bottom-width: 6rpx;
border-left-width: 6rpx;
border-bottom-left-radius: 12rpx;
}
.corner-br {
bottom: 0;
right: 0;
border-bottom-width: 6rpx;
border-right-width: 6rpx;
border-bottom-right-radius: 12rpx;
}
.scanning-text {
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
}
.progress-bar {
width: 400rpx;
height: 8rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 4rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #ff8c00;
border-radius: 4rpx;
transition: width 0.05s linear;
}
.divider {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.divider-line {
flex: 1;
height: 2rpx;
background: #e8eaed;
}
.divider-text {
padding: 0 30rpx;
font-size: 28rpx;
color: #999999;
}
}
.input-section {
background: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.input-label {
font-size: 30rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
}
.input-wrapper {
margin-bottom: 30rpx;
}
.code-input {
width: 100%;
height: 96rpx;
background: #f5f7fa;
border-radius: 16rpx;
padding: 0 30rpx;
font-size: 30rpx;
color: #333333;
border: 2rpx solid transparent;
&:focus {
border-color: #1a3a5c;
background: #ffffff;
}
}
.input-placeholder {
color: #c0c4cc;
}
.confirm-btn {
height: 96rpx;
background: #c0c4cc;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.active {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
&:active {
opacity: 0.8;
transform: scale(0.98);
}
}
.btn-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
}
</style>

@ -0,0 +1,581 @@
<template>
<view class="page-container">
<view class="header-section">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-text">返回</text>
</view>
<text class="header-title">仓库信息</text>
</view>
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入仓库编码或名称"
placeholder-class="input-placeholder"
@input="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
</view>
<scroll-view scroll-y class="content-scroll">
<view class="warehouse-list">
<view
v-for="(item, index) in warehouseList"
:key="index"
class="warehouse-card"
>
<view class="card-header">
<view class="header-left">
<text class="warehouse-name">{{ item.name }}</text>
<text class="warehouse-code">编码: {{ item.code }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="handleEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="handleDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="card-row">
<text class="card-label">仓库地址</text>
<text class="card-value">{{ item.address }}</text>
</view>
<view class="card-row">
<text class="card-label">仓库负责人</text>
<text class="card-value">{{ item.manager }}</text>
</view>
<view class="card-row">
<text class="card-label">备注</text>
<text class="card-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="add-btn" @click="showAddForm">
<text class="add-icon">+</text>
</view>
<uni-popup ref="addPopup" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">新增仓库</text>
<view class="popup-close" @click="hideAddForm">
<text class="close-icon">×</text>
</view>
</view>
<view class="form-content">
<view class="form-item">
<text class="form-label">仓库名称 <text class="required">*</text></text>
<input
v-model="formData.name"
class="form-input"
type="text"
placeholder="请输入仓库名称"
/>
</view>
<view class="form-item">
<text class="form-label">仓库编码 <text class="required">*</text></text>
<input
v-model="formData.code"
class="form-input"
type="text"
placeholder="请输入仓库编码"
/>
</view>
<view class="form-item">
<text class="form-label">仓库地址</text>
<input
v-model="formData.address"
class="form-input"
type="text"
placeholder="请输入仓库地址"
/>
</view>
<view class="form-item">
<text class="form-label">仓库负责人</text>
<input
v-model="formData.manager"
class="form-input"
type="text"
placeholder="请输入负责人"
/>
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="请输入备注"
:maxlength="200"
/>
</view>
</view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="hideAddForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="handleSave">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
const addPopup = ref(null);
const searchKeyword = ref('');
const formData = reactive({
name: '',
code: '',
address: '',
manager: '',
remark: ''
});
const warehouseList = reactive([
{ code: 'WH001', name: '一号仓库', address: '工业园区A栋1楼', manager: '张三', remark: '主要原材料仓库' },
{ code: 'WH002', name: '二号仓库', address: '工业园区A栋2楼', manager: '李四', remark: '半成品仓库' },
{ code: 'WH003', name: '三号仓库', address: '工业园区B栋1楼', manager: '王五', remark: '成品仓库' },
{ code: 'WH004', name: '四号仓库', address: '工业园区B栋2楼', manager: '赵六', remark: '备件仓库' },
{ code: 'WH005', name: '五号仓库', address: '工业园区C栋1楼', manager: '孙七', remark: '周转仓库' }
]);
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function handleEdit(item) {
uni.showToast({
title: `编辑${item.name}`,
icon: 'none'
});
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.name}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.name = '';
formData.code = '';
formData.address = '';
formData.manager = '';
formData.remark = '';
}
function handleSave() {
if (!formData.name || !formData.code) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
uni.showToast({
title: '保存成功',
icon: 'success'
});
hideAddForm();
resetForm();
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.header-section {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
padding: 60rpx 40rpx 40rpx 30rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.back-btn {
display: flex;
align-items: center;
position: absolute;
left: 30rpx;
top: 50%;
transform: translateY(-50%);
&:active {
opacity: 0.7;
}
.back-icon {
font-size: 48rpx;
color: #ffffff;
margin-right: 8rpx;
}
.back-text {
font-size: 28rpx;
color: #ffffff;
}
}
.header-title {
font-size: 34rpx;
font-weight: bold;
color: #ffffff;
}
.search-section {
background: #ffffff;
padding: 24rpx 30rpx;
margin-bottom: 20rpx;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 48rpx;
padding: 0 24rpx;
}
.search-icon {
margin-right: 20rpx;
.iconfont {
font-size: 36rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.clear-icon {
font-size: 36rpx;
color: #999999;
}
}
.content-scroll {
flex: 1;
height: calc(100vh - 260rpx);
}
.warehouse-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
}
.warehouse-card {
width: 100%;
background: #ffffff;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.header-left {
display: flex;
flex-direction: column;
}
.warehouse-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.warehouse-code {
font-size: 24rpx;
color: #999999;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
transform: scale(0.95);
}
.action-icon {
font-size: 32rpx;
color: #ffffff;
}
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.card-body {
display: flex;
flex-direction: column;
}
.card-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.card-label {
font-size: 26rpx;
color: #999999;
}
.card-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
}
.add-btn {
position: fixed;
bottom: 120rpx;
right: 30rpx;
width: 100rpx;
height: 100rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.3);
z-index: 999;
&:active {
transform: scale(0.95);
}
.add-icon {
font-size: 60rpx;
color: #ffffff;
font-weight: bold;
}
}
.popup-content {
width: 600rpx;
background: #ffffff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.7;
}
.close-icon {
font-size: 36rpx;
color: #ffffff;
}
}
.form-content {
padding: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
}
.form-textarea {
width: 100%;
min-height: 120rpx;
padding: 16rpx 24rpx;
font-size: 28rpx;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
box-sizing: border-box;
resize: none;
}
.form-footer {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.footer-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
&:active {
opacity: 0.8;
}
.btn-text {
font-size: 28rpx;
font-weight: 500;
}
}
.cancel-btn {
background: #f5f7fa;
.btn-text {
color: #666666;
}
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
.btn-text {
color: #ffffff;
}
}
</style>

@ -26,7 +26,7 @@
height: 10px;
width: 10px;
border-width: 2px 2px 0 0;
border-color: #c0c0c0;
border-color: #1a3a5c;
border-style: solid;
-webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
@ -56,7 +56,7 @@
.list-cell::after {
content: '';
position: absolute;
border-bottom: 1px solid #eaeef1;
border-bottom: 1px solid #e8eaed;
-webkit-transform: scaleY(0.5) translateZ(0);
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 100%;
@ -76,7 +76,7 @@
align-items: center;
.menu-icon {
color: #007AFF;
color: #1a3a5c;
font-size: 16px;
margin-right: 5px;
}
@ -88,3 +88,7 @@
}
}
}
page {
background-color: #f0f2f5;
}

@ -2,11 +2,11 @@
* uni-app
*/
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 行为相关颜色 - 工业生产管理风格 */
$uni-color-primary: #1a3a5c;
$uni-color-success: #18bc37;
$uni-color-warning: #ff8c00;
$uni-color-error: #e43d33;
/* 文字基本颜色 */
$uni-text-color:#333;//

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save