You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1159 lines
25 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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': !selectedType }">
{{ selectedType || '请选择设备类型筛选' }}
</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="equipment-list">
<view
v-for="(item, index) in filteredList"
:key="index"
class="equipment-card"
>
<view class="card-header">
<view class="header-left">
<text class="equipment-name">{{ item.deviceName }}</text>
<text class="equipment-code">编码: {{ item.deviceCode }}</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.deviceType }}</text>
</view>
<view class="card-row">
<text class="card-label">状态</text>
<text class="card-value" :class="getStatusClass(item.deviceStatus)">
{{ getStatusText(item.deviceStatus) }}
</text>
</view>
<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.model || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">位置</text>
<text class="card-value">{{ item.deviceLocation || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">责任人</text>
<text class="card-value">{{ item.supplier || '-' }}</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>
<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.deviceCode"
class="form-input"
type="text"
placeholder="请输入设备编码"
/>
</view>
<view class="form-item">
<text class="form-label">设备名称 <text class="required">*</text></text>
<input
v-model="formData.deviceName"
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.deviceType }">
{{ formData.deviceType || '请选择设备类型' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">设备状态 <text class="required">*</text></text>
<view class="status-selector">
<view
v-for="status in statusList"
:key="status.value"
class="status-option"
:class="{ active: formData.deviceStatus === status.value }"
@click="formData.deviceStatus = status.value"
>
<view class="status-dot" :class="{ active: formData.deviceStatus === status.value, [status.class]: true }"></view>
<text>{{ status.label }}</text>
</view>
</view>
</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>
<input
v-model="formData.model"
class="form-input"
type="text"
placeholder="请输入型号"
/>
</view>
<view class="form-item">
<text class="form-label">生产日期</text>
<view class="form-picker" @click="showDatePicker(1)">
<text :class="{ 'placeholder-text': !formData.productionDate }">
{{ formData.productionDate || '请选择生产日期' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">入厂日期</text>
<view class="form-picker" @click="showDatePicker(2)">
<text :class="{ 'placeholder-text': !formData.entryDate }">
{{ formData.entryDate || '请选择入厂日期' }}
</text>
<text class="picker-arrow"></text>
</view>
</view>
<view class="form-item">
<text class="form-label">位置</text>
<input
v-model="formData.deviceLocation"
class="form-input"
type="text"
placeholder="请输入设备位置"
/>
</view>
<view class="form-item">
<text class="form-label">责任人</text>
<input
v-model="formData.supplier"
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="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 typeList"
:key="index"
class="picker-item"
@click="selectType(item)"
>
<text class="picker-text">{{ item }}</text>
<text v-if="selectedType === item" class="picker-check">✓</text>
</view>
</scroll-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="selectTypeForm(item)"
>
<text class="picker-text">{{ item }}</text>
<text v-if="formData.deviceType === item" class="picker-check">✓</text>
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="datePicker" type="bottom" background-color="#fff">
<view class="picker-content">
<view class="picker-header">
<text class="picker-title">选择日期</text>
<view class="picker-close" @click="hideDatePicker">
<text class="close-icon">×</text>
</view>
</view>
<picker-view
:value="datePickerValue"
@change="handleDateChange"
class="date-picker"
>
<picker-view-column>
<view v-for="(year, i) in years" :key="i" class="picker-column">{{ year }}年</view>
</picker-view-column>
<picker-view-column>
<view v-for="(month, i) in months" :key="i" class="picker-column">{{ month }}月</view>
</picker-view-column>
<picker-view-column>
<view v-for="(day, i) in days" :key="i" class="picker-column">{{ day }}日</view>
</picker-view-column>
</picker-view>
<view class="date-confirm" @click="confirmDate"></view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
const addPopup = ref(null);
const filterPicker = ref(null);
const typePicker = ref(null);
const datePicker = ref(null);
const searchKeyword = ref('');
const selectedType = ref('');
const isEdit = ref(false);
const currentDatePicker = ref(1);
const statusList = [
{ value: 1, label: '正常', class: 'status-normal' },
{ value: 2, label: '维修中', class: 'status-repair' },
{ value: 3, label: '停用', class: 'status-stopped' },
{ value: 4, label: '报废', class: 'status-scrap' }
];
const formData = reactive({
deviceCode: '',
deviceName: '',
deviceType: '',
deviceStatus: 1,
standard: '',
model: '',
productionDate: '',
entryDate: '',
deviceLocation: '',
supplier: '',
remark: ''
});
const typeList = ['生产设备', '检测设备', '辅助设备', '动力设备', '运输设备', '办公设备'];
const equipmentList = reactive([
{
deviceCode: 'DEV001',
deviceName: '数控车床CK6163',
deviceType: '生产设备',
deviceStatus: 1,
standard: 'CK6163*2000',
model: 'CK6163',
productionDate: '2022-03-15',
entryDate: '2022-05-01',
deviceLocation: '车间A栋1号',
supplier: '张三',
remark: '主要加工设备'
},
{
deviceCode: 'DEV002',
deviceName: '三坐标测量仪',
deviceType: '检测设备',
deviceStatus: 1,
standard: '精度0.001mm',
model: 'CMM-550',
productionDate: '2021-08-20',
entryDate: '2021-10-15',
deviceLocation: '质检室',
supplier: '李四',
remark: '高精度检测设备'
},
{
deviceCode: 'DEV003',
deviceName: '空气压缩机',
deviceType: '动力设备',
deviceStatus: 1,
standard: '37kW',
model: 'LU37-8',
productionDate: '2020-01-10',
entryDate: '2020-03-01',
deviceLocation: '动力站房',
supplier: '王五',
remark: '车间供气设备'
},
{
deviceCode: 'DEV004',
deviceName: '激光切割机',
deviceType: '生产设备',
deviceStatus: 2,
standard: '2000W',
model: 'LCF-2015',
productionDate: '2023-06-01',
entryDate: '2023-08-10',
deviceLocation: '车间B栋3号',
supplier: '赵六',
remark: '维修中'
},
{
deviceCode: 'DEV005',
deviceName: '叉车',
deviceType: '运输设备',
deviceStatus: 1,
standard: '3T',
model: 'CPCD30',
productionDate: '2022-11-05',
entryDate: '2022-12-20',
deviceLocation: '仓库区',
supplier: '孙七',
remark: '物料搬运'
}
]);
const years = computed(() => {
const arr = [];
for (let i = 2020; i <= 2030; i++) arr.push(i);
return arr;
});
const months = computed(() => {
const arr = [];
for (let i = 1; i <= 12; i++) arr.push(i);
return arr;
});
const days = computed(() => {
const arr = [];
for (let i = 1; i <= 31; i++) arr.push(i);
return arr;
});
const datePickerValue = ref([0, 0, 0]);
const filteredList = computed(() => {
let list = equipmentList;
if (selectedType.value) {
list = list.filter(item => item.deviceType === selectedType.value);
}
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item =>
item.deviceCode.toLowerCase().includes(keyword) ||
item.deviceName.toLowerCase().includes(keyword)
);
}
return list;
});
function getStatusClass(status) {
const map = {
1: 'status-normal',
2: 'status-repair',
3: 'status-stopped',
4: 'status-scrap'
};
return map[status] || '';
}
function getStatusText(status) {
const map = {
1: '正常',
2: '维修中',
3: '停用',
4: '报废'
};
return map[status] || '';
}
function goBack() {
uni.navigateBack();
}
function handleSearch() {
console.log('搜索:', searchKeyword.value);
}
function clearSearch() {
searchKeyword.value = '';
}
function selectType(type) {
selectedType.value = type;
filterPicker.value.close();
}
function clearFilter() {
selectedType.value = '';
filterPicker.value.close();
}
function showFilterPicker() {
filterPicker.value.open();
}
function handleEdit(item) {
isEdit.value = true;
Object.assign(formData, item);
addPopup.value.open();
}
function handleDelete(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除"${item.deviceName}"吗?`,
success: () => {
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
});
}
function showAddForm() {
isEdit.value = false;
resetForm();
addPopup.value.open();
}
function hideAddForm() {
addPopup.value.close();
resetForm();
}
function resetForm() {
formData.deviceCode = '';
formData.deviceName = '';
formData.deviceType = '';
formData.deviceStatus = 1;
formData.standard = '';
formData.model = '';
formData.productionDate = '';
formData.entryDate = '';
formData.deviceLocation = '';
formData.supplier = '';
formData.remark = '';
}
function showTypePicker() {
typePicker.value.open();
}
function hideTypePicker() {
typePicker.value.close();
}
function selectTypeForm(type) {
formData.deviceType = type;
typePicker.value.close();
}
function showDatePicker(type) {
currentDatePicker.value = type;
datePicker.value.open();
}
function hideDatePicker() {
datePicker.value.close();
}
function handleDateChange(e) {
datePickerValue.value = e.detail.value;
}
function confirmDate() {
const year = years.value[datePickerValue.value[0]];
const month = String(months.value[datePickerValue.value[1]]).padStart(2, '0');
const day = String(days.value[datePickerValue.value[2]]).padStart(2, '0');
const dateStr = `${year}-${month}-${day}`;
if (currentDatePicker.value === 1) {
formData.productionDate = dateStr;
} else {
formData.entryDate = dateStr;
}
hideDatePicker();
}
function handleSave() {
if (!formData.deviceCode || !formData.deviceName) {
uni.showToast({
title: '请填写必填项',
icon: 'none'
});
return;
}
if (!formData.deviceType) {
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: 0 30rpx 24rpx;
}
.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);
}
.equipment-list {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24rpx;
padding-bottom: 120rpx;
}
.equipment-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;
}
.equipment-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.equipment-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-normal {
color: #18bc37;
}
.status-repair {
color: #ff8c00;
}
.status-stopped {
color: #999999;
}
.status-scrap {
color: #ff4d4f;
}
.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: 680rpx;
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-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;
}
.status-selector {
display: flex;
gap: 16rpx;
flex-wrap: wrap;
}
.status-option {
flex: 1;
min-width: 140rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
border-radius: 12rpx;
border: 2rpx solid #e8eaed;
gap: 8rpx;
&.active {
background: rgba(26, 58, 92, 0.1);
border-color: #1a3a5c;
text {
color: #1a3a5c;
}
}
text {
font-size: 26rpx;
color: #666666;
}
}
.status-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #999999;
&.active {
&.status-normal {
background: #18bc37;
}
&.status-repair {
background: #ff8c00;
}
&.status-stopped {
background: #999999;
}
&.status-scrap {
background: #ff4d4f;
}
}
}
.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;
}
.date-picker {
height: 400rpx;
}
.picker-column {
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.date-confirm {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
background: #1a3a5c;
font-size: 28rpx;
color: #ffffff;
}
</style>