啊鑫
3 天以前 1b8ed4768feeb6433cef55ea8e7c4ad7070c151c
OQC检验
已添加6个文件
已修改2个文件
4136 ■■■■■ 文件已修改
CLAUDE.md 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 192 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/OQC/Add.vue 843 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/OQC/ImageItem.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/OQC/List.vue 1090 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/OQC/ScanCode.vue 745 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/OQC/detail.vue 828 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
store/index.js 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
CLAUDE.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,174 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a uni-app based mobile application for quality control (QC) and warehouse management in a MES (Manufacturing Execution System) environment. The app is specifically designed for PDA devices and supports both Android and iOS platforms.
**App Details:**
- Name: GS-MES-AP (广深科技 MES Application)
- Framework: uni-app with Vue 2
- Version: 1.1.1.4
- Company: å¹¿æ·±ç§‘技 (Guangshen Technology)
## Architecture
### Core Structure
- **Frontend Framework**: Vue 2 with uni-app cross-platform framework
- **State Management**: Vuex store for application state
- **Routing**: uni-app page-based routing defined in `pages.json`
- **Entry Point**: `main.js` - contains global configurations, prototypes, and app initialization
### Key Directories
- `pages/` - All application pages organized by functionality:
  - `BasePages/` - Core app pages (login, main, user, messages)
  - `QC/` - Quality control modules (XJ-巡检, SJ-首检, RKJ-入库检, LLJ-来料检)
  - `Warehouse/` - Warehouse management (inventory, purchases, returns)
  - `Test/` - Testing and development pages
  - `Allocation/` - Transfer operations (in/out)
- `components/` - Reusable UI components and third-party components
- `static/` - Static assets (images, CSS, audio files)
- `store/` - Vuex state management
- `common/` - Shared utilities and mixins
- `utils/` - Utility functions
### Main Functionality Areas
1. **Quality Control (QC)**:
   - å·¡æ£€ (XJ) - Patrol Inspection
   - é¦–检 (SJ) - First Article Inspection
   - å…¥åº“检 (RKJ) - Incoming Inspection
   - æ¥æ–™æ£€ (LLJ) - Material Inspection
2. **Warehouse Management**:
   - Purchase inventory management
   - Material receipt and returns
   - Location changes and transfers
   - Barcode printing and scanning
3. **User Management**:
   - Login/logout functionality
   - User profile and settings
   - Message center for notifications
## Development Environment
This is a uni-app project with no traditional package.json build scripts. Development and building should be done through:
1. **HBuilderX IDE** (recommended uni-app development environment)
2. **uni-app CLI** if using command line
### Build Commands
Since this is a uni-app project without npm scripts, use:
- Development: Open project in HBuilderX and use built-in dev server
- Build for production: Use HBuilderX build tools or uni-app CLI
- No traditional lint/test commands found in the codebase
### Key Configuration Files
- `manifest.json` - App configuration, permissions, and platform settings
- `pages.json` - Page routing and navigation configuration
- `uni.scss` - Global SCSS variables
- `App.vue` - Root component with global styles
### Development Workflow
1. **Project Setup**: Open the project in HBuilderX IDE
2. **Development**: Use HBuilderX's built-in development server for hot reload
3. **Building**: Use HBuilderX's build tools to generate platform-specific builds
4. **Testing**: Manual testing on real devices or emulators (no automated test suite)
## API and Data Flow
### Server Configuration
Server endpoints are configured in `store/index.js`:
- Local development: `http://localhost:10054/api`
- Internal network: `http://192.168.11.251:10054`
- Current active API: `http://localhost:10054/api` (configured in `serverAPI`)
### Network Environment Switching
The app supports switching between internal and external network environments via the `networkFlag` state in the Vuex store.
### Request Methods
Global request methods available on Vue prototype:
- `this.$get()` - GET requests with loading indicators
- `this.$post()` - POST requests with loading indicators
- `this.$postSyncPost()` - Synchronous POST requests
- `this.$toERP()` - ERP system requests
- `this.$uni_request()` - Core request wrapper
- `this.$uni_requestNew()` - Alternative request method
- `this.$sendPostRequest()` - Wrapper for ERP POST requests
### Global Utility Methods
Additional utility methods available on Vue prototype:
- `this.$camera()` - Camera and image selection functionality
- `this.$fileUpload()` - File upload with base64 encoding
- `this.$getDate()` - Date formatting utility
- `this.$getUrlParams()` - URL parameter extraction
- `this.$showMessage()` - Toast notification display
- `this.$showDialog()` - Modal dialog display
- `this.$setTitle()` - Navigation title setting
- `this.$getUserMenu()` - User menu retrieval
### Authentication
User authentication managed through:
- Login info stored in `this.$loginInfo` prototype
- User data persisted in uni.storage
- Forced login required (`forcedLogin: true`)
- Global authentication check via `globalMixin.js` (currently commented out)
- Session management with `this.$login()` and `this.$logout()` methods
## Mobile-Specific Features
### Hardware Integration
- **Camera**: Photo capture for QC inspections (configured in `manifest.json`)
- **Barcode Scanner**: For inventory and QC operations (Barcode module enabled)
- **Printer**: Bluetooth printing for labels and barcodes (via `kk-printer` component)
- **Audio**: Sound notifications for OK/NG status (located in `static/audio/`)
### Device Permissions
Configured in `manifest.json` for Android:
- Camera access for photo capture
- Storage access for file operations
- Network access for API communications
- Bluetooth for printer connectivity
### Platform Support
- Android permissions configured in `manifest.json`
- iOS configuration included
- Cross-platform compatibility via uni-app
## Common Patterns
### Page Navigation
Pages use uni-app navigation with parameters passed via URL query strings. Use `this.$getUrlParams(url)` to extract parameters.
### Data Validation
QC modules include image upload functionality for inspection records with base64 encoding.
### State Management
Vuex store maintains server configuration and app state. Network settings can be switched between internal/external environments.
### Error Handling
Global error handling through unified request methods with toast notifications for user feedback.
### Component Organization
- **Third-party Components**: Located in `components/` directory
  - `kk-printer` - Bluetooth printer integration
  - `uni-*` - Official uni-app UI components
  - `mpvue-*` - Vue-based utility components
- **Custom Components**: Project-specific components in `components/`
  - `page-head` and `page-foot` - Layout components
  - `product.vue` - Product display component
### Code Style and Conventions
- Vue 2 syntax with Options API
- Chinese comments and variable names for business logic
- Prototype-based method attachment for global utilities
- Base64 encoding for image uploads
- Toast notifications for user feedback
- Modal dialogs for confirmations
### File Structure Conventions
- QC pages follow pattern: `List.vue` â†’ `Add.vue` â†’ `detail.vue` â†’ `ImageItem.vue`
- Each QC module (XJ, SJ, RKJ, LLJ) has identical structure
- Warehouse operations follow similar patterns
- Test pages in `Test/` directory for development
pages.json
@@ -29,7 +29,7 @@
        "backgroundColor": "#fbf9fe"
    },
    "pages": [
        //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path": "pages/BasePages/login",
@@ -150,7 +150,7 @@
                "navigationBarTitleText": "入库检"
            }
        },
        {
            "path": "pages/QC/RKJ/Add",
            "style": {
@@ -273,145 +273,161 @@
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseInventory/PurchaseInventory",
            "style" :
            {
                "navigationBarTitleText" : "采购入库"
            "path": "pages/Warehouse/PurchaseInventory/PurchaseInventory",
            "style": {
                "navigationBarTitleText": "采购入库"
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseInventory/Cgrk",
            "style" :
            {
                "navigationBarTitleText" : "采购入库单"
            "path": "pages/Warehouse/PurchaseInventory/Cgrk",
            "style": {
                "navigationBarTitleText": "采购入库单"
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseInventory/Add",
            "style" :
            {
                "navigationBarTitleText" : "采购入库单明细"
            "path": "pages/Warehouse/PurchaseInventory/Add",
            "style": {
                "navigationBarTitleText": "采购入库单明细"
            }
        },
        {
            "path" : "pages/Test/scanTest",
            "style" :
            {
                "navigationBarTitleText" : "扫码测试"
            "path": "pages/Test/scanTest",
            "style": {
                "navigationBarTitleText": "扫码测试"
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseReturn/Cgth",
            "style" :
            {
                "navigationBarTitleText" : "采购退货"
            "path": "pages/Warehouse/PurchaseReturn/Cgth",
            "style": {
                "navigationBarTitleText": "采购退货"
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseReturn/Add",
            "style" :
            {
                "navigationBarTitleText" : "添加采购退料单"
            "path": "pages/Warehouse/PurchaseReturn/Add",
            "style": {
                "navigationBarTitleText": "添加采购退料单"
            }
        },
        {
            "path" : "pages/Warehouse/PurchaseReturn/PurchaseReturn",
            "style" :
            {
                "navigationBarTitleText" : "采购退料扫码"
            "path": "pages/Warehouse/PurchaseReturn/PurchaseReturn",
            "style": {
                "navigationBarTitleText": "采购退料扫码"
            }
        },
        {
            "path" : "pages/BasePages/information",
            "style" :
            {
                "navigationBarTitleText" : "信息详情"
            "path": "pages/BasePages/information",
            "style": {
                "navigationBarTitleText": "信息详情"
            }
        },
        {
            "path" : "pages/Warehouse/OpeningReceipt/OpeningReceipt",
            "style" :
            {
                "navigationBarTitleText" : "期初入库扫码"
            "path": "pages/Warehouse/OpeningReceipt/OpeningReceipt",
            "style": {
                "navigationBarTitleText": "期初入库扫码"
            }
        },
        {
            "path" : "pages/Allocation/In",
            "style" :
            {
                "navigationBarTitleText" : "调拨入库"
            "path": "pages/Allocation/In",
            "style": {
                "navigationBarTitleText": "调拨入库"
            }
        },
        {
            "path" : "pages/Allocation/Out",
            "style" :
            {
                "navigationBarTitleText" : "调拨出库"
            "path": "pages/Allocation/Out",
            "style": {
                "navigationBarTitleText": "调拨出库"
            }
        },
        {
            "path" : "pages/Warehouse/ChangeOfLocation/ChangeOfLocation",
            "style" :
            {
                "navigationBarTitleText" : "库位变更"
            "path": "pages/Warehouse/ChangeOfLocation/ChangeOfLocation",
            "style": {
                "navigationBarTitleText": "库位变更"
            }
        },
        {
            "path" : "pages/Test/testPrint",
            "style" :
            {
                "navigationBarTitleText" : "打印测试"
            "path": "pages/Test/testPrint",
            "style": {
                "navigationBarTitleText": "打印测试"
            }
        },
        {
            "path" : "pages/BarcodePrint/BarcodePrint",
            "style" :
            {
                "navigationBarTitleText" : "期初条码打印"
            "path": "pages/BarcodePrint/BarcodePrint",
            "style": {
                "navigationBarTitleText": "期初条码打印"
            }
        },
        {
            "path" : "pages/Warehouse/MaterialReceipt/MaterialReceipt",
            "style" :
            {
                "navigationBarTitleText" : "物料接收"
            "path": "pages/Warehouse/MaterialReceipt/MaterialReceipt",
            "style": {
                "navigationBarTitleText": "物料接收"
            }
        },
        {
            "path" : "pages/Warehouse/ProductionPick/ProductionPick",
            "style" :
            {
                "navigationBarTitleText" : "生产领料"
            "path": "pages/Warehouse/ProductionPick/ProductionPick",
            "style": {
                "navigationBarTitleText": "生产领料"
            }
        },
        {
            "path" : "pages/Warehouse/ProductionPick/List",
            "style" :
            {
                "navigationBarTitleText" : "生产领料单"
            "path": "pages/Warehouse/ProductionPick/List",
            "style": {
                "navigationBarTitleText": "生产领料单"
            }
        },
        {
            "path" : "pages/Warehouse/ProductionPick/Add",
            "style" :
            {
                "navigationBarTitleText" : "生产领料单"
            "path": "pages/Warehouse/ProductionPick/Add",
            "style": {
                "navigationBarTitleText": "生产领料单"
            }
        },
        {
                    "path" : "pages/BasePages/treated",
                    "style" :
                    {
                        "navigationBarTitleText" : "待处理"
                    }
                },
                {
                    "path": "pages/Device/Spotcheck",
                    "style": {
                        "navigationBarTitleText": "设备点检",
                        "enablePullDownRefresh": false
                    }
                }
            "path": "pages/BasePages/treated",
            "style": {
                "navigationBarTitleText": "待处理"
            }
        },
        {
            "path" : "pages/QC/OQC/List",
            "style" :
            {
                "navigationBarTitleText" : "OQC检验"
            }
        },
        {
            "path" : "pages/QC/OQC/ScanCode",
            "style" :
            {
                "navigationBarTitleText" : "OQC检验"
            }
        },
        {
            "path" : "pages/QC/OQC/Add",
            "style" :
            {
                "navigationBarTitleText" : ""
            }
        },
        {
            "path" : "pages/QC/OQC/ImageItem",
            "style" :
            {
                "navigationBarTitleText" : "图片查看"
            }
        },
        {
            "path" : "pages/QC/OQC/detail",
            "style" :
            {
                "navigationBarTitleText" : "检验详情"
            }
        }
        // {
        //     "path": "pages/Device/Spotcheck",
        //     "style": {
        //         "navigationBarTitleText": "设备点检",
        //         "enablePullDownRefresh": false
        //     }
        // }
    ]
}
pages/QC/OQC/Add.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,843 @@
<template>
    <view class="page-container">
        <!-- è¡¨å•容器 -->
        <view class="form-card">
            <form :modelValue="formData">
                <view class="form-group">
                    <label class="form-label">检验单号:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.releaseNo" />
                </view>
                <view class="form-group">
                    <label class="form-label">物料编码:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.itemNo" />
                </view>
                <view class="form-group">
                    <label class="form-label">物料名称:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.itemName" />
                </view>
                <view class="form-group">
                    <label class="form-label">物料规格:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.itemModel" />
                </view>
                <view class="form-group">
                    <label class="form-label">订单编号:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.saleOrderNo" />
                </view>
                <view class="form-group">
                    <label class="form-label">送检数量:</label>
                    <input class="form-input" disabled="true" type="number" v-model="formData.planQty" />
                </view>
                <view class="form-group">
                    <label class="form-label">创建时间:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.createDate" />
                </view>
                <view class="form-group">
                    <label class="form-label">创建人:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.createUser" />
                </view>
                <view class="form-group">
                    <label class="form-label">不良描述:</label>
                    <input class="form-input" disabled="true" type="text" v-model="formData.remeke" />
                </view>
            </form>
        </view>
        <!-- æ£€éªŒé¡¹ç›®å¡ç‰‡ -->
        <view class="inspection-card">
            <view class="card-header">
                <view class="header-icon">🔍</view>
                <text class="header-title">检验项目</text>
                <view class="header-badge">{{ tableData.length }}</view>
            </view>
            <view class="inspection-list" v-if="tableData.length > 0">
                <view v-for="(item, index) in tableData" :key="index" class="inspection-item"
                    :class="{ 'item-completed': item.fcheckResu === '合格', 'item-failed': item.fcheckResu === '不合格' }">
                    <!-- å·¦ä¾§çŠ¶æ€æŒ‡ç¤ºå™¨ -->
                    <view class="status-indicator"
                        :class="{ 'status-pass': item.fcheckResu === '合格', 'status-fail': item.fcheckResu === '不合格', 'status-pending': item.fcheckResu === '未检验' }">
                    </view>
                    <!-- ä¸»è¦å†…容区域 -->
                    <view class="item-content">
                        <view class="item-header">
                            <view class="item-title">{{ item.fcheckItem }}</view>
                            <view class="status-badge"
                                :class="{ 'badge-pass': item.fcheckResu === '合格', 'badge-fail': item.fcheckResu === '不合格', 'badge-pending': item.fcheckResu === '未检验' }">
                                <text class="status-icon">{{ item.fcheckResu === '合格' ? '✓' : item.fcheckResu === '不合格' ? '✗' : '○' }}</text>
                                <text class="status-text">{{ item.fcheckResu }}</text>
                            </view>
                        </view>
                        <view class="item-description" v-if="item.fcheckItemDesc">
                            <text class="description-text">{{ item.fcheckItemDesc }}</text>
                        </view>
                        <view class="item-footer">
                            <view class="progress-info">
                                <text class="progress-label">检验进度:</text>
                                <view class="progress-bar">
                                    <view class="progress-fill"
                                        :style="{ width: (item.fenterQty / item.checkQyt * 100) + '%' }"
                                        :class="{ 'progress-complete': item.fenterQty >= item.checkQyt, 'progress-incomplete': item.fenterQty < item.checkQyt }">
                                    </view>
                                </view>
                                <text class="progress-text">{{ item.fenterQty }}/{{ item.checkQyt }}</text>
                            </view>
                            <view class="action-button" @click="toDetail(item)"
                                :class="{ 'btn-complete': item.fenterQty >= item.checkQyt, 'btn-incomplete': item.fenterQty < item.checkQyt }">
                                <text class="btn-text">{{ item.fenterQty >= item.checkQyt ? '查看详情' : '开始检验' }}</text>
                                <text class="btn-icon">→</text>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <!-- ç©ºçŠ¶æ€ -->
            <view v-else class="empty-state">
                <view class="empty-icon">📋</view>
                <text class="empty-text">暂无检验项目</text>
                <text class="empty-desc">请先生成检验项目</text>
            </view>
        </view>
        <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
        <view class="action-buttons">
            <button class="btn btn-primary" @click="submitInspection">提交</button>
            <button class="secondary-btn" @click="uploadImages">上传/查看图片</button>
            <button class="btn btn-secondary" @click="addDefectDescription">添加不合格描述</button>
        </view>
        <!-- ä¸åˆæ ¼æè¿°å¼¹å‡ºå±‚ -->
        <view v-if="remarksPopup" class="overlay active">
          <view class="popup" :class="{ 'popup-scale': isPopupAnimated }" @animationend="isPopupAnimated = false">
            <view class="popup-header">
              <h3 class="popup-title">修改不合格描述</h3>
              <view class="close-btn" @click="remarksPopup = !remarksPopup">×</view>
            </view>
            <form>
              <view class="form-group">
                <label class="form-label">不合格描述:</label>
                <input class="form-input" type="text" v-model="remarks" />
              </view>
              <view class="button-group popup-buttons">
                <button :class="['action-btn', 'btn-danger']" @click="editRemarks">
                  ä¿®æ”¹
                </button>
                <button :class="['action-btn', 'btn-light']" @click="remarksPopup = !remarksPopup">
                  å–消
                </button>
              </view>
            </form>
          </view>
        </view>
    </view>
</template>
<script>
    export default {
        data() {
            return {
                formData: {},
                tableData: [],
                remarks: "",
                remarksPopup: false,
                isPopupAnimated: false,
            }
        },
        onLoad(options) {
            //options中包含了url附带的参数
            let params = options;
            if (params["id"]) {
                this.formData.id = params["id"];
                this.init();
            }
        },
        methods: {
            init() {
                let userName = this.$loginInfo.account;
                this.$post({
                    url: "/MesOqcItemsDetect02/getPage",
                    data: {
                        id: this.formData.id,
                        createUser: userName,
                        pageIndex: 1,
                        limit: 1,
                    }
                }).then(res => {
                    let data = res.data[0];
                    if (data) {
                        this.formData = data;
                        this.getDetail5();
                    }
                });
            },
            getDetail5() {
                let userName = this.$loginInfo.account;
                this.$post({
                    url: "/MesOqcItemsDetect02/GetDetail5",
                    data: {
                        id: this.formData.id,
                        createUser: userName,
                        releaseNo: this.formData.releaseNo,
                    }
                }).then(res => {
                    let data = res.data;
                    this.tableData = data;
                });
            },
            uploadImages() {
                // ä¸Šä¼ /查看图片的逻辑
                uni.navigateTo({
                    url: 'ImageItem?id=' + this.formData.releaseNo
                });
            },
            addDefectDescription() {
                // ç¡®ä¿è¡¨å•数据存在
                if (!this.formData) return;
                this.remarksPopup = !this.remarksPopup;
                this.remarks = this.formData.remeke || '';
                this.isPopupAnimated = true;
            },
            toDetail(item) {
                uni.navigateTo({
                    url: 'detail?mainId=' + item.id + '&releaseNo=' + this.formData
                        .releaseNo
                });
            },
            submitInspection() {
                // æ£€éªŒæäº¤çš„逻辑
                this.$post({
                    url: "/MesOqcItemsDetect02/IqcQaSubmit",
                    data: {
                        userNo: this.$loginInfo.account,
                        releaseNo: this.formData.releaseNo
                    }
                }).then(res => {
                    if (res.status == 0) {
                        uni.showToast({
                            title: res.message.toString(),
                            icon: 'success',
                            duration: 2000
                        })
                        // å¦‚果有页面跳转,需要用定时器延迟
                        setTimeout(() => {
                            uni.navigateTo({
                                url: 'List'
                            });
                        }, 2000); // ä¿æŒä¸Ž duration ç›¸åŒçš„æ—¶é•¿
                    } else {
                        uni.showModal({
                            title: "提示",
                            content: res.message.toString(),
                            confirmText: "确定",
                            showCancel: false,
                            success: (res) => {
                            }
                        })
                    }
                })
            },
            editRemarks() {
              if (this.remarks && this.formData.id) {
                this.$post({
                  url: "/MesOqcItemsDetect02/saveRemarksGid",
                  data: {
                    gid: this.formData.id,
                    remarks: this.remarks,
                    releaseNo: this.formData.releaseNo,
                  }
                }).then(res => {
                  if (res.data.tbBillList > 0) {
                    this.formData.remarks = this.remarks;
                    this.remarksPopup = false;
                    this.$showMessage("保存成功");
                    this.init();
                  } else {
                    this.$showMessage("保存失败");
                  }
                }).catch(() => {
                  this.$showMessage("保存失败,请重试");
                });
              } else if (!this.formData.id) {
                this.$showMessage("请先生成检验单");
              } else {
                this.$showMessage("请输入不合格描述");
              }
            },
        }
    }
</script>
<style scoped>
.page-container {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
/* è¡¨å•卡片 */
.form-card {
  background-color: #fff;
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* è¡¨å•组 */
.form-group {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
}
.form-group:last-child {
  margin-bottom: 0;
}
/* æ ‡ç­¾æ ·å¼ */
.form-label {
  width: 120px;
  color: #333;
  font-size: 14px;
  font-weight: 500;
  flex-shrink: 0;
}
/* è¾“入框样式 */
.form-input {
  flex: 1;
  height: 40px;
  padding: 0 10px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background-color: #f8f8f8;
  color: #666;
  font-size: 14px;
  transition: all 0.3s ease;
}
.form-input:disabled {
  background-color: #e9e9e9;
}
.form-input:focus {
  border-color: #007AFF;
  outline: none;
}
/* æ“ä½œæŒ‰é’®åŒºåŸŸ */
.action-buttons {
  display: flex;
  justify-content: space-between;
  gap: 15px;
  padding: 15px 0;
}
.btn {
  flex: 1;
  height: 45px;
  border-radius: 8px;
  border: none;
  color: #fff;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color 0.3s ease;
}
.btn-primary {
  background-color: #007AFF;
}
.btn-primary:hover {
  background-color: #0056CC;
}
.secondary-btn {
  background-color: #8E8E93;
}
.secondary-btn:hover {
  background-color: #6D6D70;
}
.btn-secondary {
  background-color: #8E8E93;
}
.btn-secondary:hover {
  background-color: #6D6D70;
}
/* æ£€éªŒé¡¹ç›®å¡ç‰‡ */
.inspection-card {
  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
  border-radius: 16px;
  padding: 0;
  margin-bottom: 24px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
  border: 1px solid #e2e8f0;
  overflow: hidden;
}
/* å¡ç‰‡å¤´éƒ¨ */
.card-header {
  background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
  padding: 20px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
}
.card-header::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, transparent 100%);
  pointer-events: none;
}
.header-icon {
  font-size: 24px;
  margin-right: 12px;
}
.header-title {
  color: white;
  font-size: 18px;
  font-weight: 600;
  flex: 1;
}
.header-badge {
  background: rgba(255, 255, 255, 0.2);
  color: white;
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 14px;
  font-weight: 500;
  backdrop-filter: blur(10px);
}
/* æ£€éªŒåˆ—表 */
.inspection-list {
  padding: 24px;
}
.inspection-item {
  background: white;
  border-radius: 12px;
  margin-bottom: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  border: 1px solid #e2e8f0;
  overflow: hidden;
  transition: all 0.3s ease;
  position: relative;
}
.inspection-item:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.inspection-item:last-child {
  margin-bottom: 0;
}
.inspection-item.item-completed {
  border-left: 4px solid #10b981;
  background: linear-gradient(135deg, #ffffff 0%, #f0fdf4 100%);
}
.inspection-item.item-failed {
  border-left: 4px solid #ef4444;
  background: linear-gradient(135deg, #ffffff 0%, #fef2f2 100%);
}
/* çŠ¶æ€æŒ‡ç¤ºå™¨ */
.status-indicator {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: #e2e8f0;
}
.status-indicator.status-pass {
  background: linear-gradient(180deg, #10b981 0%, #059669 100%);
}
.status-indicator.status-fail {
  background: linear-gradient(180deg, #ef4444 0%, #dc2626 100%);
}
.status-indicator.status-pending {
  background: linear-gradient(180deg, #f59e0b 0%, #d97706 100%);
}
/* é¡¹ç›®å†…容 */
.item-content {
  padding: 20px 24px;
  margin-left: 4px;
}
.item-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.item-title {
  font-size: 16px;
  font-weight: 600;
  color: #1e293b;
  flex: 1;
}
.status-badge {
  display: flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
}
.status-badge.badge-pass {
  background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
  color: #166534;
  border: 1px solid #86efac;
}
.status-badge.badge-fail {
  background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
  color: #991b1b;
  border: 1px solid #fca5a5;
}
.status-badge.badge-pending {
  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
  color: #92400e;
  border: 1px solid #fcd34d;
}
.status-icon {
  margin-right: 6px;
  font-weight: bold;
}
/* é¡¹ç›®æè¿° */
.item-description {
  margin-bottom: 16px;
}
.description-text {
  color: #64748b;
  font-size: 14px;
  line-height: 1.5;
  background: #f8fafc;
  padding: 12px 16px;
  border-radius: 8px;
  border-left: 3px solid #e2e8f0;
}
/* é¡¹ç›®åº•部 */
.item-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.progress-info {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
}
.progress-label {
  font-size: 14px;
  color: #64748b;
  font-weight: 500;
  white-space: nowrap;
}
.progress-bar {
  flex: 1;
  height: 8px;
  background: #e2e8f0;
  border-radius: 4px;
  overflow: hidden;
  position: relative;
}
.progress-fill {
  height: 100%;
  border-radius: 4px;
  transition: all 0.3s ease;
  position: relative;
}
.progress-fill.progress-complete {
  background: linear-gradient(90deg, #10b981 0%, #059669 100%);
}
.progress-fill.progress-incomplete {
  background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
}
.progress-text {
  font-size: 14px;
  color: #374151;
  font-weight: 600;
  white-space: nowrap;
}
/* æ“ä½œæŒ‰é’® */
.action-button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
}
.action-button.btn-complete {
  background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
  color: #1e40af;
  border: 1px solid #93c5fd;
}
.action-button.btn-complete:hover {
  background: linear-gradient(135deg, #bfdbfe 0%, #93c5fd 100%);
  transform: translateX(4px);
}
.action-button.btn-incomplete {
  background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
  color: #92400e;
  border: 1px solid #fcd34d;
}
.action-button.btn-incomplete:hover {
  background: linear-gradient(135deg, #fde68a 0%, #fcd34d 100%);
  transform: translateX(4px);
}
.btn-text {
  font-size: 14px;
}
.btn-icon {
  font-size: 16px;
  transition: transform 0.3s ease;
}
.action-button:hover .btn-icon {
  transform: translateX(4px);
}
/* ç©ºçŠ¶æ€ */
.empty-state {
  padding: 60px 24px;
  text-align: center;
  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.empty-icon {
  font-size: 48px;
  margin-bottom: 16px;
  opacity: 0.6;
}
.empty-text {
  font-size: 18px;
  color: #64748b;
  font-weight: 500;
  margin-bottom: 8px;
  display: block;
}
.empty-desc {
  font-size: 14px;
  color: #94a3b8;
  display: block;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .inspection-card {
    margin: 0 -8px 24px -8px;
    border-radius: 12px;
  }
  .card-header {
    padding: 16px 20px;
  }
  .header-title {
    font-size: 16px;
  }
  .inspection-list {
    padding: 16px 20px;
  }
  .item-content {
    padding: 16px 20px;
  }
  .item-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .item-footer {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .progress-info {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }
  .action-button {
    justify-content: center;
  }
}
/* è¾“入框中显示的工具提示 */
.value-with-tooltip {
  position: relative;
}
.value-with-tooltip .tooltip {
  position: absolute;
  top: -30px;
  left: 0;
  background-color: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 12px;
  padding: 5px;
  border-radius: 4px;
}
/* å¼¹å‡ºå±‚样式 */
.overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.overlay.active {
  opacity: 1;
}
.popup {
  background-color: #fff;
  border-radius: 12px;
  width: 90%;
  max-width: 500px;
  padding: 20px;
  transform: scale(0.8);
  transition: transform 0.3s ease;
}
.popup-scale {
  transform: scale(1);
}
.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.popup-title {
  font-size: 16px;
  font-weight: 600;
  color: #333;
}
.close-btn {
  font-size: 20px;
  color: #333;
  cursor: pointer;
}
.button-group {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}
.action-btn {
  width: 48%;
  padding: 10px;
  border-radius: 8px;
  border: none;
  font-size: 14px;
  font-weight: 500;
}
.btn-danger {
  background-color: #dc3545;
  color: #fff;
}
.btn-light {
  background-color: #f8f8f8;
  color: #333;
}
.btn-danger:hover {
  background-color: #c82333;
}
.btn-light:hover {
  background-color: #e9e9e9;
}
</style>
pages/QC/OQC/ImageItem.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,234 @@
<template>
  <!-- #ifdef APP -->
  <scroll-view class="page-scroll-view">
    <!-- #endif -->
    <view>
      <view class="uni-common-mt">
        <view class="uni-list list-pd" style="padding: 15px;">
          <view class="uni-flex" style="margin-bottom: 10px;">
            <view class="uni-list-cell-left">点击可预览选好的图片</view>
            <view style="margin-left: auto;">
              <view class="click-t">共有{{ qsImage.length }}张图片</view>
            </view>
          </view>
          <view class="uni-flex" style="flex-wrap: wrap;">
            <view v-for="(image,index) in qsImage" :key="index" class="uni-uploader__input-box"
                  style="position: relative; border: 0;">
              <image :src="image.img" :data-src="image.img"
                     @tap="previewImage(index)"></image>
              <image src="/static/plus.png" class="image-remove" @click="removeImage(index,image.id)"></image>
            </view>
            <image class="uni-uploader__input-box" @tap="chooseImage" src="/static/plus.png"></image>
          </view>
        </view>
      </view>
      <view class="plus-button">
        <button type="primary" class="upImg" @click="save">上传图片</button>
      </view>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>
<script>
import {pathToBase64, base64ToPath} from '../../../js_sdk/mmmm-image-tools/index'
var sourceTypeArray = [
  ['camera'],
  ['album'],
  ['camera', 'album']
]
var sizeTypeArray = [
  ['compressed'],
  ['original'],
  ['compressed', 'original']
]
export default {
  data() {
    return {
      title: 'choose/previewImage',
      sourceTypeIndex: 2,
      sourceType: ['拍照', '相册', '拍照或相册'],
      sizeTypeIndex: 2,
      sizeType: ['压缩', '原图', '压缩或原图'],
      countIndex: 8,
      count: [1, 2, 3, 4, 5, 6, 7, 8, 9],
      isCrop: false,
      cropPercent: 80,
      cropWidth: 100,
      cropHeight: 100,
      cropResize: false,
      qsImage: [],
      fid: 0,
      qsType : 5,
    }
  },
  onLoad(options) {
    //options中包含了url附带的参数
    let params = options;
    if (params["id"]) {
      this.fid = params["id"];
      //getQaItemXj02
      this.init();
    }
  },
  onUnload() {
    this.qsImage = [];
    this.sourceTypeIndex = 2
    this.sourceType = ['拍照', '相册', '拍照或相册']
    this.sizeTypeIndex = 2
    this.sizeType = ['压缩', '原图', '压缩或原图']
    this.countIndex = 8
  },
  methods: {
    removeImage(index, id) {
      this.qsImage.splice(index, 1);
      if (id) {
        this.$post({
          url: "/Base/removeImage",
          data: {
            id: id
          }
        }).then(res => {
        });
      }
    },
    chooseImage() {
      if (this.qsImage.length >= 9) {
        uni.showToast({
          position: "bottom",
          title: "已经有9张图片了,请删除部分图片之后重新选择"
        });
        return;
      }
      uni.chooseImage({
        sourceType: sourceTypeArray[this.sourceTypeIndex],
        sizeType: sizeTypeArray[this.sizeTypeIndex],
        crop: this.isCrop ? {
          "quality": this.cropPercent,
          "width": this.cropWidth,
          "height": this.cropHeight,
          "resize": this.cropResize
        } : null,
        count: this.qsImage.length + this.count[this.countIndex] > 9 ? 9 - this.qsImage.length : this.count[this.countIndex],
        success: (res) => {
          let url = res.tempFilePaths[0];
          pathToBase64(url)
              .then(base64 => {
                // æ‰¾åˆ°æœ€åŽä¸€ä¸ªæ–œæ çš„位置
                let lastSlashIndex = url.lastIndexOf("/");
                // æå–文件名
                let fileName = url.substring(lastSlashIndex + 1);
                let entity = {};
                entity.img = base64;
                entity.Picturename = fileName;
                entity.fid = this.fid;
                entity.qsType = this.qsType;
                entity.base64Date = base64.split(',')[1];
                this.qsImage.push(entity);
              })
              .catch(error => {
                console.error(error)
              })
        },
        fail: (err) => {
          console.log("err: ", JSON.stringify(err));
        }
      });
    },
    previewImage(index) {
      // uni.previewImage({
      //   current: index, // è®¾ç½®å½“前显示图片的链接
      //   urls: this.qsImage.map(s=>s.img), // éœ€è¦é¢„览的图片链接列表
      //   loop: false, // æ˜¯å¦å¼€å¯å›¾ç‰‡è½®æ’­ï¼Œé»˜è®¤ä¸º false
      //   indicator: 'default',// å›¾ç‰‡æŒ‡ç¤ºå™¨ç±»åž‹ï¼Œå¯é€‰å€¼ä¸º "default"、"number"、"pointer",默认为 "default"
      // });
    },
    init() {
      this.$post({
        url: "/Base/getLljAllImgByFid",
        data: {
          id: this.fid,
          qsType: this.qsType
        }
      }).then(res => {
        let tableData = res.data.tbBillList;
        this.qsImage = tableData;
        this.qsImage.forEach(s => {
          s.img = 'data:image/png;base64,' + s.base64Date;
        });
      });
    },
    save() {
      this.$post({
        url: "/Base/saveImage",
        data: {
          entity: this.qsImage
        }
      }).then(res => {
        this.init();
        this.$showMessage("保存成功");
      });
    }
  }
}
</script>
<style>
.click-t {
  color: darkgray;
}
.list-pd {
  margin-top: 25px;
}
.uni-uploader__input-box {
  margin: 5px;
  border: 1px solid #D9D9D9;
}
.image-remove {
  transform: rotate(45deg);
  width: 25px;
  height: 25px;
  position: absolute;
  top: 0;
  right: 0;
  border-radius: 13px;
  background-color: #FF0000;
}
.uni-common-mt {
  background-color: #ffffff;
  /* çº¢è‰²èƒŒæ™¯ */
}
.plus-button {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  background-color: #ffffff; /* èƒŒæ™¯é¢œè‰² */
  /* padding: 10px; */
  box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); /* æ·»åŠ åº•éƒ¨é˜´å½±æ•ˆæžœ */
  z-index: 999; /* ç¡®ä¿æŒ‰é’®ä½äºŽé¡¶å±‚ */
}
.uni-flex {
  max-height: calc(100vh - 240px); /* å±å¹•高度减去上传按钮高度 */
  overflow-y: auto; /* å½“内容超出高度时出现垂直滚动条 */
}
.upImg{
      background-color: #3498db;
      color: white;
}
</style>
pages/QC/OQC/List.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1090 @@
<template>
    <view class="page-container">
        <!-- åˆ·æ–°æç¤ºæ¡† -->
        <view class="success-toast" :class="{ 'show': tipShow }">
            <view class="toast-icon">✓</view>
            <text class="toast-text">刷新成功</text>
        </view>
        <!-- å¤´éƒ¨åŒºåŸŸ -->
        <view class="header-section">
            <!-- é¡µé¢æ ‡é¢˜ -->
            <view class="page-header">
                <view class="header-content">
                    <view class="title-section">
                        <text class="page-title">OQC检验单</text>
                        <text class="page-subtitle">质量检验管理</text>
                    </view>
                    <view class="stats-badge">
                        <text class="stats-number">{{ totalCount }}</text>
                        <text class="stats-label">总计</text>
                    </view>
                </view>
            </view>
            <!-- æœç´¢æ  -->
            <!-- <view class="search-section"> -->
                <!-- <view class="search-container">
                    <view class="search-input-wrapper">
                        <uni-icons type="search" size="18" color="#94a3b8"></uni-icons>
                        <input class="search-input" type="text" v-model="searchValue"
                            @confirm="getInputValue" placeholder="搜索检验单号、物料编码..." />
                        <view v-if="searchValue" class="clear-btn" @tap="clearSearch">
                            <uni-icons type="clear" size="16" color="#94a3b8"></uni-icons>
                        </view>
                    </view>
                    <view class="filter-btn" @tap="toggleFilter">
                        <uni-icons type="tune" size="18" color="#4f46e5"></uni-icons>
                    </view>
                </view> -->
                <!-- ç­›é€‰å™¨ -->
                <!-- <view v-if="showFilter" class="filter-panel">
                    <picker mode="selector" :range="searchOptions" v-model="selectedOption" @change="onPickerChange">
                        <view class="filter-option">
                            <text class="filter-label">筛选条件</text>
                            <text class="filter-value">{{ searchOptions[selectedOption] }}</text>
                            <uni-icons type="arrowdown" size="14" color="#64748b"></uni-icons>
                        </view>
                    </picker>
                </view> -->
            <!-- </view> -->
            <!-- é€‰é¡¹å¡ -->
            <view class="tab-section">
                <view class="custom-tabs">
                    <view v-for="(item, index) in items" :key="index"
                        class="tab-item" :class="{ 'active': current === index }"
                        @tap="onClickItem({ currentIndex: index })">
                        <text class="tab-text">{{ item }}</text>
                        <view class="tab-indicator" v-if="current === index"></view>
                    </view>
                </view>
            </view>
        </view>
        <!-- å†…容区域 -->
        <view class="content-container">
            <view v-show="current === 0">
                <!-- åŠ è½½çŠ¶æ€ -->
                <view v-if="isLoading" class="loading-state">
                    <view class="loading-spinner"></view>
                    <text class="loading-text">加载中...</text>
                </view>
                <!-- ç©ºçŠ¶æ€ -->
                <view v-else-if="data.length === 0" class="empty-state">
                    <view class="empty-icon">📋</view>
                    <text class="empty-title">暂无检验单</text>
                    <text class="empty-desc">点击右下角按钮创建新的检验单</text>
                </view>
                <!-- æ£€éªŒå•列表 -->
                <view v-else class="inspection-list">
                    <view v-for="item in data" :key="item.id || item.releaseNo"
                        class="inspection-card" @tap="navigateToDetail(item)">
                        <!-- å¡ç‰‡å¤´éƒ¨ -->
                        <view class="card-header">
                            <view class="header-left">
                                <view class="inspection-number">
                                    <text class="number-label">检验单号</text>
                                    <text class="number-value">{{ item.releaseNo }}</text>
                                </view>
                                <view class="material-info">
                                    <text class="material-code">{{ item.itemNo }}</text>
                                    <text class="material-name">{{ item.itemName || '未知物料' }}</text>
                                </view>
                            </view>
                            <view class="header-right">
                                <view class="status-badge"
                                    :class="{
                                        'status-submitted': item.fsubmit == 1,
                                        'status-pending': item.fsubmit != 1
                                    }">
                                    <view class="status-dot"></view>
                                    <text class="status-text">{{ item.fsubmit == 1 ? '已提交' : '待提交' }}</text>
                                </view>
                            </view>
                        </view>
                        <!-- æ£€éªŒç»“果区域 -->
                        <view class="inspection-result">
                            <view class="result-item">
                                <view class="result-icon"
                                    :class="{
                                        'icon-pass': item.fcheckResu === '合格',
                                        'icon-fail': item.fcheckResu === '不合格',
                                        'icon-pending': !item.fcheckResu || item.fcheckResu === '未检验'
                                    }">
                                    <text class="result-symbol">{{
                                        item.fcheckResu === '合格' ? '✓' :
                                        item.fcheckResu === '不合格' ? '✗' : '○'
                                    }}</text>
                                </view>
                                <view class="result-content">
                                    <text class="result-label">检验结果</text>
                                    <text class="result-value"
                                        :class="{
                                            'value-pass': item.fcheckResu === '合格',
                                            'value-fail': item.fcheckResu === '不合格',
                                            'value-pending': !item.fcheckResu || item.fcheckResu === '未检验'
                                        }">
                                        {{ item.fcheckResu || '未检验' }}
                                    </text>
                                </view>
                            </view>
                            <view class="inspector-info">
                                <text class="inspector-label">检验人</text>
                                <text class="inspector-name">{{ item.modify1By || '待分配' }}</text>
                            </view>
                        </view>
                        <!-- è¯¦ç»†ä¿¡æ¯ -->
                        <view class="card-details">
                            <view class="detail-grid">
                                <view class="detail-item">
                                    <text class="detail-label">创建时间</text>
                                    <text class="detail-value">{{ item.createDate }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">数量</text>
                                    <text class="detail-value">{{ item.planQty }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">创建人</text>
                                    <text class="detail-value">{{ item.createUser }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">送检人</text>
                                    <text class="detail-value">{{ item.fcheckUser || '-' }}</text>
                                </view>
                            </view>
                        </view>
                        <!-- æ“ä½œæŒ‡ç¤ºå™¨ -->
                        <view class="action-indicator">
                            <uni-icons type="arrowright" size="16" color="#94a3b8"></uni-icons>
                        </view>
                    </view>
                </view>
            </view>
            <!-- ç¬¬äºŒä¸ªé€‰é¡¹å¡å†…容 - å·²æ£€éªŒ -->
            <view v-show="current === 1">
                <!-- åŠ è½½çŠ¶æ€ -->
                <view v-if="isLoading" class="loading-state">
                    <view class="loading-spinner"></view>
                    <text class="loading-text">加载中...</text>
                </view>
                <!-- ç©ºçŠ¶æ€ -->
                <view v-else-if="data.length === 0" class="empty-state">
                    <view class="empty-icon">✅</view>
                    <text class="empty-title">暂无已检验单据</text>
                    <text class="empty-desc">完成检验的单据将显示在这里</text>
                </view>
                <!-- æ£€éªŒå•列表 -->
                <view v-else class="inspection-list">
                    <view v-for="item in data" :key="item.id || item.releaseNo"
                        class="inspection-card completed" @tap="navigateToDetail(item)">
                        <!-- å¡ç‰‡å¤´éƒ¨ -->
                        <view class="card-header">
                            <view class="header-left">
                                <view class="inspection-number">
                                    <text class="number-label">检验单号</text>
                                    <text class="number-value">{{ item.releaseNo }}</text>
                                </view>
                                <view class="material-info">
                                    <text class="material-code">{{ item.itemNo }}</text>
                                    <text class="material-name">{{ item.itemName || '未知物料' }}</text>
                                </view>
                            </view>
                            <view class="header-right">
                                <view class="status-badge status-completed">
                                    <view class="status-dot"></view>
                                    <text class="status-text">已完成</text>
                                </view>
                            </view>
                        </view>
                        <!-- æ£€éªŒç»“果区域 -->
                        <view class="inspection-result">
                            <view class="result-item">
                                <view class="result-icon"
                                    :class="{
                                        'icon-pass': item.fcheckResu === '合格',
                                        'icon-fail': item.fcheckResu === '不合格'
                                    }">
                                    <text class="result-symbol">{{
                                        item.fcheckResu === '合格' ? '✓' : '✗'
                                    }}</text>
                                </view>
                                <view class="result-content">
                                    <text class="result-label">最终结果</text>
                                    <text class="result-value"
                                        :class="{
                                            'value-pass': item.fcheckResu === '合格',
                                            'value-fail': item.fcheckResu === '不合格'
                                        }">
                                        {{ item.fcheckResu }}
                                    </text>
                                </view>
                            </view>
                            <view class="inspector-info">
                                <text class="inspector-label">检验人</text>
                                <text class="inspector-name">{{ item.modify1By }}</text>
                            </view>
                        </view>
                        <!-- è¯¦ç»†ä¿¡æ¯ -->
                        <view class="card-details">
                            <view class="detail-grid">
                                <view class="detail-item">
                                    <text class="detail-label">检验时间</text>
                                    <text class="detail-value">{{ item.modify1Date }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">数量</text>
                                    <text class="detail-value">{{ item.planQty }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">创建人</text>
                                    <text class="detail-value">{{ item.createUser }}</text>
                                </view>
                                <view class="detail-item">
                                    <text class="detail-label">送检人</text>
                                    <text class="detail-value">{{ item.fcheckUser || '-' }}</text>
                                </view>
                            </view>
                        </view>
                        <!-- æ“ä½œæŒ‡ç¤ºå™¨ -->
                        <view class="action-indicator">
                            <uni-icons type="arrowright" size="16" color="#94a3b8"></uni-icons>
                        </view>
                    </view>
                </view>
            </view>
        </view>
        <!-- æµ®åŠ¨æ“ä½œæŒ‰é’® -->
        <view class="fab-container">
            <view class="fab-button" @tap="handleFabClick">
                <uni-icons type="plus" size="24" color="#ffffff"></uni-icons>
            </view>
        </view>
    </view>
</template>
<script>
    export default {
        components: {},
        data() {
            return {
                items: ['未检验', '已检验'],
                current: 0,
                data: [],
                pageIndex: 1,
                limit: 20,
                totalPage: 0,
                totalCount: 0,
                noData: false,
                isLoading: false,
                tipShow: false,
                searchOptions: ['物料编号', '规格', '物料名称', '供应商', '送检人', '检验单号'],
                selectedOption: 0,
                searchValue: '',
                copiedText: '',
                headerHeight: 0,
                showFilter: false, // æŽ§åˆ¶ç­›é€‰é¢æ¿æ˜¾ç¤º
            };
        },
        onLoad() {
            this.init();
        },
        onShow() {
            // é¡µé¢æ˜¾ç¤ºæ—¶åˆ·æ–°æ•°æ®
            this.pageIndex = 1;
            this.loadData();
        },
        methods: {
            onPickerChange(e) {
                this.selectedOption = e.detail.value;
            },
            getInputValue() {
                this.pageIndex = 1; // æœç´¢æ—¶é‡ç½®é¡µç 
                this.loadData(); // è°ƒç”¨ç»Ÿä¸€çš„æ•°æ®åŠ è½½æ–¹æ³•
            },
            loadData() {
                let result = "未完成";
                if (this.current === 1) {
                    result = "已完成";
                }
                if (this.isLoading) return;
                this.isLoading = true;
                let userName = this.$loginInfo.account;
                let url = "/MesOqcItemsDetect02/getPage"; // é»˜è®¤è°ƒç”¨getPage
                let requestData = {
                    pageIndex: this.pageIndex,
                    limit: this.limit,
                    createUser: userName,
                    result: result
                };
                console.log("请求参数:", requestData);
                console.log("当前选项卡:", this.current);
                console.log("result状态:", result);
                // åˆ¤æ–­æœç´¢æ¡†æ˜¯å¦æœ‰å€¼
                if (this.searchValue != null && this.searchValue.trim() !== '') {
                    // æ ¹æ®é€‰æ‹©çš„æœç´¢é€‰é¡¹è®¾ç½®æœç´¢æ¡ä»¶
                    switch (this.selectedOption) {
                        case 0:
                            requestData.ItemNo = this.searchValue;
                            break;
                        case 1:
                            requestData.SalesOrder = this.searchValue;
                            break;
                        case 2:
                            requestData.ItemName = this.searchValue;
                            break;
                        case 3:
                            requestData.SuppNameContains = this.searchValue;
                            break;
                        case 4:
                            requestData.SongJ = this.searchValue;
                            break;
                        case 5:
                            requestData.SongNo = this.searchValue;
                            break;
                    }
                }
                this.$post({
                    url: url,
                    data: requestData
                }).then(res => {
                    console.log("API返回完整数据:", JSON.stringify(res, null, 2));
                    console.log("res结构:", res);
                    console.log("res.data:", res.data);
                    console.log("res.totalCount:", res.totalCount);
                    // æ ¹æ®å“åº”格式.json,正确的数据结构是:
                    // res.data æ˜¯æ•°ç»„,res.totalCount æ˜¯æ€»æ•°
                    let dataList = null;
                    let totalCount = 0;
                    if (res.data && Array.isArray(res.data)) {
                        // æ­£ç¡®çš„æ•°æ®ç»“构:data是数组
                        dataList = res.data;
                        totalCount = res.totalCount || 0;
                    } else if (res.tbBillList) {
                        // å¤‡ç”¨ç»“构(兼容其他接口)
                        dataList = res.tbBillList;
                        totalCount = res.totalCount || 0;
                    } else {
                        console.error("无法解析的数据结构:", res);
                        console.error("期望的字段 data (数组) ä¸å­˜åœ¨");
                        this.$showMessage("数据格式错误,请联系技术支持");
                        this.isLoading = false;
                        return;
                    }
                    console.log("解析后的dataList:", dataList);
                    console.log("dataList长度:", dataList ? dataList.length : 0);
                    if (this.pageIndex === 1) {
                        this.data = dataList || [];
                    } else {
                        if (dataList && dataList.length > 0) {
                            this.data = [...this.data, ...dataList];
                        }
                    }
                    console.log("处理后的data:", this.data);
                    console.log("data长度:", this.data.length);
                    this.totalCount = totalCount;
                    this.totalPage = Math.ceil(this.totalCount / this.limit);
                    this.noData = this.pageIndex >= this.totalPage;
                    this.isLoading = false;
                }).catch((error) => {
                    console.error("API请求失败:", error);
                    this.isLoading = false;
                    this.searchValue = '';
                    this.$showMessage("请求失败,请重试");
                });
            },
            init() {
                this.loadData(); // ç»Ÿä¸€è°ƒç”¨loadData方法
            },
            handleFabClick() {
                uni.navigateTo({
                    url: 'ScanCode'
                });
            },
            onClickItem(index) {
                if (this.current !== index.currentIndex) {
                    this.current = index.currentIndex;
                    this.data = [];
                    this.pageIndex = 1;
                    this.loadData(); // é€‰é¡¹å¡åˆ‡æ¢æ—¶è°ƒç”¨loadData
                }
            },
            copyText(text) {
                uni.setClipboardData({
                    data: text,
                    success: () => {
                        this.copiedText = text;
                        this.tipShow = true;
                        setTimeout(() => {
                            this.tipShow = false;
                        }, 1000);
                    }
                });
            },
            // æ–°å¢žæ–¹æ³•
            navigateToDetail(item) {
                uni.navigateTo({
                    url: 'Add?id=' + item.id
                });
            },
            toggleFilter() {
                this.showFilter = !this.showFilter;
            },
            clearSearch() {
                this.searchValue = '';
                this.pageIndex = 1;
                this.loadData();
            }
        },
        onPullDownRefresh() {
            this.pageIndex = 1;
            this.loadData();
            this.tipShow = true;
            uni.stopPullDownRefresh();
            setTimeout(() => {
                this.tipShow = false;
            }, 1000);
        },
        onReachBottom() {
            if (this.noData || this.isLoading) return;
            this.pageIndex++;
            this.loadData(); // ä¸Šæ‹‰åŠ è½½æ—¶è°ƒç”¨loadData
        }
    };
</script>
<style>
/* é¡µé¢å®¹å™¨ */
.page-container {
  min-height: 100vh;
  background: #f5f5f5;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* æˆåŠŸæç¤ºæ¡† */
.success-toast {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%) translateY(-100px);
  background: #28a745;
  color: white;
  padding: 8px 16px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 1000;
  opacity: 0;
  transition: all 0.3s ease;
}
.success-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.toast-icon {
  font-size: 14px;
  font-weight: bold;
}
.toast-text {
  font-size: 14px;
}
/* å¤´éƒ¨åŒºåŸŸ */
.header-section {
  background: white;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* é¡µé¢æ ‡é¢˜ */
.page-header {
  margin-bottom: 16px;
}
.header-content {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.title-section {
  flex: 1;
}
.page-title {
  font-size: 20px;
  font-weight: 600;
  color: #333;
  margin-bottom: 2px;
  display: block;
}
.page-subtitle {
  font-size: 13px;
  color: #666;
  display: block;
}
.stats-badge {
  background: #007AFF;
  color: white;
  padding: 8px 12px;
  border-radius: 8px;
  text-align: center;
  min-width: 60px;
}
.stats-number {
  font-size: 16px;
  font-weight: 600;
  display: block;
  line-height: 1;
}
.stats-label {
  font-size: 11px;
  display: block;
  margin-top: 2px;
}
/* æœç´¢åŒºåŸŸ */
.search-section {
  margin-bottom: 16px;
}
.search-container {
  display: flex;
  gap: 8px;
  align-items: center;
}
.search-input-wrapper {
  flex: 1;
  position: relative;
  display: flex;
  align-items: center;
  background: #f8f8f8;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 0 12px;
}
.search-input-wrapper:focus-within {
  border-color: #007AFF;
  background: white;
}
.search-input {
  flex: 1;
  height: 40px;
  border: none;
  background: transparent;
  font-size: 14px;
  color: #333;
  margin-left: 8px;
  outline: none;
}
.search-input::placeholder {
  color: #999;
}
.clear-btn {
  padding: 4px;
  cursor: pointer;
}
.filter-btn {
  width: 40px;
  height: 40px;
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.filter-btn:active {
  background: #e0e0e0;
}
/* ç­›é€‰é¢æ¿ */
.filter-panel {
  margin-top: 8px;
  background: white;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 12px;
}
.filter-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  cursor: pointer;
}
.filter-label {
  font-size: 14px;
  color: #666;
}
.filter-value {
  font-size: 14px;
  color: #333;
  font-weight: 500;
}
/* é€‰é¡¹å¡ */
.tab-section {
  margin-bottom: 0;
}
.custom-tabs {
  display: flex;
  background: #f0f0f0;
  border-radius: 8px;
  padding: 2px;
}
.tab-item {
  flex: 1;
  position: relative;
  padding: 10px 16px;
  text-align: center;
  cursor: pointer;
  border-radius: 6px;
}
.tab-item.active {
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.tab-text {
  font-size: 14px;
  font-weight: 500;
  color: #666;
}
.tab-item.active .tab-text {
  color: #007AFF;
  font-weight: 600;
}
/* å†…容区域 */
.content-container {
  flex: 1;
  padding: 20px 16px;
}
/* åŠ è½½çŠ¶æ€ */
.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
}
.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #e2e8f0;
  border-top: 3px solid #4f46e5;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-text {
  font-size: 14px;
  color: #64748b;
}
/* ç©ºçŠ¶æ€ */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80px 20px;
  text-align: center;
}
.empty-icon {
  font-size: 64px;
  margin-bottom: 20px;
  opacity: 0.6;
}
.empty-title {
  font-size: 18px;
  font-weight: 600;
  color: #374151;
  margin-bottom: 8px;
  display: block;
}
.empty-desc {
  font-size: 14px;
  color: #9ca3af;
  display: block;
}
/* æ£€éªŒå•列表 */
.inspection-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.inspection-card {
  background: white;
  border-radius: 8px;
  padding: 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  border: 1px solid #e0e0e0;
  cursor: pointer;
  position: relative;
}
.inspection-card:active {
  background: #f8f8f8;
}
/* å¡ç‰‡å¤´éƒ¨ */
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 16px;
}
.header-left {
  flex: 1;
}
.inspection-number {
  margin-bottom: 8px;
}
.number-label {
  font-size: 12px;
  color: #64748b;
  display: block;
  margin-bottom: 4px;
}
.number-value {
  font-size: 16px;
  font-weight: 700;
  color: #1e293b;
  display: block;
}
.material-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.material-code {
  font-size: 13px;
  color: #4f46e5;
  font-weight: 600;
}
.material-name {
  font-size: 12px;
  color: #64748b;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.header-right {
  margin-left: 16px;
}
/* çŠ¶æ€å¾½ç«  */
.status-badge {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}
.status-badge.status-submitted {
  background: #e8f5e8;
  color: #2e7d32;
  border: 1px solid #c8e6c9;
}
.status-badge.status-pending {
  background: #fff3e0;
  color: #f57c00;
  border: 1px solid #ffcc02;
}
.status-badge.status-completed {
  background: #e8f5e8;
  color: #2e7d32;
  border: 1px solid #c8e6c9;
}
.status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.status-text {
  font-size: 12px;
  font-weight: 500;
}
/* æ£€éªŒç»“果区域 */
.inspection-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  padding: 8px;
  background: #f8f8f8;
  border-radius: 6px;
}
.result-item {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
}
.result-icon {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 12px;
}
.result-icon.icon-pass {
  background: #28a745;
  color: white;
}
.result-icon.icon-fail {
  background: #dc3545;
  color: white;
}
.result-icon.icon-pending {
  background: #ffc107;
  color: white;
}
.result-content {
  flex: 1;
}
.result-label {
  font-size: 12px;
  color: #666;
  display: block;
  margin-bottom: 2px;
}
.result-value {
  font-size: 14px;
  font-weight: 500;
  display: block;
}
.result-value.value-pass {
  color: #28a745;
}
.result-value.value-fail {
  color: #dc3545;
}
.result-value.value-pending {
  color: #ffc107;
}
.inspector-info {
  text-align: right;
}
.inspector-label {
  font-size: 12px;
  color: #666;
  display: block;
  margin-bottom: 2px;
}
.inspector-name {
  font-size: 13px;
  font-weight: 500;
  color: #333;
  display: block;
}
/* è¯¦ç»†ä¿¡æ¯ */
.card-details {
  margin-bottom: 8px;
}
.detail-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}
.detail-item {
  background: #f8f8f8;
  padding: 6px 8px;
  border-radius: 4px;
}
.detail-label {
  font-size: 11px;
  color: #666;
  display: block;
  margin-bottom: 2px;
}
.detail-value {
  font-size: 12px;
  font-weight: 500;
  color: #333;
  display: block;
}
/* æ“ä½œæŒ‡ç¤ºå™¨ */
.action-indicator {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  opacity: 0.3;
}
/* æµ®åŠ¨æ“ä½œæŒ‰é’® */
.fab-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 1000;
}
.fab-button {
  width: 50px;
  height: 50px;
  background: #007AFF;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  cursor: pointer;
}
.fab-button:active {
  transform: scale(0.95);
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .header-content {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .stats-badge {
    align-self: flex-end;
  }
  .search-container {
    flex-direction: column;
    gap: 8px;
  }
  .filter-btn {
    align-self: flex-end;
  }
  .detail-grid {
    grid-template-columns: 1fr;
  }
  .inspection-result {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .inspector-info {
    text-align: left;
    width: 100%;
  }
}
</style>
pages/QC/OQC/ScanCode.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,745 @@
<template>
    <view class="container">
        <!-- è¡¨å•区域 -->
        <view class="form-container card">
            <form :modelValue="formData">
                <view class="form-grid">
                    <view class="form-group col-2">
                        <label class="form-label">物料条码:</label>
                        <view class="input-with-scan">
                            <input class="form-input scan-input" type="text" v-model="formData.ItemBarcode"
                                @confirm="addItemBarCode"
                                placeholder="输入后离开或按回车" />
                            <view class="scan-button" @tap="startScan">
                                <uni-icons type="scan" size="24" color="#007bff"></uni-icons>
                                <text class="scan-text">扫码</text>
                            </view>
                        </view>
                    </view>
                    <view class="form-group col-2">
                        <label class="form-label">产品名称:</label>
                        <input class="form-input" disabled="true" type="text" v-model="formData.itemName" />
                    </view>
                    <view class="form-group col-2">
                        <label class="form-label">产品编码:</label>
                        <input class="form-input" disabled="true" type="text" v-model="formData.itemNo" />
                    </view>
                    <view class="form-group col-2">
                        <label class="form-label">订单编号:</label>
                        <input class="form-input" disabled="true" type="text" v-model="formData.taskNo" />
                    </view>
                    <view class="form-group col-2">
                        <label class="form-label">已扫数量:</label>
                        <input class="form-input" disabled="true" type="text" v-model="quantity" />
                    </view>
                </view>
            </form>
        </view>
        <!-- å¾…检条码表格区域 -->
        <view class="table-container card">
            <view class="table-header">
                <text class="section-title">待检条码列表</text>
                <view class="table-tip">
                    <text class="tip-text">👈 å·¦å³æ»‘动查看更多信息</text>
                </view>
            </view>
            <view class="table-wrapper">
                <scroll-view class="table-scroll" scroll-x="true" show-scrollbar="true">
                    <uni-table ref="table" border emptyText="暂无更多数据" class="custom-table">
                        <uni-tr class="table-header-row">
                            <uni-th align="center" class="th" width="80">序号</uni-th>
                            <uni-th align="center" class="th" width="160">物料条码</uni-th>
                            <uni-th align="center" class="th" width="140">订单编号</uni-th>
                            <uni-th align="center" class="th" width="140">产品编码</uni-th>
                            <uni-th align="center" class="th" width="160">产品名称</uni-th>
                            <uni-th align="center" class="th" width="100">条码数量</uni-th>
                            <uni-th align="center" class="th" width="80"> </uni-th>
                        </uni-tr>
                        <uni-tr v-for="(item, index) in tableData" :key="index" class="table-row">
                            <uni-td align="center" width="80">
                                <view class="description-text">{{ index + 1 }}</view>
                            </uni-td>
                            <uni-td align="center" width="160">
                                <view class="cell-content">{{ item.itemBarcode }}</view>
                            </uni-td>
                            <uni-td align="center" width="140">
                                <view class="cell-content">{{ item.taskNo || '-' }}</view>
                            </uni-td>
                            <uni-td align="center" width="140">
                                <view class="cell-content">{{ item.itemNo }}</view>
                            </uni-td>
                            <uni-td align="center" width="160">
                                <view class="cell-content" :title="item.itemName">{{ item.itemName }}</view>
                            </uni-td>
                            <uni-td align="center" width="100">
                                <view class="cell-content quantity">{{ item.quantity }}</view>
                            </uni-td>
                            <uni-td align="center" width="80">
                                <view class="cell-content"> </view>
                            </uni-td>
                        </uni-tr>
                    </uni-table>
                </scroll-view>
                <!-- å³å›ºå®šæ‚¬æµ®åˆ é™¤æŒ‰é’®åˆ— -->
                <view class="fixed-delete-column">
                    <view class="fixed-header">
                        <text class="fixed-header-text">操作</text>
                    </view>
                    <view class="fixed-content">
                        <view v-for="(item, index) in tableData" :key="index" class="fixed-delete-item"
                            :class="{ 'even': index % 2 === 1 }">
                            <view @click="deleteItem(index)" class="delete-icon">
                                <uni-icons type="trash" size="24"></uni-icons>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
        </view>
        <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
        <view class="action-buttons-container button-group">
            <view class="plus-button" :class="{ 'submitting': isSubmitting }" @tap="handleSubmit">
                <text>{{ isSubmitting ? '提交中...' : '生成OQC检验单' }}</text>
            </view>
        </view>
    </view>
</template>
<script>
    export default {
        data() {
            return {
                formData: {},
                tableData: [],
                quantity: 0,
                isSubmitting: false, // é˜²æ­¢é‡å¤æäº¤
                isProcessing: false, // é˜²æ­¢é‡å¤å¤„理条码
            }
        },
        methods: {
            // å¯åŠ¨æ‰«ç åŠŸèƒ½
            startScan() {
                // æ£€æŸ¥æ˜¯å¦æ­£åœ¨å¤„理条码,防止重复扫码
                if (this.isProcessing) {
                    this.$showMessage("正在处理条码,请稍候");
                    return;
                }
                uni.scanCode({
                    scanType: ['barCode', 'qrCode'], // æ”¯æŒæ¡ç å’ŒäºŒç»´ç 
                    success: (res) => {
                        console.log('扫码成功:', res);
                        // å°†æ‰«ç ç»“果设置到输入框
                        this.formData.ItemBarcode = res.result;
                        // è‡ªåŠ¨å¤„ç†æ‰«ç ç»“æžœ
                        this.addItemBarCode();
                    },
                    fail: (err) => {
                        console.log('扫码失败:', err);
                        if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
                            this.$showMessage("扫码失败,请重试");
                        }
                    }
                });
            },
            addItemBarCode() {
                // é˜²æ­¢é‡å¤å¤„理
                if (this.isProcessing) {
                    return;
                }
                // æ ¡éªŒç‰©æ–™æ¡ç æ˜¯å¦ä¸ºç©º
                if (!this.formData.ItemBarcode || this.formData.ItemBarcode.trim() === '') {
                    this.$showMessage("请输入物料条码");
                    return;
                }
                // æ£€æŸ¥ç‰©æ–™æ¡ç æ˜¯å¦å·²å­˜åœ¨äºŽ tableData ä¸­
                const isDuplicate = this.tableData.some(item => item.itemBarcode === this.formData.ItemBarcode);
                if (isDuplicate) {
                    this.$showMessage("该物料条码已存在,请检查!");
                    return;
                }
                // è®¾ç½®å¤„理状态
                this.isProcessing = true;
                // å¦‚果通过了上述校验,发送请求并更新数据
                this.$post({
                    url: "/MesOqcItemsDetect02/GetItemBarCode",
                    data: {
                        ItemCode: this.formData.ItemBarcode
                    }
                }).then(res => {
                    let fr = res.data.tbBillList;
                    // æ•°æ®å®Œæ•´æ€§æ ¡éªŒ
                    if (!fr) {
                        this.$showMessage("获取条码信息失败,请重试");
                        return;
                    }
                    if (!fr.itemId || !fr.itemName || !fr.itemNo) {
                        this.$showMessage("条码信息不完整,请检查条码");
                        return;
                    }
                    if (fr.quantity <= 0) {
                        this.$showMessage("条码数量为0,请确认");
                        return;
                    }
                    //需要验证
                    //扫的码的itemId必须是和tableData中的itemId相同  å¿…须的条件
                    //扫的码的TaskNo也必须是和tableData中的TaskNo相同
                    //TaskNo为空的只能和TaskNo为空的一起扫
                    // å¦‚æžœtableData中已有数据,需要验证itemId和TaskNo的一致性
                    if (this.tableData.length > 0) {
                        const firstItem = this.tableData[0];
                        // éªŒè¯itemId是否相同(必须条件)
                        if (fr.itemId !== firstItem.itemId) {
                            this.$showMessage("物料编码不一致,请扫描相同物料的条码");
                            return;
                        }
                        // éªŒè¯TaskNo是否相同
                        if (fr.taskNo !== firstItem.taskNo) {
                            this.$showMessage("订单编号不一致,请扫描相同订单的条码");
                            return;
                        }
                        // éªŒè¯ç©ºTaskNo的情况:TaskNo为空的只能和TaskNo为空的一起扫
                        if ((fr.taskNo === '' || fr.taskNo === null || fr.taskNo === undefined) &&
                            (firstItem.taskNo !== '' && firstItem.taskNo !== null && firstItem.taskNo !== undefined)) {
                            this.$showMessage("订单编号不匹配,空订单编号只能与空订单编号一起扫描");
                            return;
                        }
                        if ((fr.taskNo !== '' && fr.taskNo !== null && fr.taskNo !== undefined) &&
                            (firstItem.taskNo === '' || firstItem.taskNo === null || firstItem.taskNo === undefined)) {
                            this.$showMessage("订单编号不匹配,有订单编号的条码只能与有订单编号的条码一起扫描");
                            return;
                        }
                    }
                    this.formData = fr;
                    this.tableData.push(fr); // å°†æ–°æ•°æ®æ·»åŠ åˆ°è¡¨æ ¼
                    this.quantity = this.tableData.reduce(function(accumulator, current) {
                        return accumulator + current["quantity"];
                    }, 0);
                    // æ¸…空输入框
                    this.formData.ItemBarcode = '';
                }).catch(error => {
                    this.$showMessage("获取条码信息失败,请重试");
                }).finally(() => {
                    // é‡ç½®å¤„理状态
                    this.isProcessing = false;
                });
            },
            // åˆ é™¤æ“ä½œï¼šåˆ é™¤ tableData ä¸­æŒ‡å®šç´¢å¼•的数据
            deleteItem(index) {
                this.tableData.splice(index, 1); // åˆ é™¤è¯¥è¡Œæ•°æ®
                this.quantity = this.tableData.reduce(function(accumulator, current) {
                    return accumulator + current["quantity"];
                }, 0); // æ›´æ–°æ¡ç æ•°é‡
                if (this.tableData.length <= 0) {
                    this.formData = {};
                }
            },
            handleSubmit() {
                console.log("handleSubmit方法被调用,isSubmitting:", this.isSubmitting);
                console.log("tableData长度:", this.tableData.length);
                console.log("quantity:", this.quantity);
                this.submit();
            },
            submit() {
                console.log("submit方法被调用");
                // é˜²æ­¢é‡å¤æäº¤
                if (this.isSubmitting) {
                    this.$showMessage("正在提交中,请勿重复操作");
                    return;
                }
                // æ ¡éªŒç”¨æˆ·ç™»å½•状态
                if (!this.$loginInfo.account) {
                    this.$showMessage("用户未登录,请重新登录");
                    return;
                }
                // æ ¡éªŒæ¡ç æ•°é‡æ˜¯å¦æœ‰æ•ˆ
                if (this.quantity <= 0) {
                    this.$showMessage("条码数量必须大于0");
                    return;
                }
                // æ ¡éªŒæ˜¯å¦æœ‰æ•°æ®
                if (this.tableData.length === 0) {
                    this.$showMessage("请扫描条码");
                    return;
                }
                let userName = this.$loginInfo.account;
                this.isSubmitting = true; // è®¾ç½®æäº¤çŠ¶æ€
                // å‘送请求
                this.$post({
                    url: "/MesOqcItemsDetect02/ItemBarCodeSubmit",
                    data: {
                        itemBarCodeData: this.tableData,
                        CreateUser: userName
                    }
                }).then(response => {
                    if(response.status == 0){
                        // è¯·æ±‚成功后显示选择框
                        uni.showModal({
                            title: "操作成功",
                            content: "已经成功生成检验单",
                            showCancel: true,
                            cancelText: "继续扫码",
                            confirmText: "跳转到检验单",
                            success: (res) => {
                                if (res.confirm) {
                                    // ç”¨æˆ·ç‚¹å‡»äº†"跳转到检验单"
                                    uni.navigateTo({
                                        url: '/pages/QC/OQC/Add?id=' + response.data
                                    });
                                } else if (res.cancel) {
                                    // ç”¨æˆ·ç‚¹å‡»äº†"继续扫码"
                                    this.clearData();
                                }
                            }
                        });
                    }else{
                        this.$showMessage(response.message);
                    }
                }).catch(error => {
                    // è¯·æ±‚失败时的处理
                    this.$showMessage("请求失败,请稍后重试");
                }).finally(() => {
                    // æ— è®ºæˆåŠŸè¿˜æ˜¯å¤±è´¥éƒ½é‡ç½®æäº¤çŠ¶æ€
                    this.isSubmitting = false;
                });
            },
            // æ¸…空表格和表单数据
            clearData() {
                this.tableData = [];
                this.formData = {};
                this.quantity = 0;
                this.isSubmitting = false;
                this.isProcessing = false;
            }
        }
    }
</script>
<style>
    .container {
      padding: 24rpx;
      background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
      min-height: 100vh;
      display: flex;
      flex-direction: column;
    }
    /* å¡ç‰‡é€šç”¨æ ·å¼ */
    .card {
      background: #ffffff;
      border-radius: 12rpx;
      box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
      margin-bottom: 24rpx;
      padding: 32rpx;
      border: 1rpx solid #e9ecef;
    }
    /* è¡¨å•区域 */
    .form-container {
      .form-grid {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 24rpx 16rpx;
      }
      .form-group {
        display: flex;
        flex-direction: column;
        gap: 16rpx;
        .form-label {
          font-size: 28rpx;
          color: #495057;
          font-weight: 600;
          padding-left: 4rpx;
          position: relative;
          &::after {
            content: "";
            position: absolute;
            left: 0;
            bottom: -4rpx;
            width: 24rpx;
            height: 3rpx;
            background: #007bff;
            border-radius: 2rpx;
          }
        }
        .form-input {
          height: 92rpx;
          padding: 0 24rpx;
          border: 2rpx solid #dee2e6;
          border-radius: 8rpx;
          font-size: 28rpx;
          color: #212529;
          background: #ffffff;
          transition: all 0.3s ease;
          &[disabled] {
            background: #f8f9fa;
            color: #6c757d;
            border-color: #e9ecef;
          }
          &:focus {
            border-color: #007bff;
            box-shadow: 0 0 0 4rpx rgba(0, 123, 255, 0.1);
            outline: none;
          }
          &::placeholder {
            color: #adb5bd;
          }
        }
      }
    }
    /* è¡¨æ ¼ä¼˜åŒ– */
    .table-container {
      .table-header {
        margin-bottom: 24rpx;
        padding-bottom: 16rpx;
        border-bottom: 2rpx solid #e9ecef;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .section-title {
          font-size: 32rpx;
          color: #212529;
          font-weight: 700;
          position: relative;
          padding-left: 24rpx;
          &::before {
            content: "";
            position: absolute;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
            width: 4rpx;
            height: 28rpx;
            background: linear-gradient(135deg, #007bff, #0056b3);
            border-radius: 2rpx;
          }
        }
        .table-tip {
          .tip-text {
            font-size: 22rpx;
            color: #6c757d;
            background: #f8f9fa;
            padding: 8rpx 16rpx;
            border-radius: 16rpx;
            border: 1rpx solid #e9ecef;
          }
        }
      }
      .table-wrapper {
        position: relative;
        border-radius: 8rpx;
        overflow: hidden;
        border: 1rpx solid #dee2e6;
      }
      .table-scroll {
        width: calc(100% - 80rpx); /* å‡åŽ»å›ºå®šåˆ—å®½åº¦ */
        white-space: nowrap;
      }
      .custom-table {
        border: none;
        min-width: 780rpx;
        .table-header-row {
          background: linear-gradient(135deg, #f8f9fa, #e9ecef);
          .th {
            font-size: 26rpx;
            color: #495057;
            font-weight: 600;
            padding: 20rpx 12rpx;
            background: transparent !important;
            border-right: 1rpx solid #dee2e6;
            white-space: nowrap;
            min-width: 80rpx;
            &:last-child {
              border-right: none;
            }
          }
        }
        .table-row {
          transition: background-color 0.2s ease;
          &:nth-child(even) {
            background: #f8f9fa;
          }
          &:hover {
            background: #e3f2fd;
          }
          .uni-td {
            padding: 16rpx 12rpx;
            border-color: #dee2e6 !important;
            border-right: 1rpx solid #dee2e6;
            white-space: nowrap;
            min-width: 80rpx;
            &:last-child {
              border-right: none;
            }
            .cell-content {
              font-size: 24rpx;
              color: #495057;
              line-height: 1.4;
              max-width: 100%;
              overflow: hidden;
              text-overflow: ellipsis;
              &.quantity {
                font-weight: 600;
                color: #007bff;
                background: linear-gradient(135deg, #e3f2fd, #bbdefb);
                padding: 8rpx 16rpx;
                border-radius: 12rpx;
                display: inline-block;
                min-width: 60rpx;
                text-align: center;
              }
            }
            .description-text {
              font-size: 24rpx;
              color: #6c757d;
              font-weight: 500;
            }
          }
        }
      }
      /* å³å›ºå®šæ‚¬æµ®åˆ é™¤åˆ— */
      .fixed-delete-column {
        position: absolute;
        top: 0;
        right: 0;
        width: 80rpx;
        background: #ffffff;
        border-left: 1rpx solid #dee2e6;
        box-shadow: -4rpx 0 8rpx rgba(0, 0, 0, 0.05);
        z-index: 10;
        .fixed-header {
          height: 60rpx;
          background: linear-gradient(135deg, #f8f9fa, #e9ecef);
          display: flex;
          align-items: center;
          justify-content: center;
          border-bottom: 1rpx solid #dee2e6;
          .fixed-header-text {
            font-size: 26rpx;
            color: #495057;
            font-weight: 600;
          }
        }
        .fixed-content {
          .fixed-delete-item {
            height: 48rpx;
            display: flex;
            align-items: center;
            justify-content: center;
            border-bottom: 1rpx solid #dee2e6;
            transition: background-color 0.2s ease;
            &.even {
              background: #f8f9fa;
            }
            &:hover {
              background: #e3f2fd;
            }
            &:last-child {
              border-bottom: none;
            }
          }
        }
      }
    }
    /* åˆ é™¤å›¾æ ‡ä¼˜åŒ– */
    .delete-icon {
      cursor: pointer;
      color: #dc3545;
      transition: all 0.3s ease;
      padding: 12rpx;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 48rpx;
      min-height: 48rpx;
      &:hover {
        color: #c82333;
        background: rgba(220, 53, 69, 0.1);
        transform: scale(1.1);
      }
      &:active {
        transform: scale(0.95);
        background: rgba(220, 53, 69, 0.2);
      }
    }
    /* æŒ‰é’®ä¼˜åŒ– */
    .action-buttons-container {
      display: flex;
      justify-content: center;
      margin-top: 48rpx;
      padding: 0 24rpx;
      .plus-button {
        width: 100%;
        max-width: 600rpx;
        height: 96rpx;
        background: linear-gradient(135deg, #007bff, #0056b3);
        border-radius: 48rpx;
        display: flex;
        justify-content: center;
        align-items: center;
        color: #ffffff;
        font-size: 32rpx;
        font-weight: 600;
        box-shadow: 0 8rpx 24rpx rgba(0, 123, 255, 0.3);
        transition: all 0.3s ease;
        border: none;
        position: relative;
        overflow: hidden;
        &::before {
          content: "";
          position: absolute;
          top: 0;
          left: -100%;
          width: 100%;
          height: 100%;
          background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
          transition: left 0.5s ease;
        }
        &:active {
          transform: scale(0.98);
          box-shadow: 0 4rpx 12rpx rgba(0, 123, 255, 0.4);
          &::before {
            left: 100%;
          }
        }
        &.submitting {
          background: linear-gradient(135deg, #6c757d, #5a6268);
          cursor: not-allowed;
          &::before {
            display: none;
          }
        }
      }
    }
    /* æ•°é‡æ˜¾ç¤ºä¼˜åŒ– */
    .form-group {
      &:nth-child(5) .form-input {
        font-weight: 600;
        color: #007bff;
        background: linear-gradient(135deg, #e3f2fd, #bbdefb);
        border: 2rpx solid #007bff;
        text-align: center;
      }
    }
    /* æ‰«ç è¾“入框样式 */
    .input-with-scan {
      display: flex;
      align-items: center;
      gap: 16rpx;
      .scan-input {
        flex: 1;
      }
      .scan-button {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 16rpx 20rpx;
        background: linear-gradient(135deg, #e3f2fd, #bbdefb);
        border: 2rpx solid #007bff;
        border-radius: 8rpx;
        min-width: 120rpx;
        height: 92rpx;
        cursor: pointer;
        transition: all 0.3s ease;
        &:hover {
          background: linear-gradient(135deg, #bbdefb, #90caf9);
          transform: translateY(-2rpx);
          box-shadow: 0 4rpx 12rpx rgba(0, 123, 255, 0.2);
        }
        &:active {
          transform: translateY(0);
          box-shadow: 0 2rpx 6rpx rgba(0, 123, 255, 0.3);
        }
        .scan-text {
          font-size: 22rpx;
          color: #007bff;
          font-weight: 600;
          margin-top: 4rpx;
        }
      }
    }
</style>
pages/QC/OQC/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,828 @@
<template>
    <view class="page-container">
        <!-- æ£€éªŒé¡¹ç›®è¡¨å•卡片 -->
        <view class="form-card">
            <view class="form-title">
                <view class="title-icon">📋</view>
                <span>检验项目详情</span>
            </view>
            <view class="form-container">
                <!-- åŸºæœ¬ä¿¡æ¯æ¨¡å— -->
                <view class="form-section">
                    <view class="section-title">基本信息</view>
                    <view class="form-grid">
                        <view class="form-group">
                            <label class="form-label">项目名称:</label>
                            <input class="form-input" disabled="true" type="text" v-model="formData.fcheckItem" />
                        </view>
                        <view class="form-group">
                            <label class="form-label">规格要求:</label>
                            <input class="form-input" disabled="true" type="text" v-model="formData.fspecRequ" />
                        </view>
                        <view class="form-group">
                            <label class="form-label">检验方法:</label>
                            <input class="form-input" disabled="true" type="text" v-model="formData.inspectionMethod" />
                        </view>
                        <view class="form-group">
                            <label class="form-label">检验工具:</label>
                            <input class="form-input" disabled="true" type="text" v-model="formData.fcheckTool" />
                        </view>
                    </view>
                </view>
                <!-- ä¸‰ä¸ªæ¨¡å—并列容器 -->
                <view class="three-modules-container">
                    <!-- æ£€éªŒå‚数模块 -->
                    <view class="module-item">
                        <view class="module-header">
                            <text class="module-title">检验参数</text>
                        </view>
                        <view class="module-content">
                            <view class="form-grid">
                                <view class="form-group">
                                    <label class="form-label">检验数:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.checkQyt" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">检验标准编码:</label>
                                    <input class="form-input" disabled="true" type="text"
                                        v-model="formData.sampleSizeNo" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">检验水平:</label>
                                    <input class="form-input" disabled="true" type="text"
                                        v-model="formData.fcheckLevel" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">接收水平:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.facLevel" />
                                </view>
                            </view>
                        </view>
                    </view>
                    <!-- æ•°å€¼æ ‡å‡†æ¨¡å— -->
                    <view class="module-item">
                        <view class="module-header">
                            <text class="module-title">数值标准</text>
                        </view>
                        <view class="module-content">
                            <view class="form-grid">
                                <view class="form-group">
                                    <label class="form-label">下限:</label>
                                    <input class="form-input" disabled="true" type="text"
                                        v-model="formData.fdownAllow" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">标准值:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.fstand" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">上限:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.fupAllow" />
                                </view>
                            </view>
                        </view>
                    </view>
                    <!-- åˆ¤å®šæ ‡å‡†æ¨¡å— -->
                    <view class="module-item">
                        <view class="module-header">
                            <text class="module-title">判定标准</text>
                        </view>
                        <view class="module-content">
                            <view class="form-grid">
                                <view class="form-group">
                                    <label class="form-label">AC数:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.facQty" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">RE数:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.freQty" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">不合格数:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.fngQty" />
                                </view>
                                <view class="form-group">
                                    <label class="form-label">预览结果:</label>
                                    <input class="form-input" disabled="true" type="text" v-model="formData.fcheckResu" />
                                </view>
                            </view>
                        </view>
                    </view>
                </view>
                <!-- å¤‡æ³¨ä¿¡æ¯æ¨¡å— -->
                <!-- <view class="form-section">
                    <view class="section-title">备注信息</view>
                    <view class="form-grid">
                        <view class="form-group">
                            <label class="form-label">不合格描述:</label>
                            <input class="form-input" disabled="true" type="text" v-model="formData.remarks" />
                        </view>
                    </view>
                </view> -->
                <!-- æ£€æµ‹ç»“果区域 -->
                <view class="form-section">
                    <view class="section-title">检测结果</view>
                    <view class="form-grid">
                        <view class="form-group">
                            <label class="form-label">检测结果:</label>
                            <input class="form-input" type="number" v-model="fcheckResuK" />
                        </view>
                        <!-- æç¤ºè¯ä½œä¸ºæ£€æµ‹ç»“果的提示 -->
                        <view class="form-group tip-group">
                            <view class="tip-box">
                                <view class="tip-icon">⚠️</view>
                                <view class="tip-text">没有最大值和最小值时填写<span class="highlight">0(未通过检验)</span>或<span
                                        class="highlight">1(通过检验)</span></view>
                            </view>
                        </view>
                    </view>
                </view>
                <button :class="['action-btn', 'btn-primary', { 'btn-loading': isLoading }]"
                    v-if="tableData.length < formData.checkQyt" @click="submit" :disabled="isLoading">
                    {{ isLoading ? '保存中...' : '保存' }}
                </button>
            </view>
        </view>
        <!-- æ£€éªŒç»“果表格卡片 -->
        <view class="table-card">
            <view class="table-title">
                <view class="title-icon">📊</view>
                <span>检验结果列表</span>
            </view>
            <view class="list-container">
                <uni-table ref="table" border emptyText="暂无更多数据">
                    <uni-tr>
                        <uni-th width="80" align="center" class="th">编号</uni-th>
                        <uni-th width="120" align="center" class="th">判定标识</uni-th>
                        <uni-th width="100" align="center" class="th">检验结果</uni-th>
                        <uni-th width="120" align="center" class="th">操作</uni-th>
                    </uni-tr>
                    <uni-tr v-for="(item, index) in tableData" :key="index" class="table-row"
                        :class="{ 'hover-effect': isHoveringRow === index }" @mouseenter="isHoveringRow = index"
                        @mouseleave="isHoveringRow = -1">
                        <uni-td align="center">
                            {{ index + 1 }}
                        </uni-td>
                        <uni-td align="center">
                            <input class="form-input" disabled="true" type="text" v-model="item.fstand" />
                        </uni-td>
                        <uni-td align="center">
                            <span class="result-badge"
                                :class="{ 'pass': item.fcheckResu === 'OK', 'fail': item.fcheckResu === 'NG' }">
                                {{ item.fcheckResu || '未检验' }}
                            </span>
                        </uni-td>
                        <uni-td align="center">
                            <view class="action-group">
                                <button :class="['action-btn', 'btn-sm', 'btn-warn', { 'btn-disabled': isLoading }]"
                                    v-if="isNumber" @click="toDetail(item)" :disabled="isLoading">
                                    {{ isLoading ? '处理中...' : '修改' }}
                                </button>
                                <button :class="['action-btn', 'btn-sm', 'btn-warn', { 'btn-disabled': isLoading }]"
                                    v-if="!isNumber" @click="numberEdit(item)" :disabled="isLoading">
                                    {{ isLoading ? '处理中...' : editResult(item.fcheckResu) }}
                                </button>
                            </view>
                        </uni-td>
                    </uni-tr>
                </uni-table>
            </view>
        </view>
        <!-- æ“ä½œæŒ‰é’® -->
        <view class="action-buttons">
            <view class="button-group">
                <button :class="['action-btn', 'btn-warn', { 'btn-disabled': isLoading }]" @click="saveRemarks"
                    :disabled="isLoading">
                    {{ isLoading ? '处理中...' : '添加不合格描述' }}
                </button>
            </view>
        </view>
        <!-- ä¿®æ”¹æ£€éªŒç»“果弹出层 -->
        <view v-if="showPopup" class="overlay active">
            <view class="popup" :class="{ 'popup-scale': isPopupAnimated }" @animationend="isPopupAnimated = false">
                <view class="popup-header">
                    <h3 class="popup-title">修改检验结果</h3>
                    <view class="close-btn" @click="showPopup = !showPopup">×</view>
                </view>
                <form :modelValue="editData">
                    <view class="form-group">
                        <label class="form-label">检验结果:</label>
                        <input class="form-input" type="text" v-model="editData.fcheckResu" />
                    </view>
                    <view class="button-group">
                        <button :class="['action-btn', 'btn-warn', { 'btn-loading': isEditLoading }]" @click="eidt"
                            :disabled="isEditLoading">
                            {{ isEditLoading ? '修改中...' : '修改' }}
                        </button>
                        <button @click="showPopup = !showPopup">
                            å–消
                        </button>
                    </view>
                </form>
            </view>
        </view>
        <!-- ä¿®æ”¹ä¸åˆæ ¼æè¿°å¼¹å‡ºå±‚ -->
        <view v-if="remarksPopup" class="overlay active">
            <view class="popup" :class="{ 'popup-scale': isPopupAnimated }" @animationend="isPopupAnimated = false">
                <view class="popup-header">
                    <h3 class="popup-title">修改不合格描述</h3>
                    <view class="close-btn" @click="remarksPopup = !remarksPopup">×</view>
                </view>
                <form>
                    <view class="form-group">
                        <label class="form-label">不合格描述:</label>
                        <input class="form-input" type="text" v-model="remarks" />
                    </view>
                    <view class="button-group">
                        <button :class="['action-btn', 'btn-warn', { 'btn-loading': isRemarksLoading }]"
                            @click="editRemarks" :disabled="isRemarksLoading">
                            {{ isRemarksLoading ? '保存中...' : '修改' }}
                        </button>
                        <button @click="remarksPopup = !remarksPopup">
                            å–消
                        </button>
                    </view>
                </form>
            </view>
        </view>
    </view>
</template>
<script>
    export default {
        data() {
            return {
                formData: {},
                releaseNo: "",
                isNumber: false,
                checkItem: "",
                id: 0,
                gid: 0,
                billNo: "",
                showPopup: false,
                editData: {},
                tableData: [],
                remarks: "",
                remarksPopup: false,
                fcheckResuK: "",
                isLoading: false,
                isEditLoading: false,
                isRemarksLoading: false,
                isHoveringRow: -1,
                isPopupAnimated: false
            };
        },
        methods: {
            editResult(fcheckResu) {
                if (fcheckResu == "OK") {
                    return "改为不合格";
                } else {
                    return "改为合格";
                }
            },
            submit() {
                this.isLoading = true;
                let count = this.formData.checkQyt;
                let fstand = "√";
                if (Number(this.formData.fupAllow) && Number(this.formData.fdownAllow)) {
                    if (!this.fcheckResuK) {
                        this.$showMessage("请输入检验值");
                        this.isLoading = false;
                        return;
                    }
                    if (
                        Number(this.fcheckResuK) >= Number(this.formData.fdownAllow) &&
                        Number(this.fcheckResuK) <= Number(this.formData.fupAllow)
                    ) {
                        fstand = "√";
                    } else {
                        fstand = "×";
                    }
                    count = 1;
                } else {
                    if (!this.fcheckResuK) {
                        this.formData.fcheckResu = 1;
                    }
                    if (this.fcheckResuK == 0 || this.fcheckResuK == 1) {
                        this.formData.isPass = this.fcheckResuK;
                    } else {
                        this.$showMessage("无标准值时,检验结果只能为0或1!");
                        this.isLoading = false;
                        return;
                    }
                    count = count - this.tableData.length;
                }
                this.formData.updater = this.$loginInfo.account;
                this.$post({
                    url: "/LLJ/SetQSItemDetail",
                    data: {
                        mainId: this.formData.id,
                        releaseNo: this.formData.releaseNo,
                        fstand: fstand,
                        fcheckResu: this.fcheckResuK,
                        LastupdateBy: this.$loginInfo.account,
                        count: count
                    }
                }).then((res) => {
                    this.formData.fcheckResu = null;
                    this.$showMessage("保存成功");
                    this.refreshResult();
                    this.isLoading = false;
                }).catch(() => {
                    this.$showMessage("保存失败,请重试");
                    this.isLoading = false;
                });
            },
            refreshResult() {
                this.isLoading = true;
                this.$post({
                    url: "/MesOqcItemsDetect02/getXjDetail02ById",
                    data: {
                        id: this.id
                    }
                }).then((res) => {
                    this.formData = res.data.tbBillList.itemXj01;
                    this.tableData = res.data.tbBillList.itemXj02s;
                    if (this.formData.fupAllow && this.formData.fdownAllow && this.formData.fstand) {
                        this.isNumber = true;
                    } else {
                        this.isNumber = false;
                    }
                    this.isLoading = false;
                }).catch(() => {
                    this.$showMessage("获取数据失败");
                    this.isLoading = false;
                });
            },
            toDetail(item) {
                this.showPopup = true;
                this.editData = {
                    ...item
                };
            },
            eidt() {
                this.isEditLoading = true;
                if (!this.editData.fcheckResu) {
                    this.$showMessage("请输入检验结果");
                    this.isEditLoading = false;
                    return;
                }
                if (this.formData.fcheckResu == this.editData.fcheckResu) {
                    this.$showMessage("修改成功");
                    this.showPopup = false;
                    this.isEditLoading = false;
                    return;
                }
                let fstand = "√";
                if (this.formData.fupAllow && this.formData.fdownAllow) {
                    if (!this.editData.fcheckResu) {
                        this.$showMessage("请输入检验值");
                        this.isEditLoading = false;
                        return;
                    }
                    if (
                        Number(this.editData.fcheckResu) >= Number(this.formData.fdownAllow) &&
                        Number(this.editData.fcheckResu) <= Number(this.formData.fupAllow)
                    ) {
                        this.editData.isPass = 1;
                        fstand = "√";
                    } else {
                        this.editData.isPass = 0;
                        fstand = "×";
                    }
                } else {
                    if (!this.editData.fcheckResu) {
                        this.editData.fcheckResu = 1;
                    }
                    if (this.editData.fcheckResu == 0 || this.editData.fcheckResu == 1) {
                        if (this.editData.fcheckResu == 0) {
                            fstand = "×";
                        }
                    } else {
                        this.$showMessage("无标准值时,检验结果只能为0或1!");
                        this.isEditLoading = false;
                        return;
                    }
                }
                this.editData.updater = this.$loginInfo.account;
                this.$post({
                    url: "/MesOqcItemsDetect02/UpdateQSItemDetail",
                    data: {
                        id: this.editData.id,
                        mainId: this.formData.id,
                        releaseNo: this.formData.releaseNo,
                        fstand: fstand,
                        fcheckResu: this.editData.fcheckResu,
                        lastupdateBy: this.$loginInfo.account
                    }
                }).then((res) => {
                    this.showPopup = false;
                    this.$showMessage("修改成功");
                    this.refreshResult();
                    this.isEditLoading = false;
                }).catch(() => {
                    this.$showMessage("修改失败,请重试");
                    this.isEditLoading = false;
                });
            },
            numberEdit(item) {
                this.isLoading = true;
                let fstand = "√";
                let fcheckResu = "OK";
                if (item.fcheckResu == "OK") {
                    fstand = "×";
                    fcheckResu = "NG";
                }
                this.$post({
                    url: "/MesOqcItemsDetect02/UpdateQSItemDetail",
                    data: {
                        id: item.id,
                        mainId: this.formData.id,
                        releaseNo: this.formData.releaseNo,
                        fstand: fstand,
                        fcheckResu: fcheckResu,
                        lastupdateBy: this.$loginInfo.account
                    }
                }).then((res) => {
                    this.$showMessage("修改成功");
                    this.refreshResult();
                    this.isLoading = false;
                }).catch(() => {
                    this.$showMessage("修改失败,请重试");
                    this.isLoading = false;
                });
            },
            saveRemarks() {
                this.remarksPopup = true;
                this.remarks = this.formData.remarks || "";
            },
            editRemarks() {
                this.isRemarksLoading = true;
                if (this.remarks) {
                    this.$post({
                        url: "/MesOqcItemsDetect02/saveRemarksPid",
                        data: {
                            pid: this.formData.id,
                            remarks: this.remarks
                        }
                    }).then((res) => {
                        if (res.data.tbBillList > 0) {
                            this.formData.remarks = this.remarks;
                            this.remarksPopup = false;
                            this.$showMessage("保存成功");
                        } else {
                            this.$showMessage("保存失败");
                        }
                        this.isRemarksLoading = false;
                    }).catch(() => {
                        this.$showMessage("保存失败,请重试");
                        this.isRemarksLoading = false;
                    });
                } else {
                    this.$showMessage("请输入不合格描述");
                    this.isRemarksLoading = false;
                }
            }
        },
        onLoad(options) {
            let params = options;
            this.id = params["mainId"];
            this.releaseNo = params["releaseNo"];
            this.refreshResult();
        }
    };
</script>
<style>
    /* é¡µé¢å®¹å™¨ */
    .page-container {
        padding: 20px;
        background-color: #f5f5f5;
        min-height: 100vh;
    }
    /* è¡¨å•卡片 */
    .form-card {
        background-color: #fff;
        border-radius: 12px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    /* è¡¨å•标题 */
    .form-title {
        display: flex;
        align-items: center;
        font-size: 18px;
        color: #333;
        margin-bottom: 15px;
    }
    .title-icon {
        font-size: 22px;
        margin-right: 10px;
    }
    /* è¡¨å•容器 */
    .form-container {
        padding-top: 10px;
    }
    /* è¡¨å•模块标题 */
    .form-section {
        margin-bottom: 20px;
    }
    .section-title {
        font-size: 16px;
        font-weight: 600;
        color: #333;
        margin-bottom: 10px;
    }
    /* è¡¨æ ¼å¸ƒå±€ */
    .form-grid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 15px;
    }
    /* è¡¨å•项 */
    .form-group {
        display: flex;
        align-items: center;
    }
    .form-label {
        width: 130px;
        color: #333;
        font-size: 14px;
        font-weight: 500;
    }
    .form-input {
        flex: 1;
        height: 40px;
        padding: 0 10px;
        border: 1px solid #e0e0e0;
        border-radius: 8px;
        background-color: #f8f8f8;
        color: #666;
        font-size: 14px;
    }
    .form-input:disabled {
        background-color: #e9e9e9;
    }
    .form-input:focus {
        border-color: #007AFF;
        outline: none;
    }
    /* æ£€éªŒç»“果区域 */
    .tip-group {
        margin-top: 15px;
    }
    .tip-box {
        display: flex;
        align-items: center;
        background-color: #fff5d1;
        padding: 10px;
        border-radius: 8px;
        border: 1px solid #f0e0a7;
    }
    .tip-icon {
        font-size: 20px;
        color: #f39c12;
        margin-right: 10px;
    }
    .tip-text {
        font-size: 14px;
        color: #333;
    }
    .highlight {
        color: #007AFF;
        font-weight: 600;
    }
    /* æŒ‰é’®æ ·å¼ */
    .action-btn {
        width: 100%;
        padding: 12px;
        border-radius: 8px;
        border: none;
        color: #fff;
        font-size: 16px;
        font-weight: 500;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        transition: background-color 0.3s ease;
    }
    .btn-primary {
        background-color: #007AFF;
    }
    .btn-primary:hover {
        background-color: #0056CC;
    }
    .btn-warn {
        background-color: #f1b344;
    }
    .btn-warn:hover {
        background-color: #e6a135;
    }
    .btn-disabled {
        background-color: #c0c0c0;
        cursor: not-allowed;
    }
    /* å¼¹å‡ºå±‚样式 */
    .overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
        opacity: 0;
        transition: opacity 0.3s ease;
    }
    .overlay.active {
        opacity: 1;
    }
    .popup {
        background-color: #fff;
        border-radius: 12px;
        width: 90%;
        max-width: 500px;
        padding: 20px;
        transform: scale(0.8);
        transition: transform 0.3s ease;
    }
    .popup-scale {
        transform: scale(1);
    }
    .popup-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
    }
    .popup-title {
        font-size: 16px;
        font-weight: 600;
        color: #333;
    }
    .close-btn {
        font-size: 20px;
        color: #333;
        cursor: pointer;
    }
    /* æ“ä½œæŒ‰é’®ç»„ */
    .button-group {
        display: flex;
        justify-content: space-between;
        margin-top: 20px;
    }
    .button-group button {
        width: 48%;
        padding: 10px;
        border-radius: 8px;
        border: none;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
    }
    /* æ£€éªŒç»“果表格卡片 */
    .table-card {
        background-color: #fff;
        border-radius: 12px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    /* è¡¨æ ¼æ ‡é¢˜ */
    .table-title {
        display: flex;
        align-items: center;
        font-size: 16px;
        color: #333;
        font-weight: 600;
        margin-bottom: 15px;
    }
    .title-icon {
        font-size: 22px;
        margin-right: 10px;
    }
    /* è¡¨æ ¼æ ·å¼ */
    .uni-table {
        width: 100%;
        border-collapse: collapse;
    }
    .uni-th,
    .uni-td {
        padding: 12px;
        text-align: center;
    }
    .uni-th {
        background-color: #f5f5f5;
        font-size: 14px;
        color: #666;
        font-weight: 500;
    }
    .table-row {
        background-color: #fff;
        border-bottom: 1px solid #e0e0e0;
    }
    .table-row:hover {
        background-color: #f9f9f9;
    }
    .result-badge {
        padding: 5px 12px;
        font-size: 12px;
        border-radius: 10px;
        color: #fff;
        display: inline-block;
    }
    .pass {
        background-color: #28a745;
    }
    .fail {
        background-color: #dc3545;
    }
    .pending {
        background-color: #f1b344;
    }
    .action-group {
        display: flex;
        gap: 10px;
    }
    .action-btn.btn-sm {
        width: auto;
        padding: 6px 12px;
        font-size: 12px;
    }
    /* æç¤ºä¿¡æ¯ */
    .hover-effect:hover {
        background-color: #f9f9f9;
    }
    .btn-sm {
        font-size: 14px;
    }
    .btn-loading {
        background-color: #c0c0c0;
        cursor: not-allowed;
    }
</style>
store/index.js
@@ -2,31 +2,31 @@
import Vuex from 'vuex'
//引入vuex插件 è¿›è¡ŒçŠ¶æ€ç®¡ç†
Vue.use(Vuex)
Vue.use(Vuex)
const store = new Vuex.Store({
    state: {
    state: {
        id: 'id',
        serverInfo:{//服务信息
            networkFlag:'内网',
            serverURLInt:'http://192.168.11.251:10054',//服务器体检 10.0.1.104:10054
            serverURL:'http://localhost:10054',//本地调试地址
            serverAPI:'http://localhost:5184/api',//当前正在使用本地
           // serverAPI:'http://192.168.1.92:10054/api',//当前正在使用的服务器
        serverInfo: { //服务信息
            networkFlag: '内网',
            serverURLInt: 'http://192.168.11.251:10054', //服务器体检 10.0.1.104:10054
            serverURL: 'http://localhost:10054', //本地调试地址
            serverAPI:'http://localhost:10054/api',//当前正在使用本地
            //serverAPI: 'http://192.168.1.92:10054/api', //当前正在使用的服务器
        }
    },
    mutations: {
        test(state,id){
        test(state, id) {
            state.id = id;
        }
    },
    getters:{
        currentColor(state){
            return state.colorList[state.colorIndex]
        }
    },
    getters: {
        currentColor(state) {
            return state.colorList[state.colorIndex]
        }
    },
    actions: {
        // lazy loading openid
    }
})
export default store
export default store