|
|
@@ -0,0 +1,1113 @@
|
|
|
+<!-- 采购流程专用表单组件 -->
|
|
|
+<template>
|
|
|
+ <view class="purchase-form-component">
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <uni-card>
|
|
|
+ <uni-section titleFontSize="1.3rem" title="基本信息" type="line"></uni-section>
|
|
|
+ <uni-forms ref="baseFormRef" :modelValue="baseForm" :rules="baseFormRules" label-position="left" :label-width="100" :border="true">
|
|
|
+ <uni-forms-item name="contractPurchaseNumber" label="采购单号">
|
|
|
+ <uni-easyinput v-model="baseForm.contractPurchaseNumber" disabled placeholder="自动生成"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="contractPurchaseName" label="采购单名称" required>
|
|
|
+ <uni-easyinput v-model="baseForm.contractPurchaseName"
|
|
|
+ :disabled="!isInitiateOrFieldEditable('contract_purchase_name')"
|
|
|
+ :placeholder="'请输入采购单名称'"
|
|
|
+ :clearable="false"></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="applyDate" label="申请日期">
|
|
|
+ <uni-easyinput v-model="baseForm.applyDate" type="date" disabled />
|
|
|
+ </uni-forms-item>
|
|
|
+ <uni-forms-item name="department" label="申请部门">
|
|
|
+ <uni-easyinput v-model="baseForm.department" disabled></uni-easyinput>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 部门意见 -->
|
|
|
+ <uni-forms-item label="部门意见" name="departmentalOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(departmentalOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="departmentalOpinionElem && departmentalOpinionElem.defaultValue == ''">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('departmental_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('departmental_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="departmentalOpinionElem && departmentalOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('departmental_opinion')"
|
|
|
+ :src="config.baseUrlPre + departmentalOpinionElem.sealImgPath"
|
|
|
+ :alt="departmentalOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="departmentalOpinionElem && typeof departmentalOpinionElem.sealImgPath === 'string' && departmentalOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + departmentalOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 分管副总意见 -->
|
|
|
+ <uni-forms-item label="分管副总" name="dgmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(deputyGeneralManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="deputyGeneralManagerOpinionElem && deputyGeneralManagerOpinionElem.defaultValue == ''">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('deputy_general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('deputy_general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="deputyGeneralManagerOpinionElem && deputyGeneralManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('deputy_general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + deputyGeneralManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="deputyGeneralManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="deputyGeneralManagerOpinionElem && typeof deputyGeneralManagerOpinionElem.sealImgPath === 'string' && deputyGeneralManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + deputyGeneralManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 审核副总意见 -->
|
|
|
+ <uni-forms-item label="分管副总" name="auditDgmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(auditDeputyGeneralManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="auditDeputyGeneralManagerOpinionElem && auditDeputyGeneralManagerOpinionElem.defaultValue == ''">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('audit_deputy_general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('audit_deputy_general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="auditDeputyGeneralManagerOpinionElem && auditDeputyGeneralManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('audit_deputy_general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + auditDeputyGeneralManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="auditDeputyGeneralManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="auditDeputyGeneralManagerOpinionElem && typeof auditDeputyGeneralManagerOpinionElem.sealImgPath === 'string' && auditDeputyGeneralManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + auditDeputyGeneralManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+
|
|
|
+ <!-- 总经理意见 -->
|
|
|
+ <uni-forms-item label="总经理" name="gmOpinion">
|
|
|
+ <view class="element_value_container">
|
|
|
+ <view v-if="isApprovalFieldEditable(generalManagerOpinionElem)" class="element_value">
|
|
|
+ <!-- 审批签字板 -->
|
|
|
+ <view v-if="generalManagerOpinionElem && generalManagerOpinionElem.defaultValue == ''">
|
|
|
+ <uni-row>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button type="primary" @click="handleApprovalSignature('general_manager_opinion')">手动签名</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="24">
|
|
|
+ <button style="margin-top: 5px;" type="primary" @click="handleAutoSeal('general_manager_opinion')">一键签名</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="generalManagerOpinionElem && generalManagerOpinionElem.defaultValue" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix" @click="handleApprovalSignature('general_manager_opinion')"
|
|
|
+ :src="config.baseUrlPre + generalManagerOpinionElem.sealImgPath"
|
|
|
+ :alt="generalManagerOpinionElem.elementName + '签名'" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="generalManagerOpinionElem && typeof generalManagerOpinionElem.sealImgPath === 'string' && generalManagerOpinionElem.sealImgPath.startsWith('/shares')" class="signature_img">
|
|
|
+ <img style="width: 100%;" mode="widthFix"
|
|
|
+ :src="config.baseUrlPre + generalManagerOpinionElem.sealImgPath" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-forms-item>
|
|
|
+ </uni-forms>
|
|
|
+ </uni-card>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 物料明细 -->
|
|
|
+ <uni-card>
|
|
|
+ <uni-section titleFontSize="1.3rem" title="物料明细" type="line"></uni-section>
|
|
|
+ <!-- 添加物料按钮:发起环节或字段可编辑时显示 -->
|
|
|
+ <view v-if="isSeModel" class="material-actions">
|
|
|
+ <button type="primary" size="mini" @click="openMaterialSelector" plain>添加物料</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 物料列表 - 卡片式展示 -->
|
|
|
+ <view v-if="materialList.length > 0" class="material-list">
|
|
|
+ <view v-for="(item, index) in materialList" :key="'material-' + item.materialCode + '-' + index" class="material-card">
|
|
|
+ <view class="material-header" @click="toggleExpand(index)">
|
|
|
+ <view class="material-main-info">
|
|
|
+ <text class="material-name">{{ item.materialName }}</text>
|
|
|
+ <text class="material-code">{{ item.materialCode }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="material-expand">
|
|
|
+ <uni-icons :type="item.expanded ? 'up' : 'down'" size="16" color="#999"></uni-icons>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 展开的详细信息 -->
|
|
|
+ <view v-show="item.expanded" class="material-detail">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">规格型号:</text>
|
|
|
+ <text class="detail-value">{{ item.materialModel }}</text>
|
|
|
+ <text class="detail-label" style="margin-left: 15px;">单位:</text>
|
|
|
+ <text class="detail-value">{{ item.measureName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row delete-row">
|
|
|
+ <text class="detail-label">数量:</text>
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="item.qty"
|
|
|
+ type="digit"
|
|
|
+ v-if="isSeModel"
|
|
|
+ placeholder="请输入数量"
|
|
|
+ style="width: 100px; display: inline-block; margin-right: 15px;"
|
|
|
+ />
|
|
|
+ <text v-if="!isSeModel" class="detail-value">{{ item.qty }}</text>
|
|
|
+ <button v-if="isSeModel" type="warn" size="mini" @click="removeMaterial(index)">删除</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="materialList.length === 0" class="empty-materials">
|
|
|
+ <text>暂无物料</text>
|
|
|
+ </view>
|
|
|
+ </uni-card>
|
|
|
+
|
|
|
+ <!-- 选择器弹出层 -->
|
|
|
+ <uni-popup ref="selectorPopup" type="center">
|
|
|
+ <view class="selector-popup">
|
|
|
+ <view class="popup-header">
|
|
|
+ <text class="popup-title">选择物料</text>
|
|
|
+ <uni-icons type="closeempty" size="20" @click="closePopup"></uni-icons>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <view class="search-bar">
|
|
|
+ <uni-easyinput
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="输入物料名称搜索"
|
|
|
+ clearable
|
|
|
+ @confirm="handleSearch"
|
|
|
+ />
|
|
|
+ <button type="primary" size="mini" @click="handleSearch">搜索</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view
|
|
|
+ scroll-y
|
|
|
+ class="popup-content"
|
|
|
+ refresher-enabled
|
|
|
+ :refresher-triggered="isRefreshing"
|
|
|
+ @refresh="onRefresh"
|
|
|
+ @scrolltolower="loadMore"
|
|
|
+ >
|
|
|
+ <view v-for="(item, index) in selectorList" :key="index"
|
|
|
+ class="selector-item"
|
|
|
+ @click="selectItem(item)">
|
|
|
+ <view class="selector-item-content">
|
|
|
+ <view class="item-info">
|
|
|
+ <text class="item-name">{{ item.materialName || item.itemName }}</text>
|
|
|
+ <text class="item-code">{{ item.materialCode || item.itemCode }}</text>
|
|
|
+ <text class="item-spec">{{ item.materialModel || item.specification || '' }}</text>
|
|
|
+ <text class="item-unit">{{ item.measureName || item.unit || '' }}</text>
|
|
|
+ </view>
|
|
|
+ <text v-if="isSelected(item)" class="selected-tag">已选择</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view v-if="isLoading && selectorList.length > 0" class="loading-text">
|
|
|
+ <uni-load-more status="loading" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 没有更多数据 -->
|
|
|
+ <view v-else-if="!hasMore && selectorList.length > 0" class="no-more-text">
|
|
|
+ <text>没有更多了</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 空数据提示 -->
|
|
|
+ <view v-if="selectorList.length === 0 && !isLoading" class="empty-data">
|
|
|
+ <text>{{ searchKeyword ? '暂无相关数据' : '暂无数据' }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+
|
|
|
+ <!-- 签名板弹出层 -->
|
|
|
+ <uni-popup ref="signaturePopup" @maskClick="closeSignature">
|
|
|
+ <view class="signature_container" :class="{ 'signature_container_landscape': isLandscape }">
|
|
|
+ <view class="signature_content">
|
|
|
+ <l-signature ref="signatureRef" v-if="signaturePopupShow" :landscape="isLandscape" :penSize="8"
|
|
|
+ :minLineWidth="4" :maxLineWidth="12" :openSmooth="true" :preferToDataURL="true"
|
|
|
+ backgroundColor="#ffffff" penColor="black"></l-signature>
|
|
|
+ </view>
|
|
|
+ <view class="signature_button_container">
|
|
|
+ <uni-row :gutter="10">
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="warn" @click="onclickSignatureButton('undo')">撤销</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="warn" @click="onclickSignatureButton('clear')">清空</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button type="primary" @click="onclickSignatureButton('save')">保存</button>
|
|
|
+ </uni-col>
|
|
|
+ <uni-col :span="6">
|
|
|
+ <button @click="onclickSignatureButton('landscape')">全屏</button>
|
|
|
+ </uni-col>
|
|
|
+ </uni-row>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch, computed, nextTick } from 'vue'
|
|
|
+import { getMaterialList } from '@/api/purchase.js'
|
|
|
+import { useUserStore } from '@/store/user.js'
|
|
|
+import $modal from '@/plugins/modal.js'
|
|
|
+import config from '@/config.js'
|
|
|
+import { uploadSignatureBoardImg, getSeal } from '@/api/process.js'
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ formData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ },
|
|
|
+ formElements: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ repeatingForm: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({ elementItem: [], elements: [] })
|
|
|
+ },
|
|
|
+ // 是否为发起环节(seModel == '1')
|
|
|
+ isInitiate: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // 当前环节可编辑的字段列表
|
|
|
+ editableFields: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ // 当前环节的审批意见配置
|
|
|
+ currentTacheOpinion: {
|
|
|
+ type: Object,
|
|
|
+ default: null
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const emits = defineEmits(['update', 'signature-change'])
|
|
|
+
|
|
|
+// 表单数据
|
|
|
+const baseForm = ref<any>({
|
|
|
+ contractPurchaseNumber: '',
|
|
|
+ contractPurchaseName: '',
|
|
|
+ applyDate: '',
|
|
|
+ department: ''
|
|
|
+})
|
|
|
+
|
|
|
+const baseFormRules = ref({
|
|
|
+ contractPurchaseName: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ errorMessage: '请输入采购单名称'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+const materialList = ref<any[]>([])
|
|
|
+const selectorList = ref<any[]>([])
|
|
|
+const searchKeyword = ref('')
|
|
|
+const isLoading = ref(false)
|
|
|
+const hasMore = ref(true)
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(20)
|
|
|
+const isRefreshing = ref(false)
|
|
|
+const selectorPopup = ref(null)
|
|
|
+const signaturePopup = ref(null)
|
|
|
+const signatureRef = ref(null)
|
|
|
+const signatureImg = ref('')
|
|
|
+const currentApprovalText = ref('')
|
|
|
+const approvals = ref<any[]>([]) // 历史审批意见列表
|
|
|
+const signaturePopupShow = ref(false)
|
|
|
+const isLandscape = ref(false)
|
|
|
+
|
|
|
+// 获取指定的审批意见元素
|
|
|
+const getApprovalElement = (fieldName: string) => {
|
|
|
+ if (!props.formElements || !Array.isArray(props.formElements)) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ // 从 formElements 中找到对应的字段
|
|
|
+ const elem = props.formElements.find(e => e.tableField === fieldName || e.elementName.includes(fieldName.replace(/_/g, '')))
|
|
|
+ return elem || null
|
|
|
+}
|
|
|
+
|
|
|
+// 获取部门意见元素(计算属性,避免重复调用)
|
|
|
+const departmentalOpinionElem = computed(() => getApprovalElement('departmental_opinion'))
|
|
|
+// 获取副总经理意见元素
|
|
|
+const deputyGeneralManagerOpinionElem = computed(() => getApprovalElement('deputy_general_manager_opinion'))
|
|
|
+// 获取审计副总经理意见元素
|
|
|
+const auditDeputyGeneralManagerOpinionElem = computed(() => getApprovalElement('audit_deputy_general_manager_opinion'))
|
|
|
+// 获取总经理意见元素
|
|
|
+const generalManagerOpinionElem = computed(() => getApprovalElement('general_manager_opinion'))
|
|
|
+
|
|
|
+// 是否发起环节
|
|
|
+const isSeModel = computed(() => props.isInitiate)
|
|
|
+
|
|
|
+// 计算各部门意见是否可编辑
|
|
|
+const isApprovalFieldEditable = (elem: any) => {
|
|
|
+ if (!elem) return false
|
|
|
+ return props.editableFields.includes(elem.tableField)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理审批意见签名
|
|
|
+let lastFormInsId = ''
|
|
|
+
|
|
|
+// 计算字段是否可编辑 (只需要该字段在 table_fields 中即可)
|
|
|
+const getFieldEditable = (fieldName: string) => {
|
|
|
+ // 如果没有配置 editableFields,则都不可编辑
|
|
|
+ if (!props.editableFields || props.editableFields.length === 0) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查该字段是否在可编辑字段列表中 (table_fields 包含的字段才可以编辑)
|
|
|
+ return props.editableFields.includes(fieldName)
|
|
|
+}
|
|
|
+
|
|
|
+// 计算是否为发起环节或字段可编辑 (满足任一条件即可)
|
|
|
+const isInitiateOrFieldEditable = (fieldName: string) => {
|
|
|
+ return props.isInitiate || getFieldEditable(fieldName)
|
|
|
+}
|
|
|
+
|
|
|
+// 监听表单数据变化
|
|
|
+watch(() => props.formData, (newVal) => {
|
|
|
+ if (!newVal || Object.keys(newVal).length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 使用 formInsId 判断是否是新的数据(formInsId 肯定存在)
|
|
|
+ const currentFormInsId = newVal.lFormInsId || newVal.universalid || ''
|
|
|
+
|
|
|
+ // 只有当 formInsId 变化时,才认为是新的数据需要加载
|
|
|
+ if (currentFormInsId && currentFormInsId !== lastFormInsId) {
|
|
|
+ lastFormInsId = currentFormInsId
|
|
|
+
|
|
|
+ // 填充基本信息
|
|
|
+ baseForm.value = {
|
|
|
+ contractPurchaseNumber: newVal.contractPurchaseNumber || '',
|
|
|
+ contractPurchaseName: newVal.contractPurchaseName || '',
|
|
|
+ applyDate: newVal.applyDate || '',
|
|
|
+ department: newVal.department || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从 details 或 detailList 中获取物料列表 (后端返回的是 details)
|
|
|
+ const rawList = newVal.details || newVal.detailList || []
|
|
|
+
|
|
|
+ // 加载物料数据 (即使为空也加载,因为可能是新增场景)
|
|
|
+ if (Array.isArray(rawList)) {
|
|
|
+ materialList.value = rawList.map((item: any) => ({
|
|
|
+ materialCode: item.materialCode || item.itemCode,
|
|
|
+ materialName: item.materialName || item.itemName,
|
|
|
+ materialModel: item.materialModel || item.specification,
|
|
|
+ measureName: item.measureName || item.unit,
|
|
|
+ itemTypeName: item.itemTypeName || '',
|
|
|
+ qty: item.qty || 0,
|
|
|
+ expanded: true // 默认展开
|
|
|
+ }))
|
|
|
+ } else {
|
|
|
+ materialList.value = []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理 formElements,提取可编辑字段配置
|
|
|
+ if (props.formElements && props.formElements.length > 0) {
|
|
|
+ console.log('采购表单组件:处理 formElements 配置', props.formElements)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}, { immediate: true, deep: true })
|
|
|
+
|
|
|
+// 切换展开/收起状态
|
|
|
+function toggleExpand(index: number) {
|
|
|
+ if (materialList.value[index]) {
|
|
|
+ materialList.value[index].expanded = !materialList.value[index].expanded
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开物料选择器
|
|
|
+async function openMaterialSelector() {
|
|
|
+ currentPage.value = 1
|
|
|
+ hasMore.value = true
|
|
|
+ selectorList.value = []
|
|
|
+ searchKeyword.value = ''
|
|
|
+ await loadMaterialList(1)
|
|
|
+ selectorPopup.value.open()
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭弹出层
|
|
|
+function closePopup() {
|
|
|
+ selectorPopup.value.close()
|
|
|
+}
|
|
|
+
|
|
|
+// 加载物料列表
|
|
|
+function loadMaterialList(page: number = 1) {
|
|
|
+ isLoading.value = true
|
|
|
+ getMaterialList(userStore.user.useId, page, pageSize.value, searchKeyword.value)
|
|
|
+ .then(({ returnParams }) => {
|
|
|
+ const list = returnParams.list || []
|
|
|
+ const total = returnParams.total || 0
|
|
|
+
|
|
|
+ if (page === 1) {
|
|
|
+ selectorList.value = list
|
|
|
+ } else {
|
|
|
+ selectorList.value = [...selectorList.value, ...list]
|
|
|
+ }
|
|
|
+ hasMore.value = selectorList.value.length < total
|
|
|
+ currentPage.value = page
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ console.error('加载物料失败:', err)
|
|
|
+ $modal.msgError('加载物料失败')
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ isLoading.value = false
|
|
|
+ isRefreshing.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索
|
|
|
+function handleSearch() {
|
|
|
+ loadMaterialList(1)
|
|
|
+}
|
|
|
+
|
|
|
+// 刷新
|
|
|
+function onRefresh() {
|
|
|
+ isRefreshing.value = true
|
|
|
+ loadMaterialList(1)
|
|
|
+}
|
|
|
+
|
|
|
+// 加载更多
|
|
|
+function loadMore() {
|
|
|
+ if (!isLoading.value && hasMore.value) {
|
|
|
+ loadMaterialList(currentPage.value + 1)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 选择物料
|
|
|
+async function selectItem(item: any) {
|
|
|
+ // 检查是否已选择
|
|
|
+ const exists = materialList.value.find(m =>
|
|
|
+ m.materialCode === item.materialCode ||
|
|
|
+ m.materialCode === item.itemCode
|
|
|
+ )
|
|
|
+ if (!exists) {
|
|
|
+ // 使用完整的字段映射(参考 start.vue)
|
|
|
+ const newItem = {
|
|
|
+ materialCode: item.materialCode || item.itemCode,
|
|
|
+ materialName: item.materialName || item.itemName,
|
|
|
+ materialModel: item.materialModel || item.specification,
|
|
|
+ measureName: item.measureName || item.unit,
|
|
|
+ itemTypeName: item.itemTypeName || '',
|
|
|
+ qty: 0,
|
|
|
+ expanded: true
|
|
|
+ }
|
|
|
+ materialList.value = [...materialList.value, newItem]
|
|
|
+ await nextTick()
|
|
|
+ emitUpdate()
|
|
|
+ }
|
|
|
+ closePopup()
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否已选择
|
|
|
+function isSelected(item: any) {
|
|
|
+ return materialList.value.some(m => m.materialCode === item.materialCode)
|
|
|
+}
|
|
|
+
|
|
|
+// 删除物料
|
|
|
+async function removeMaterial(index: number) {
|
|
|
+ uni.showModal({
|
|
|
+ title: '',
|
|
|
+ content: '确认删除该物料?',
|
|
|
+ success: async (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ // 使用新数组替换旧数组,确保触发响应式更新
|
|
|
+ materialList.value = materialList.value.filter((_, i) => i !== index)
|
|
|
+ await nextTick()
|
|
|
+ emitUpdate()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 发送更新事件
|
|
|
+function emitUpdate() {
|
|
|
+ emits('update', {
|
|
|
+ ...baseForm.value,
|
|
|
+ detailList: materialList.value
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 处理审批意见签名
|
|
|
+const currentApprovalFieldName = ref('')
|
|
|
+// 存储每个签名字段的印章信息
|
|
|
+const approvalSealInfo = ref<Record<string, any>>({})
|
|
|
+
|
|
|
+function handleApprovalSignature(fieldName: string) {
|
|
|
+ currentApprovalFieldName.value = fieldName
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ nextTick(() => {
|
|
|
+ signaturePopupShow.value = true
|
|
|
+ })
|
|
|
+ signaturePopup.value.open()
|
|
|
+}
|
|
|
+
|
|
|
+// 处理一键签名
|
|
|
+function handleAutoSeal(fieldName: string) {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ getSeal(userStore.user.useId).then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ elem.defaultValue = returnParams.sealFileId.universalid
|
|
|
+ elem.sealImgPath = returnParams.sealFileId.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // 一键签名时,sealFileId.universalid 就是印章模板 ID
|
|
|
+ approvalSealInfo.value[fieldName] = {
|
|
|
+ sealInsId: returnParams.sealFileId.universalid, // 签名实例 ID(这里与印章模板 ID 相同)
|
|
|
+ sealFileId: returnParams.sealFileId.universalid, // 印章模板 ID(用于 imgval)
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ $modal.msgError('获取签名失败:' + err)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化签字板
|
|
|
+function initSignature() {
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ setTimeout(() => {
|
|
|
+ signaturePopupShow.value = true
|
|
|
+ }, 100)
|
|
|
+}
|
|
|
+
|
|
|
+// 点击签字板按钮
|
|
|
+function onclickSignatureButton(event: string) {
|
|
|
+ switch (event) {
|
|
|
+ case 'undo':
|
|
|
+ signatureRef.value.undo()
|
|
|
+ break
|
|
|
+ case 'clear':
|
|
|
+ signatureRef.value.clear()
|
|
|
+ break
|
|
|
+ case 'save':
|
|
|
+ signatureRef.value.canvasToTempFilePath({
|
|
|
+ success: (res: any) => {
|
|
|
+ if (res.isEmpty) {
|
|
|
+ $modal.msgError('签名不能为空!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 判断上传文件是否是 base64
|
|
|
+ if (res.tempFilePath.substring(0, 'data:image/png;base64,'.length) == 'data:image/png;base64,') {
|
|
|
+ const _fileData = res.tempFilePath
|
|
|
+ uploadSignatureBoardImg(userStore.user.useId, _fileData, getApprovalElement(currentApprovalFieldName.value).tableField)
|
|
|
+ .then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(currentApprovalFieldName.value)
|
|
|
+ elem.defaultValue = returnParams.sealInsID
|
|
|
+ elem.sealImgPath = returnParams.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // sealInsID 是手写签名的实例 ID
|
|
|
+ approvalSealInfo.value[currentApprovalFieldName.value] = {
|
|
|
+ sealInsId: returnParams.sealInsID, // 签名实例 ID
|
|
|
+ sealFileId: returnParams.sealInsID, // 手写签名图片 ID
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ currentApprovalFieldName.value = ''
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 转 base64
|
|
|
+ uni.getFileSystemManager().readFile({
|
|
|
+ filePath: res.tempFilePath,
|
|
|
+ encoding: 'base64',
|
|
|
+ success: (fileData: any) => {
|
|
|
+ const _fileData = 'data:image/png;base64,' + fileData.data
|
|
|
+ uploadSignatureBoardImg(userStore.user.useId, _fileData, getApprovalElement(currentApprovalFieldName.value).tableField)
|
|
|
+ .then(({ returnParams }) => {
|
|
|
+ const elem = getApprovalElement(currentApprovalFieldName.value)
|
|
|
+ elem.defaultValue = returnParams.sealInsID
|
|
|
+ elem.sealImgPath = returnParams.path
|
|
|
+ // 保存印章信息(用于后端处理)
|
|
|
+ // sealInsID 是手写签名的实例 ID
|
|
|
+ approvalSealInfo.value[currentApprovalFieldName.value] = {
|
|
|
+ sealInsId: returnParams.sealInsID, // 签名实例 ID
|
|
|
+ sealFileId: returnParams.sealInsID, // 手写签名图片 ID
|
|
|
+ left: 0, // 默认左边距为 0
|
|
|
+ top: 0 // 默认上边距为 0
|
|
|
+ }
|
|
|
+ currentApprovalFieldName.value = ''
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ break
|
|
|
+ case 'landscape':
|
|
|
+ isLandscape.value = !isLandscape.value
|
|
|
+ initSignature()
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 清空签名
|
|
|
+function clearSignature() {
|
|
|
+ onclickSignatureButton('clear')
|
|
|
+}
|
|
|
+
|
|
|
+// 保存签名
|
|
|
+function saveSignature() {
|
|
|
+ onclickSignatureButton('save')
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭签名
|
|
|
+function closeSignature() {
|
|
|
+ signaturePopupShow.value = false
|
|
|
+ signaturePopup.value.close()
|
|
|
+}
|
|
|
+
|
|
|
+// 暴露验证方法给父组件
|
|
|
+defineExpose({
|
|
|
+ validate: async () => {
|
|
|
+ // 采购表单验证
|
|
|
+ if (!baseForm.value.contractPurchaseName) {
|
|
|
+ return Promise.reject(new Error('请输入采购单名称'))
|
|
|
+ }
|
|
|
+ if (!materialList.value || materialList.value.length === 0) {
|
|
|
+ return Promise.reject(new Error('请至少添加一个物料'))
|
|
|
+ }
|
|
|
+ // 验证审批意见字段(如果在 table_fields 中)
|
|
|
+ const approvalFields = [
|
|
|
+ { fieldName: 'departmental_opinion', msg: '部门意见' },
|
|
|
+ { fieldName: 'deputy_general_manager_opinion', msg: '副总经理意见' },
|
|
|
+ { fieldName: 'audit_deputy_general_manager_opinion', msg: '审核副总意见' },
|
|
|
+ { fieldName: 'general_manager_opinion', msg: '总经理意见' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ for (const field of approvalFields) {
|
|
|
+ const elem = getApprovalElement(field.fieldName)
|
|
|
+ if (elem) {
|
|
|
+ // 检查该字段是否在当前环节的可编辑字段列表中
|
|
|
+ // editableFields 包含了当前环节可编辑的所有字段(即 table_fields)
|
|
|
+ if (props.editableFields && props.editableFields.includes(field.fieldName)) {
|
|
|
+ console.log('【验证】检查字段:', field.fieldName, ', defaultValue:', elem.defaultValue)
|
|
|
+ if (!elem.defaultValue || elem.defaultValue === '') {
|
|
|
+ return Promise.reject(new Error(field.msg + '不能为空!'))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return Promise.resolve()
|
|
|
+ },
|
|
|
+ // 获取表单数据(返回 formElements 格式)
|
|
|
+ getFormElements: () => {
|
|
|
+ const formElements: any[] = []
|
|
|
+
|
|
|
+ // 添加基本信息字段
|
|
|
+ Object.keys(baseForm.value).forEach(key => {
|
|
|
+ const value = baseForm.value[key]
|
|
|
+ if (value !== undefined && value !== null) {
|
|
|
+ formElements.push({
|
|
|
+ name: key,
|
|
|
+ value: String(value),
|
|
|
+ type: '0' // 普通文本类型
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加审批意见字段
|
|
|
+ const approvalFields = ['departmental_opinion', 'deputy_general_manager_opinion', 'audit_deputy_general_manager_opinion', 'general_manager_opinion']
|
|
|
+ approvalFields.forEach(fieldName => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName,
|
|
|
+ value: elem.defaultValue || '',
|
|
|
+ type: elem.type || '0'
|
|
|
+ })
|
|
|
+ // 添加签名图片的 imgval 值(用于后端处理)
|
|
|
+ // 格式:sealFileId_left_top(必须使用印章模板 ID,而不是签名实例 ID)
|
|
|
+ if (approvalSealInfo.value[fieldName]) {
|
|
|
+ const sealInfo = approvalSealInfo.value[fieldName]
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName + '_imgval',
|
|
|
+ value: `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`,
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ } else if (elem.sealImgPath) {
|
|
|
+ // 兼容旧数据(如果没有印章信息,仅传递图片路径)
|
|
|
+ formElements.push({
|
|
|
+ name: fieldName + '_imgval',
|
|
|
+ value: elem.sealImgPath,
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 添加物料明细(JSON 字符串格式)
|
|
|
+ if (materialList.value && materialList.value.length > 0) {
|
|
|
+ formElements.push({
|
|
|
+ name: 'detailList',
|
|
|
+ value: JSON.stringify(materialList.value),
|
|
|
+ type: '0'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return formElements
|
|
|
+ },
|
|
|
+ // 获取表单数据(兼容旧版本,返回键值对格式)
|
|
|
+ getFormData: () => {
|
|
|
+ const formData: any = {
|
|
|
+ ...baseForm.value,
|
|
|
+ detailList: materialList.value || []
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加审批意见字段数据
|
|
|
+ const approvalFields = ['departmental_opinion', 'deputy_general_manager_opinion', 'audit_deputy_general_manager_opinion', 'general_manager_opinion']
|
|
|
+ approvalFields.forEach(fieldName => {
|
|
|
+ const elem = getApprovalElement(fieldName)
|
|
|
+ if (elem) {
|
|
|
+ formData[fieldName] = elem.defaultValue || ''
|
|
|
+ // 添加签名图片的 imgval 值(用于后端处理)
|
|
|
+ // 格式:sealFileId_left_top(必须使用印章模板 ID,而不是签名实例 ID)
|
|
|
+ if (approvalSealInfo.value[fieldName]) {
|
|
|
+ const sealInfo = approvalSealInfo.value[fieldName]
|
|
|
+ formData[fieldName + '_imgval'] = `${sealInfo.sealFileId}_${sealInfo.left}_${sealInfo.top}`
|
|
|
+ } else if (elem && elem.sealImgPath) {
|
|
|
+ // 兼容旧数据(如果没有印章信息,仅传递图片路径)
|
|
|
+ formData[fieldName + '_imgval'] = elem.sealImgPath
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return formData
|
|
|
+ }
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+/*.purchase-form-component {*/
|
|
|
+ // 基本信息中的禁用字段样式优化
|
|
|
+ ::v-deep .uni-forms {
|
|
|
+ .uni-forms-item__content {
|
|
|
+ .uni-easyinput__content-input {
|
|
|
+ font-size: calc(14px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .uni-date {
|
|
|
+ .uni-icons {
|
|
|
+ font-size: calc(22px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .uni-date__x-input {
|
|
|
+ height: auto;
|
|
|
+ font-size: calc(14px + 1.2*(1rem - 16px)) !important;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 审批意见字段样式(与 index.vue 保持一致)
|
|
|
+ .element_value_container {
|
|
|
+ .signature_img {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 签名弹窗样式(与 index.vue 保持一致)
|
|
|
+ .signature_container {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ height: 40vh;
|
|
|
+ width: 90vw;
|
|
|
+
|
|
|
+ .signature_content {
|
|
|
+ height: 80%;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature_button_container {
|
|
|
+ height: 20%;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ button {
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature_container_landscape {
|
|
|
+ height: 100vh;
|
|
|
+ width: 100vw;
|
|
|
+
|
|
|
+ .signature_content {
|
|
|
+ height: 85%;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .signature_button_container {
|
|
|
+ margin-top: 10%;
|
|
|
+ height: 15%;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ button {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 物料列表 - 卡片式展示
|
|
|
+ .material-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-card {
|
|
|
+ border: 1px solid #e5e5e5;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #fff;
|
|
|
+ margin-bottom: 8px;
|
|
|
+
|
|
|
+ .material-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .material-main-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ .material-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-code {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-expand {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .material-detail {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-top: 1px solid #e5e5e5;
|
|
|
+
|
|
|
+ .detail-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .detail-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-value {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .delete-row {
|
|
|
+ margin-top: 10px;
|
|
|
+ text-align: center;
|
|
|
+ padding-top: 8px;
|
|
|
+ border-top: 1px dashed #e5e5e5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-materials {
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 弹窗样式
|
|
|
+ .selector-popup {
|
|
|
+ width: 90%;
|
|
|
+ max-width: 500px;
|
|
|
+ max-height: 70vh;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
+
|
|
|
+ /* 确保弹窗居中 */
|
|
|
+ position: relative;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ .popup-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 15px;
|
|
|
+ border-bottom: 1px solid #e5e5e5;
|
|
|
+
|
|
|
+ .popup-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-bar {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-content {
|
|
|
+ max-height: 50vh;
|
|
|
+
|
|
|
+ .selector-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .selector-item-content {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .item-info {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+
|
|
|
+ .item-name {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ flex-basis: 100%;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-code,
|
|
|
+ .item-spec,
|
|
|
+ .item-unit {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-code::after,
|
|
|
+ .item-spec::after,
|
|
|
+ .item-unit::after {
|
|
|
+ content: ' | ';
|
|
|
+ margin: 0 4px;
|
|
|
+ color: #ddd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-unit:last-child::after {
|
|
|
+ content: '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .selected-tag {
|
|
|
+ color: #409eff;
|
|
|
+ font-size: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-text {
|
|
|
+ text-align: center;
|
|
|
+ padding: 12px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .no-more-text {
|
|
|
+ text-align: center;
|
|
|
+ padding: 12px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-data {
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+/*}*/
|
|
|
+</style>
|