From 077ab59c700b85efdd057265bf752ad5942395b2 Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期二, 17 三月 2026 17:36:13 +0800
Subject: [PATCH] feat(quality): 新增质量管理模块的API接口和移动端页面
---
src/pages/qualityManagement/nonconformingManagement/index.vue | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 434 insertions(+), 0 deletions(-)
diff --git a/src/pages/qualityManagement/nonconformingManagement/index.vue b/src/pages/qualityManagement/nonconformingManagement/index.vue
new file mode 100644
index 0000000..5efa7ae
--- /dev/null
+++ b/src/pages/qualityManagement/nonconformingManagement/index.vue
@@ -0,0 +1,434 @@
+<template>
+ <view class="nonconforming-management-page">
+ <PageHeader title="涓嶅悎鏍煎搧绠$悊" @back="goBack" />
+
+ <!-- 鎼滅储涓庣瓫閫� -->
+ <view class="search-section">
+ <up-search
+ placeholder="璇疯緭鍏ヤ骇鍝佸悕绉版悳绱�"
+ v-model="searchForm.productName"
+ @search="handleQuery"
+ @custom="handleQuery"
+ @clear="handleQuery"
+ :show-action="true"
+ action-text="鎼滅储"
+ :animation="true"
+ customStyle="margin-bottom: 20rpx"
+ ></up-search>
+ <view class="filter-row">
+ <view class="filter-item" @click="showTypeSelect = true">
+ <text>{{ typeLabel }}</text>
+ <up-icon name="arrow-down" size="14" color="#999"></up-icon>
+ </view>
+ <view class="filter-item" @click="showStatusSelect = true">
+ <text>{{ statusLabel }}</text>
+ <up-icon name="arrow-down" size="14" color="#999"></up-icon>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍒楄〃鍖哄煙 -->
+ <view class="list-container" v-if="tableData.length > 0">
+ <view v-for="(item, index) in tableData" :key="index" class="list-item">
+ <view class="item-header">
+ <text class="product-name">{{ item.productName }}</text>
+ <up-tag :text="getStatusText(item.inspectState)" :type="getStatusType(item.inspectState)" size="mini"></up-tag>
+ </view>
+ <view class="item-content">
+ <view class="item-row">
+ <text class="item-label">绫诲埆锛�</text>
+ <text class="item-value">{{ getInspectTypeText(item.inspectType) }}</text>
+ </view>
+ <view class="item-row">
+ <text class="item-label">妫�娴嬫棩鏈燂細</text>
+ <text class="item-value">{{ item.checkTime || '-' }}</text>
+ </view>
+ <view class="item-row">
+ <text class="item-label">瑙勬牸鍨嬪彿锛�</text>
+ <text class="item-value">{{ item.model || '-' }}</text>
+ </view>
+ <view class="item-row">
+ <text class="item-label">涓嶅悎鏍肩幇璞★細</text>
+ <text class="item-value text-error">{{ item.defectivePhenomena || '-' }}</text>
+ </view>
+ <view class="item-row" v-if="item.inspectState === 1">
+ <text class="item-label">澶勭悊缁撴灉锛�</text>
+ <text class="item-value text-success">{{ item.dealResult || '-' }}</text>
+ </view>
+ </view>
+ <view class="item-actions">
+ <up-button v-if="item.inspectState === 0" type="primary" size="mini" @click.stop="openDealDialog(item)">澶勭悊</up-button>
+ <up-button type="error" size="mini" @click.stop="handleDelete(item)">鍒犻櫎</up-button>
+ </view>
+ </view>
+ <view class="pagination-container">
+ <up-loadmore :status="loadStatus" @loadmore="getList" />
+ </view>
+ </view>
+
+ <view v-else class="no-data">
+ <up-empty mode="data" text="鏆傛棤鏁版嵁"></up-empty>
+ </view>
+
+ <!-- 绫诲瀷閫夋嫨鍣� -->
+ <up-action-sheet
+ :actions="typeActions"
+ :show="showTypeSelect"
+ @close="showTypeSelect = false"
+ @select="selectType"
+ title="璇烽�夋嫨绫诲埆"
+ ></up-action-sheet>
+
+ <!-- 鐘舵�侀�夋嫨鍣� -->
+ <up-action-sheet
+ :actions="statusActions"
+ :show="showStatusSelect"
+ @close="showStatusSelect = false"
+ @select="selectStatus"
+ title="璇烽�夋嫨鐘舵��"
+ ></up-action-sheet>
+
+ <!-- 澶勭悊寮圭獥 -->
+ <up-popup v-model:show="dealDialogVisible" mode="center" round closeable @close="dealDialogVisible = false">
+ <view class="dialog-content">
+ <view class="dialog-header">
+ <text class="dialog-title">涓嶅悎鏍煎搧澶勭悊</text>
+ </view>
+ <up-form :model="dealForm" ref="dealFormRef" label-width="100" label-position="top">
+ <view class="info-summary">
+ <text class="summary-text">浜у搧锛歿{ currentItem?.productName }}</text>
+ <text class="summary-text">涓嶅悎鏍肩幇璞★細{{ currentItem?.defectivePhenomena }}</text>
+ </view>
+ <up-form-item label="澶勭悊缁撴灉" prop="dealResult" required borderBottom>
+ <up-textarea v-model="dealForm.dealResult" placeholder="璇疯緭鍏ュ鐞嗙粨鏋�" count border="surround" />
+ </up-form-item>
+ <up-form-item label="澶勭悊鏃ユ湡" prop="dealTime" required borderBottom>
+ <up-input
+ v-model="dealForm.dealTime"
+ placeholder="璇烽�夋嫨澶勭悊鏃ユ湡"
+ border="surround"
+ readonly
+ @click="showDatePicker = true"
+ />
+ </up-form-item>
+ </up-form>
+ <view class="dialog-footer">
+ <up-button type="primary" text="鎻愪氦澶勭悊" @click="submitDeal" :loading="submitLoading"></up-button>
+ <up-button text="鍙栨秷" @click="dealDialogVisible = false" customStyle="margin-top: 20rpx"></up-button>
+ </view>
+ </view>
+ </up-popup>
+
+ <!-- 鏃ユ湡閫夋嫨鍣� -->
+ <up-datetime-picker
+ :show="showDatePicker"
+ v-model="dateValue"
+ mode="date"
+ @confirm="confirmDate"
+ @cancel="showDatePicker = false"
+ ></up-datetime-picker>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue';
+import {
+ qualityUnqualifiedListPage,
+ qualityUnqualifiedDeal,
+ qualityUnqualifiedDel
+} from '@/api/qualityManagement/nonconformingManagement.js';
+import { toast, showConfirm } from '@/utils/common';
+import dayjs from 'dayjs';
+
+const searchForm = reactive({
+ productName: '',
+ inspectType: '',
+ inspectState: ''
+});
+
+const tableData = ref([]);
+const page = reactive({
+ current: 1,
+ size: 20,
+ total: 0
+});
+const loadStatus = ref('loadmore');
+
+const showTypeSelect = ref(false);
+const typeActions = [
+ { name: '鍏ㄩ儴', value: '' },
+ { name: '鍏ュ巶妫�', value: '0' },
+ { name: '杞﹂棿妫�', value: '1' },
+ { name: '鍑哄巶妫�', value: '2' }
+];
+const typeLabel = computed(() => {
+ const action = typeActions.find(a => a.value === searchForm.inspectType);
+ return action ? action.name : '鍏ㄩ儴绫诲埆';
+});
+
+const showStatusSelect = ref(false);
+const statusActions = [
+ { name: '鍏ㄩ儴', value: '' },
+ { name: '寰呭鐞�', value: '0' },
+ { name: '宸插鐞�', value: '1' }
+];
+const statusLabel = computed(() => {
+ const action = statusActions.find(a => a.value === searchForm.inspectState);
+ return action ? action.name : '鍏ㄩ儴鐘舵��';
+});
+
+const dealDialogVisible = ref(false);
+const submitLoading = ref(false);
+const currentItem = ref(null);
+const dealForm = reactive({
+ id: null,
+ dealResult: '',
+ dealTime: dayjs().format('YYYY-MM-DD')
+});
+
+const showDatePicker = ref(false);
+const dateValue = ref(Number(new Date()));
+
+const getInspectTypeText = (type) => {
+ const types = { '0': '鍏ュ巶妫�', '1': '杞﹂棿妫�', '2': '鍑哄巶妫�' };
+ return types[type] || '-';
+};
+
+const getStatusText = (state) => {
+ return state === 1 ? '宸插鐞�' : '寰呭鐞�';
+};
+
+const getStatusType = (state) => {
+ return state === 1 ? 'success' : 'warning';
+};
+
+const getList = () => {
+ if (loadStatus.value === 'loading' || (page.total > 0 && tableData.value.length >= page.total)) return;
+
+ loadStatus.value = 'loading';
+ const params = {
+ productName: searchForm.productName || null,
+ inspectType: searchForm.inspectType || null,
+ inspectState: searchForm.inspectState || null,
+ current: page.current,
+ size: page.size
+ };
+
+ qualityUnqualifiedListPage(params).then(res => {
+ const records = res?.data?.records || [];
+ if (page.current === 1) {
+ tableData.value = records;
+ } else {
+ tableData.value = [...tableData.value, ...records];
+ }
+ page.total = res?.data?.total || 0;
+
+ if (tableData.value.length >= page.total) {
+ loadStatus.value = 'nomore';
+ } else {
+ loadStatus.value = 'loadmore';
+ page.current++;
+ }
+ }).catch(() => {
+ loadStatus.value = 'loadmore';
+ });
+};
+
+const handleQuery = () => {
+ page.current = 1;
+ page.total = 0;
+ tableData.value = [];
+ loadStatus.value = 'loadmore';
+ getList();
+};
+
+const selectType = (e) => {
+ searchForm.inspectType = e.value;
+ handleQuery();
+};
+
+const selectStatus = (e) => {
+ searchForm.inspectState = e.value;
+ handleQuery();
+};
+
+const openDealDialog = (item) => {
+ currentItem.value = item;
+ dealForm.id = item.id;
+ dealForm.dealResult = '';
+ dealForm.dealTime = dayjs().format('YYYY-MM-DD');
+ dealDialogVisible.value = true;
+};
+
+const submitDeal = () => {
+ if (!dealForm.dealResult) {
+ toast('璇疯緭鍏ュ鐞嗙粨鏋�');
+ return;
+ }
+ submitLoading.value = true;
+ qualityUnqualifiedDeal(dealForm).then(() => {
+ toast('澶勭悊鎴愬姛');
+ dealDialogVisible.value = false;
+ handleQuery();
+ }).finally(() => {
+ submitLoading.value = false;
+ });
+};
+
+const handleDelete = (row) => {
+ showConfirm('纭鍒犻櫎璇ヤ笉鍚堟牸璁板綍鍚楋紵').then(res => {
+ if (res.confirm) {
+ qualityUnqualifiedDel([row.id]).then(() => {
+ toast('鍒犻櫎鎴愬姛');
+ handleQuery();
+ });
+ }
+ });
+};
+
+const confirmDate = (e) => {
+ dealForm.dealTime = dayjs(e.value).format('YYYY-MM-DD');
+ showDatePicker.value = false;
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+onMounted(() => {
+ handleQuery();
+});
+</script>
+
+<style lang="scss" scoped>
+.nonconforming-management-page {
+ padding-bottom: 20rpx;
+ background-color: #f5f7fa;
+ min-height: 100vh;
+}
+
+.search-section {
+ padding: 20rpx 30rpx;
+ background-color: #ffffff;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+}
+
+.filter-row {
+ display: flex;
+ justify-content: space-around;
+ padding: 10rpx 0;
+}
+
+.filter-item {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ font-size: 28rpx;
+ color: #606266;
+}
+
+.list-container {
+ padding: 20rpx;
+}
+
+.list-item {
+ background-color: #ffffff;
+ border-radius: 16rpx;
+ padding: 30rpx;
+ margin-bottom: 20rpx;
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.item-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20rpx;
+}
+
+.product-name {
+ font-size: 30rpx;
+ font-weight: bold;
+ color: #303133;
+}
+
+.item-content {
+ margin-bottom: 20rpx;
+}
+
+.item-row {
+ display: flex;
+ margin-bottom: 10rpx;
+}
+
+.item-label {
+ color: #909399;
+ width: 180rpx;
+ font-size: 28rpx;
+}
+
+.item-value {
+ flex: 1;
+ color: #303133;
+ font-size: 28rpx;
+}
+
+.text-error {
+ color: #f56c6c;
+}
+
+.text-success {
+ color: #67c23a;
+}
+
+.item-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 20rpx;
+ border-top: 1rpx solid #ebeef5;
+ padding-top: 20rpx;
+}
+
+.no-data {
+ padding-top: 200rpx;
+}
+
+.dialog-content {
+ width: 650rpx;
+ padding: 40rpx;
+ background-color: #ffffff;
+ border-radius: 24rpx;
+ overflow: hidden;
+ box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
+}
+
+.dialog-header {
+ margin-bottom: 30rpx;
+ text-align: center;
+}
+
+.dialog-title {
+ font-size: 32rpx;
+ font-weight: bold;
+}
+
+.info-summary {
+ background-color: #f5f7fa;
+ padding: 20rpx;
+ border-radius: 8rpx;
+ margin-bottom: 30rpx;
+}
+
+.summary-text {
+ display: block;
+ font-size: 26rpx;
+ color: #606266;
+ margin-bottom: 10rpx;
+}
+
+.dialog-footer {
+ margin-top: 40rpx;
+}
+</style>
--
Gitblit v1.9.3