start.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. <template>
  2. <view class="purchase-container">
  3. <!-- 基本信息 -->
  4. <uni-card title="基本信息" spacing="0" >
  5. <uni-forms ref="baseFormRef" :modelValue="baseForm" :rules="baseFormRules" label-position="left" :label-width="100" :border="true">
  6. <uni-forms-item name="contractPurchaseNumber" label="采购单号">
  7. <uni-easyinput v-model="baseForm.contractPurchaseNumber" disabled placeholder="自动生成"></uni-easyinput>
  8. </uni-forms-item>
  9. <uni-forms-item name="contractPurchaseName" label="采购单名称" required>
  10. <uni-easyinput v-model="baseForm.contractPurchaseName" placeholder="请输入采购单名称"></uni-easyinput>
  11. </uni-forms-item>
  12. <uni-forms-item name="applyDate" label="申请日期">
  13. <uni-easyinput v-model="baseForm.applyDate" type="date" disabled />
  14. </uni-forms-item>
  15. <uni-forms-item name="department" label="申请部门">
  16. <uni-easyinput v-model="baseForm.department" disabled></uni-easyinput>
  17. </uni-forms-item>
  18. </uni-forms>
  19. </uni-card>
  20. <!-- 物料明细 -->
  21. <uni-card title="物品信息" spacing="0">
  22. <view class="material-actions">
  23. <button type="primary" size="mini" @click="openMaterialSelector" plain>添加物料</button>
  24. </view>
  25. <!-- 物料列表 - 卡片式展示 -->
  26. <view v-if="materialList.length > 0" class="material-list">
  27. <view v-for="(item, index) in materialList" :key="index" class="material-card">
  28. <view class="material-header" @click="toggleExpand(index)">
  29. <view class="material-main-info">
  30. <text class="material-name">{{ item.materialName }}</text>
  31. <text class="material-code">{{ item.materialCode }}</text>
  32. </view>
  33. <view class="material-expand">
  34. <uni-icons :type="item.expanded ? 'up' : 'down'" size="16" color="#999"></uni-icons>
  35. </view>
  36. </view>
  37. <!-- 展开的详细信息 -->
  38. <view v-show="item.expanded" class="material-detail">
  39. <view class="detail-row">
  40. <text class="detail-label">规格型号:</text>
  41. <text class="detail-value">{{ item.materialModel }}</text>
  42. <text class="detail-label" style="margin-left: 15px;">单位:</text>
  43. <text class="detail-value">{{ item.measureName }}</text>
  44. </view>
  45. <view class="detail-row delete-row">
  46. <text class="detail-label">数量:</text>
  47. <uni-easyinput
  48. v-model="item.qty"
  49. type="digit"
  50. placeholder="请输入数量"
  51. style="width: 100px; display: inline-block; margin-right: 15px;"
  52. />
  53. <button type="warn" size="mini" @click="removeMaterial(index)">删除</button>
  54. </view>
  55. </view>
  56. </view>
  57. </view>
  58. <view v-else class="empty-materials">
  59. <text>暂无物料,请点击上方按钮添加</text>
  60. </view>
  61. </uni-card>
  62. <!-- 上传附件 -->
  63. <uni-card title="上传附件" :extra="`${fileList.length}/50`" spacing="0">
  64. <uni-file-picker
  65. ref="filePicker"
  66. v-model="fileList"
  67. :auto-upload="true"
  68. mode="list"
  69. :limit="50"
  70. :limit-length="50"
  71. file-mediatype="all"
  72. @select="handleFileSelect"
  73. @progress="handleFileProgress"
  74. @success="handleFileSuccess"
  75. @fail="handleFileFail"
  76. @delete="handleFileDelete"
  77. />
  78. </uni-card>
  79. <!-- 提交按钮 -->
  80. <view class="submit-btn-wrapper">
  81. <button type="primary" :loading="isSubmitting" :disabled="isSubmitting" @click="submitForm">提 交</button>
  82. </view>
  83. <!-- 选择器弹出层 -->
  84. <uni-popup ref="selectorPopup" type="center">
  85. <view class="selector-popup">
  86. <view class="popup-header">
  87. <text class="popup-title">{{ popupTitle }}</text>
  88. <uni-icons type="closeempty" size="20" @click="closePopup"></uni-icons>
  89. </view>
  90. <!-- 搜索框 -->
  91. <view v-if="selectorType === 'material'" class="search-bar">
  92. <uni-easyinput
  93. v-model="searchKeyword"
  94. placeholder="输入物料名称搜索"
  95. clearable
  96. @confirm="handleSearch"
  97. />
  98. <button type="primary" size="mini" @click="handleSearch">搜索</button>
  99. </view>
  100. <scroll-view
  101. scroll-y
  102. class="popup-content"
  103. refresher-enabled
  104. :refresher-triggered="isRefreshing"
  105. @refresherrefresh="onRefresh"
  106. @scrolltolower="loadMore"
  107. >
  108. <view v-for="(item, index) in selectorList" :key="index"
  109. class="selector-item"
  110. @click="selectItem(item)">
  111. <view class="selector-item-content">
  112. <view class="item-info">
  113. <text class="item-name">{{ item.materialName || item.itemName }}</text>
  114. <text class="item-code">{{ item.materialCode || item.itemCode }}</text>
  115. <text class="item-spec">{{ item.materialModel || item.specification || '' }}</text>
  116. <text class="item-unit">{{ item.measureName || item.unit || '' }}</text>
  117. <text v-if="item.itemTypeName" class="item-type">{{ item.itemTypeName }}</text>
  118. </view>
  119. <text v-if="isSelected(item)" class="selected-tag">已选择</text>
  120. </view>
  121. </view>
  122. <!-- 加载状态 -->
  123. <view v-if="isLoading && selectorList.length > 0" class="loading-text">
  124. <uni-load-more status="loading" />
  125. </view>
  126. <!-- 没有更多数据 -->
  127. <view v-else-if="!hasMore && selectorList.length > 0" class="no-more-text">
  128. <text>没有更多了</text>
  129. </view>
  130. <!-- 空数据提示 -->
  131. <view v-if="selectorList.length === 0 && !isLoading" class="empty-data">
  132. <text>{{ searchKeyword ? '暂无相关数据' : '暂无数据' }}</text>
  133. </view>
  134. </scroll-view>
  135. </view>
  136. </uni-popup>
  137. </view>
  138. </template>
  139. <script setup lang="ts">
  140. import { onMounted, reactive, ref, computed } from 'vue'
  141. import { onLoad } from '@dcloudio/uni-app'
  142. import { useUserStore } from '@/store/user.js'
  143. import $modal from '@/plugins/modal.js'
  144. import $tab from '@/plugins/tab.js'
  145. import { getPurchaseInitData, startPurchaseProcess, getMaterialList } from '@/api/purchase.js'
  146. import { uploadFile, getProcessInfo } from '@/api/work.js'
  147. import config from '@/config.js'
  148. const userStore = useUserStore()
  149. // 流程信息(参考通用流程)
  150. let processInfo = reactive({
  151. modelName: '采购申请',
  152. reqOffice: 0,
  153. control: '',
  154. formId: '',
  155. modelId: '',
  156. tmodelId: '',
  157. isMoreIns: '',
  158. pathJudgeType: '',
  159. form: []
  160. })
  161. // 接收页面参数
  162. onLoad((options) => {
  163. const { modelName, modelId, control } = options
  164. if (modelName) processInfo.modelName = modelName
  165. if (modelId) processInfo.modelId = modelId
  166. if (control) processInfo.control = control
  167. })
  168. // 初始化流程信息(获取 formId, tmodelId 等)
  169. onMounted(() => {
  170. initProcessInfo().then(() => {
  171. // 初始化基本信息(流水号、部门等)
  172. initBaseForm()
  173. })
  174. })
  175. function initProcessInfo() {
  176. return new Promise<void>((resolve, reject) => {
  177. // 如果没有传入 modelId,使用默认值或提示错误
  178. if (!processInfo.modelId) {
  179. $modal.msgError('缺少流程模型')
  180. reject(new Error('缺少流程模型 ID'))
  181. return
  182. }
  183. getProcessInfo(processInfo)
  184. .then(({ returnParams }) => {
  185. const { formId, tmodelId, isMoreIns, pathJudgeType, reqOffice } = returnParams.flow[0]
  186. processInfo.formId = formId
  187. processInfo.tmodelId = tmodelId
  188. processInfo.isMoreIns = isMoreIns
  189. processInfo.pathJudgeType = pathJudgeType
  190. processInfo.reqOffice = reqOffice || 0 // 是否需要附件(0-不需要,1-需要)
  191. resolve()
  192. })
  193. .catch(err => {
  194. console.error('获取流程信息失败:', err)
  195. reject(err)
  196. })
  197. })
  198. }
  199. function initBaseForm() {
  200. // 获取初始化数据(流水号、部门信息等)
  201. getPurchaseInitData(userStore.user.useId).then(res => {
  202. if (res.returnCode === '1') {
  203. const params = res.returnParams
  204. baseForm.contractPurchaseNumber = params.contractPurchaseNumber || ''
  205. baseForm.initiator = params.initiator || userStore.user.name
  206. baseForm.department = params.department || ''
  207. baseForm.depid = params.depid || null
  208. }
  209. }).catch(err => {
  210. console.error('获取初始化数据失败:', err)
  211. })
  212. }
  213. const baseForm = reactive({
  214. contractPurchaseNumber: '',
  215. contractPurchaseName: '',
  216. // iOS 兼容的日期格式:使用 "/" 而不是 "-"
  217. applyDate: '',
  218. initiator: '',
  219. department: '',
  220. depid: null
  221. })
  222. const materialList = ref<any[]>([])
  223. const isSubmitting = ref(false)
  224. // 文件上传相关
  225. const fileList = ref<any[]>([])
  226. const fileSeqs = ref<any[]>([])
  227. // 表单校验规则(参考通用流程)
  228. const baseFormRef = ref(null)
  229. const baseFormRules = computed(() => {
  230. return {
  231. contractPurchaseName: {
  232. rules: [{ required: true, message: '请输入采购单名称' }],
  233. label: '采购单名称'
  234. }
  235. }
  236. })
  237. // 选择器相关
  238. const selectorPopup = ref(null)
  239. const selectorList = ref<any[]>([])
  240. const selectorType = ref('') // 'material'
  241. // 分页和搜索相关
  242. const currentPage = ref(1)
  243. const pageSize = 20
  244. const hasMore = ref(true)
  245. const isLoading = ref(false)
  246. const isRefreshing = ref(false)
  247. const searchKeyword = ref('')
  248. const popupTitle = computed(() => {
  249. return selectorType.value === 'material' ? '选择物料' : '选择'
  250. })
  251. // 切换展开/收起状态
  252. function toggleExpand(index: number) {
  253. if (materialList.value[index]) {
  254. materialList.value[index].expanded = !materialList.value[index].expanded
  255. }
  256. }
  257. onLoad(() => {
  258. initData()
  259. })
  260. async function initData() {
  261. try {
  262. const res = await getPurchaseInitData(userStore.user.useId)
  263. if (res.returnCode === '1') {
  264. const data = res.returnParams
  265. baseForm.contractPurchaseNumber = data.contractPurchaseNumber
  266. baseForm.initiator = data.initiator
  267. baseForm.department = data.department
  268. baseForm.applyDate = data.applyDate
  269. baseForm.depid = data.depid
  270. }
  271. } catch (error) {
  272. console.error('初始化数据失败', error)
  273. $modal.msgError('加载数据失败')
  274. }
  275. }
  276. // 打开物料选择器
  277. async function openMaterialSelector() {
  278. selectorType.value = 'material'
  279. currentPage.value = 1
  280. hasMore.value = true
  281. selectorList.value = []
  282. await loadMaterials(1, false)
  283. openPopup()
  284. }
  285. // 加载物料列表(分页)
  286. async function loadMaterials(page: number, append: boolean = false) {
  287. if (isLoading.value) return
  288. isLoading.value = true
  289. if (page === 1) {
  290. isRefreshing.value = true
  291. }
  292. try {
  293. const res = await getMaterialList(
  294. userStore.user.useId,
  295. page,
  296. pageSize,
  297. searchKeyword.value
  298. )
  299. if (res.returnCode === '1') {
  300. const result = res.returnParams
  301. const newList = result.list || []
  302. if (append) {
  303. // 追加模式(加载更多)
  304. selectorList.value = [...selectorList.value, ...newList]
  305. } else {
  306. // 覆盖模式(刷新/搜索)
  307. selectorList.value = newList
  308. }
  309. // 判断是否还有更多数据
  310. hasMore.value = selectorList.value.length < result.total
  311. } else {
  312. $modal.msgError('加载物料失败')
  313. }
  314. } catch (error) {
  315. console.error('加载物料失败', error)
  316. $modal.msgError('加载失败')
  317. } finally {
  318. isLoading.value = false
  319. isRefreshing.value = false
  320. }
  321. }
  322. // 加载更多
  323. function loadMore() {
  324. if (!hasMore.value || isLoading.value) return
  325. currentPage.value++
  326. loadMaterials(currentPage.value, true)
  327. }
  328. // 下拉刷新
  329. function onRefresh() {
  330. currentPage.value = 1
  331. loadMaterials(1, false)
  332. }
  333. // 搜索
  334. function handleSearch() {
  335. currentPage.value = 1
  336. loadMaterials(1, false)
  337. }
  338. // 判断物料是否已选中
  339. function isSelected(item: any): boolean {
  340. const code = item.materialCode || item.itemCode
  341. return materialList.value.some(m =>
  342. (m.materialCode || '') === code
  343. )
  344. }
  345. function openPopup() {
  346. ;(selectorPopup.value as any).open()
  347. }
  348. function closePopup() {
  349. ;(selectorPopup.value as any).close()
  350. }
  351. function selectItem(item: any) {
  352. // 只处理物料选择
  353. addMaterial(item)
  354. closePopup()
  355. }
  356. function addMaterial(item: any) {
  357. // 检查是否已存在
  358. const exists = materialList.value.some(m => m.materialCode === item.materialCode || m.materialCode === item.itemCode)
  359. if (exists) {
  360. $modal.msgWarn('该物料已存在')
  361. return
  362. }
  363. materialList.value.push({
  364. materialCode: item.materialCode || item.itemCode,
  365. materialName: item.materialName || item.itemName,
  366. materialModel: item.materialModel || item.specification,
  367. measureName: item.measureName || item.unit,
  368. itemTypeName: item.itemTypeName || '', // 物料类别
  369. qty: item.qty || 0,
  370. expanded: true // 默认展开
  371. })
  372. }
  373. async function handleFileSelect(files: any) {
  374. files.tempFiles.forEach(async (file: any) => {
  375. const data = {
  376. name: file.name,
  377. filePath: file.path,
  378. }
  379. try {
  380. const res = await uploadFile(data)
  381. file.seq = res.returnParams
  382. fileSeqs.value.push({ 'seq': res.returnParams, 'path': file.path })
  383. fileList.value.push(file)
  384. $modal.msgSuccess('文件' + data.name + '上传成功')
  385. } catch (err) {
  386. $modal.msgError('文件' + data.name + '上传失败,请删除重新上传')
  387. console.error('文件上传失败:', err)
  388. }
  389. })
  390. }
  391. function handleFileProgress(file: any, progress: any) {
  392. //console.log('handleFileProgress', file, progress)
  393. }
  394. function handleFileSuccess(file: any, res: any) {
  395. //console.log('handleFileSuccess', file, res)
  396. }
  397. function handleFileFail(file: any, err: any) {
  398. //console.log('handleFileFail', file, err)
  399. }
  400. function handleFileDelete(file: any) {
  401. const index = fileSeqs.value.findIndex(({ path }) => path === file.tempFilePath)
  402. if (index !== -1) {
  403. fileSeqs.value.splice(index, 1)
  404. }
  405. }
  406. function removeMaterial(index: number) {
  407. $modal.confirm('', '确认删除该物料?')
  408. .then(() => {
  409. materialList.value.splice(index, 1)
  410. })
  411. .catch(() => {})
  412. }
  413. async function submitForm() {
  414. // 先进行表单校验(使用 uni-forms 的 validate 方法)
  415. if (!baseFormRef.value) {
  416. $modal.msgError('表单未初始化')
  417. return
  418. }
  419. try {
  420. await baseFormRef.value.validate()
  421. } catch (error) {
  422. console.error('表单校验失败', error)
  423. $modal.msgError('请填写必填项')
  424. return
  425. }
  426. // 验证采购单名称长度
  427. if (baseForm.contractPurchaseName && baseForm.contractPurchaseName.length > 100) {
  428. $modal.msgError('采购单名称长度不能超过 100 个字符')
  429. return
  430. }
  431. // 校验物料列表和数量
  432. if (materialList.value.length === 0) {
  433. $modal.msgError('请添加物料')
  434. return
  435. }
  436. // 检查每个物料的数量是否填写且为大于 0 的数字
  437. for (let i = 0; i < materialList.value.length; i++) {
  438. const item = materialList.value[i]
  439. if (!item.qty || Number(item.qty) <= 0) {
  440. $modal.msgError(`请填写第${i + 1}个物料的数量,且必须大于 0`)
  441. return
  442. }
  443. }
  444. isSubmitting.value = true
  445. try {
  446. // 构建表单数据(包含流程信息)
  447. const formData = {
  448. ...baseForm,
  449. detailList: materialList.value.map(item => ({
  450. materialCode: item.materialCode,
  451. materialName: item.materialName,
  452. materialModel: item.materialModel,
  453. measureName: item.measureName,
  454. qty: item.qty,
  455. price: item.price,
  456. cess: item.cess
  457. }))
  458. }
  459. // 准备提交流程参数(参考通用流程 bpm_20150325001FlowStart)
  460. const processInfoData = {
  461. staffId: userStore.user.useId,
  462. staffName: userStore.user.name,
  463. gxId: userStore.user.gxId,
  464. groupId: userStore.user.groupid,
  465. insName: userStore.user.name + '的'+ processInfo.modelName,
  466. modelId: processInfo.modelId, // 从页面参数或初始化获取
  467. tmodelId: processInfo.tmodelId, // 从 getProcessInfo 获取
  468. formId: processInfo.formId, // 从 getProcessInfo 获取
  469. // 附件 ID:参考 work.js,为空时设置为空字符串
  470. fileIds: fileSeqs.value.length === 0 ? '' : fileSeqs.value.map(f => f.seq)
  471. }
  472. // 检查是否需要附件
  473. if (processInfo.reqOffice == 1 && fileSeqs.value.length === 0) {
  474. $modal.msgError('该流程需要上传附件')
  475. isSubmitting.value = false
  476. return
  477. }
  478. // 一次性提交表单和流程数据(包含附件)
  479. const res = await startPurchaseProcess(userStore.user.useId, formData, processInfoData)
  480. if (res.returnCode !== '1') {
  481. $modal.msgError(res.returnMsg || '提交失败')
  482. isSubmitting.value = false
  483. return
  484. }
  485. $modal.msgSuccess('提交成功')
  486. setTimeout(() => {
  487. $tab.navigateBack()
  488. }, 1000)
  489. } catch (error) {
  490. console.error('提交失败', error)
  491. $modal.msgError('提交失败,请重试')
  492. } finally {
  493. isSubmitting.value = false
  494. }
  495. }
  496. </script>
  497. <style lang="scss" scoped>
  498. .purchase-container {
  499. padding-bottom: 80px;
  500. }
  501. .material-actions {
  502. display: flex;
  503. gap: 10px;
  504. margin-bottom: 15px;
  505. button {
  506. flex: 1;
  507. }
  508. }
  509. // 基本信息中的禁用字段样式优化(参考 edit/index.vue)
  510. ::v-deep .uni-forms {
  511. .uni-forms-item__content {
  512. .uni-easyinput__content-input {
  513. font-size: calc(14px + 1.2*(1rem - 16px)) !important;
  514. font-weight: 500;
  515. color: #333;
  516. }
  517. .uni-date {
  518. .uni-icons {
  519. font-size: calc(22px + 1.2*(1rem - 16px)) !important;
  520. font-weight: 500;
  521. }
  522. .uni-date__x-input {
  523. height: auto;
  524. font-size: calc(14px + 1.2*(1rem - 16px)) !important;
  525. font-weight: 500;
  526. color: #333;
  527. }
  528. }
  529. }
  530. }
  531. // 物料列表 - 卡片式展示
  532. .material-list {
  533. .display: flex;
  534. flex-direction: column;
  535. gap: 10px;
  536. }
  537. .material-card {
  538. border: 1px solid #e5e5e5;
  539. border-radius: 6px;
  540. overflow: hidden;
  541. background-color: #fff;
  542. margin-bottom: 8px;
  543. .material-header {
  544. display: flex;
  545. justify-content: space-between;
  546. align-items: center;
  547. padding: 10px 12px;
  548. background-color: #f8f9fa;
  549. cursor: pointer;
  550. .material-main-info {
  551. display: flex;
  552. align-items: center;
  553. gap: 8px;
  554. flex: 1;
  555. .material-name {
  556. font-size: 14px;
  557. font-weight: bold;
  558. color: #333;
  559. }
  560. .material-code {
  561. font-size: 12px;
  562. color: #999;
  563. white-space: nowrap;
  564. }
  565. }
  566. .material-expand {
  567. display: flex;
  568. align-items: center;
  569. margin-left: 10px;
  570. }
  571. }
  572. .material-detail {
  573. padding: 10px 12px;
  574. border-top: 1px solid #e5e5e5;
  575. .detail-row {
  576. display: flex;
  577. align-items: center;
  578. flex-wrap: wrap;
  579. gap: 8px;
  580. .detail-label {
  581. font-size: 13px;
  582. color: #666;
  583. flex-shrink: 0;
  584. }
  585. .detail-value {
  586. flex: 0 0 auto;
  587. font-size: 13px;
  588. color: #333;
  589. }
  590. }
  591. .delete-row {
  592. margin-top: 10px;
  593. text-align: center;
  594. padding-top: 8px;
  595. border-top: 1px dashed #e5e5e5;
  596. }
  597. }
  598. }
  599. .empty-materials {
  600. text-align: center;
  601. padding: 30px;
  602. color: #909399;
  603. font-size: 14px;
  604. }
  605. .submit-btn-wrapper {
  606. position: sticky;
  607. width: 100%;
  608. bottom: 10px;
  609. padding: 10px 15px;
  610. background-color: #fff;
  611. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
  612. z-index: 10;
  613. box-sizing: border-box;
  614. button {
  615. background-color: #007aff !important;
  616. color: #fff !important;
  617. }
  618. }
  619. .selector-popup {
  620. width: 90%;
  621. max-width: 500px;
  622. max-height: 70vh;
  623. background-color: #fff;
  624. border-radius: 12px;
  625. overflow: hidden;
  626. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  627. /* 确保弹窗居中 */
  628. position: relative;
  629. margin: 0 auto;
  630. .popup-header {
  631. display: flex;
  632. justify-content: space-between;
  633. align-items: center;
  634. padding: 15px;
  635. border-bottom: 1px solid #e5e5e5;
  636. .popup-title {
  637. font-size: 16px;
  638. font-weight: bold;
  639. }
  640. }
  641. .search-bar {
  642. display: flex;
  643. gap: 10px;
  644. padding: 10px 15px;
  645. align-items: center;
  646. }
  647. .popup-content {
  648. max-height: 50vh;
  649. .selector-item {
  650. padding: 10px 12px;
  651. border-bottom: 1px solid #f0f0f0;
  652. &:active {
  653. background-color: #f5f5f5;
  654. }
  655. .selector-item-content {
  656. display: flex;
  657. justify-content: space-between;
  658. align-items: center;
  659. gap: 8px;
  660. .item-info {
  661. flex: 1;
  662. display: flex;
  663. flex-wrap: wrap;
  664. align-items: center;
  665. gap: 4px;
  666. .item-name {
  667. font-size: 15px;
  668. font-weight: bold;
  669. color: #333;
  670. flex-basis: 100%;
  671. margin-bottom: 4px;
  672. }
  673. .item-code,
  674. .item-spec,
  675. .item-unit,
  676. .item-type {
  677. font-size: 13px;
  678. color: #666;
  679. white-space: nowrap;
  680. font-weight: 500;
  681. }
  682. .item-code::after,
  683. .item-spec::after,
  684. .item-unit::after {
  685. content: ' | ';
  686. margin: 0 4px;
  687. color: #ddd;
  688. }
  689. .item-type:last-child::after,
  690. .item-unit:last-child::after {
  691. content: '';
  692. }
  693. }
  694. .selected-tag {
  695. color: #409eff;
  696. font-size: 12px;
  697. flex-shrink: 0;
  698. white-space: nowrap;
  699. }
  700. }
  701. }
  702. .loading-text {
  703. text-align: center;
  704. padding: 12px;
  705. color: #909399;
  706. font-size: 13px;
  707. }
  708. .no-more-text {
  709. text-align: center;
  710. padding: 12px;
  711. color: #909399;
  712. font-size: 13px;
  713. }
  714. .empty-data {
  715. text-align: center;
  716. padding: 30px;
  717. color: #909399;
  718. }
  719. }
  720. }
  721. </style>