create.uvue 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. <template>
  2. <uni-navbar-lite :showRight="showRightBtn" :title="getPageTitle()" :rightBtnText="rightBtnText" :rightBtnStyle="rightBtnStyle" @rightClick="handleDelete"></uni-navbar-lite>
  3. <view class="page-container">
  4. <scroll-view class="page-content" :scroll-y="true">
  5. <view class="section">
  6. <view class="section-header">
  7. <view class="section-indicator"></view>
  8. <text class="section-title">产品信息</text>
  9. </view>
  10. <view class="form-card">
  11. <view v-if="selectedProduct != null" class="selected-product-card" @click="openProductPicker">
  12. <view class="selected-product-info">
  13. <text class="selected-product-name">{{ selectedProduct.itemName }}</text>
  14. <text class="selected-product-code">编码: {{ selectedProduct.itemCode }}</text>
  15. <text class="selected-product-spec" v-if="selectedProduct.specification != null && selectedProduct.specification.length > 0">规格: {{ selectedProduct.specification }}</text>
  16. </view>
  17. <view class="change-product-btn" v-if="!isEdit">
  18. <text class="change-product-text">更换</text>
  19. </view>
  20. </view>
  21. <view v-else class="select-product-btn" @click="openProductPicker">
  22. <text class="select-product-text">+ 请选择产品</text>
  23. </view>
  24. </view>
  25. </view>
  26. <view class="section">
  27. <view class="section-header">
  28. <view class="section-indicator"></view>
  29. <text class="section-title">汇报信息</text>
  30. </view>
  31. <view class="form-card">
  32. <view class="form-row">
  33. <text class="form-label">汇报数量</text>
  34. <input class="form-input" :class="{ 'disabled': formStatus !== 'PREPARE' }" type="digit" placeholder="请输入数量" v-model="formData.quantity" :disabled="formStatus !== 'PREPARE'" />
  35. </view>
  36. <view class="form-row">
  37. <text class="form-label">单位</text>
  38. <view class="form-input-static">包</view>
  39. </view>
  40. <view class="form-row">
  41. <text class="form-label">报工日期</text>
  42. <view class="form-input disabled">{{ formData.reportDate || currentDate }}</view>
  43. </view>
  44. <view class="form-row">
  45. <text class="form-label">开始时间</text>
  46. <view class="input-with-btn">
  47. <input class="form-input" :class="{ 'disabled': formStatus !== 'PREPARE' }" type="text" :value="formData.startTime" placeholder="请输入时间" :disabled="formStatus !== 'PREPARE'" />
  48. <picker v-if="formStatus === 'PREPARE'" mode="time" :value="formData.startTime" @change="onStartTimeChange">
  49. <view class="picker-btn">选择</view>
  50. </picker>
  51. </view>
  52. </view>
  53. <view class="form-row">
  54. <text class="form-label">结束时间</text>
  55. <view class="input-with-btn">
  56. <input class="form-input" :class="{ 'disabled': formStatus !== 'PREPARE' }" type="text" :value="formData.endTime" placeholder="请输入时间" :disabled="formStatus !== 'PREPARE'" />
  57. <picker v-if="formStatus === 'PREPARE'" mode="time" :value="formData.endTime" @change="onEndTimeChange">
  58. <view class="picker-btn">选择</view>
  59. </picker>
  60. </view>
  61. </view>
  62. </view>
  63. </view>
  64. <view class="section">
  65. <view class="section-header">
  66. <view class="section-indicator"></view>
  67. <text class="section-title">备注</text>
  68. </view>
  69. <view class="form-card">
  70. <view class="form-row">
  71. <textarea class="form-textarea" placeholder="请输入备注" v-model="formData.remark" />
  72. </view>
  73. </view>
  74. </view>
  75. </scroll-view>
  76. <view class="bottom-buttons" v-if="formStatus === 'PREPARE'">
  77. <button class="save-btn" @click="handleSave">保存</button>
  78. <button class="confirm-btn" @click="handleSaveAndConfirm">保存&确认</button>
  79. </view>
  80. <view v-if="showProductPicker" class="picker-modal">
  81. <view class="modal-mask" @click="closeProductPicker"></view>
  82. <view class="modal-content">
  83. <view class="modal-header">
  84. <text class="modal-title">选择产品</text>
  85. <text class="modal-close" @click="closeProductPicker">取消</text>
  86. </view>
  87. <view class="search-bar">
  88. <view class="search-box">
  89. <input class="search-input" type="text" placeholder="请输入关键字查询" v-model="keyword" @input="handleSearch" />
  90. <view v-if="keyword.length > 0" class="clear-btn" @tap="clearKeyword">
  91. <text class="clear-btn-text">×</text>
  92. </view>
  93. <view class="search-btn" @tap="handleSearch">
  94. <text class="search-btn-text">搜索</text>
  95. </view>
  96. </view>
  97. </view>
  98. <scroll-view class="product-list-scroll" :scroll-y="true">
  99. <view
  100. v-for="(item, index) in productList"
  101. :key="item.itemId + '_' + page"
  102. class="list-item"
  103. >
  104. <view class="item-header">
  105. <view class="item-info">
  106. <view class="item-name-row">
  107. <text class="item-name">{{ item.itemName }}</text>
  108. </view>
  109. <view class="item-sub-row">
  110. <text class="item-sub-title">{{ item.itemTypeName }}</text>
  111. <text class="item-code">{{ item.itemCode }}</text>
  112. </view>
  113. </view>
  114. <view class="add-btn" @click.stop="selectProduct(item)">
  115. <text class="add-icon">+</text>
  116. </view>
  117. </view>
  118. </view>
  119. <view v-if="productList.length === 0 && !loading" class="empty-tip">
  120. <text class="empty-tip-text">暂无产品</text>
  121. </view>
  122. </scroll-view>
  123. <view class="pagination">
  124. <view class="page-btn" :class="{disabled: page <= 1}" @click="prevPage">
  125. <text class="page-btn-text"><</text>
  126. </view>
  127. <text class="page-info">第{{ page }}页 / 共{{ totalPages }}页</text>
  128. <view class="page-btn" :class="{disabled: page >= totalPages}" @click="nextPage">
  129. <text class="page-btn-text">></text>
  130. </view>
  131. </view>
  132. </view>
  133. </view>
  134. </view>
  135. </template>
  136. <script setup lang="uts">
  137. import { ref, computed } from 'vue'
  138. import { onLoad } from '@dcloudio/uni-app';
  139. import { getProReportById, addProReport, updateProReport, confirmProReport, delProReport, getProductList } from '../../api/pro/index'
  140. import { getUserInfo } from '../../utils/storage'
  141. type ProductItem = {
  142. itemId: string
  143. itemCode: string
  144. itemName: string
  145. itemTypeName: string
  146. specification: string
  147. measureName: string
  148. unitOfMeasure: string
  149. }
  150. const getTodayStr = (): string => {
  151. const d = new Date()
  152. const y = d.getFullYear()
  153. const m = (d.getMonth() + 1).toString().padStart(2, '0')
  154. const day = d.getDate().toString().padStart(2, '0')
  155. return `${y}-${m}-${day}`
  156. }
  157. const extractTime = (datetimeStr: string): string => {
  158. if (!datetimeStr) return ''
  159. const parts = datetimeStr.split(' ')
  160. if (parts.length >= 2) {
  161. return parts[1].substring(0, 5)
  162. }
  163. return ''
  164. }
  165. const isEdit = ref<boolean>(false)
  166. const reportId = ref<string>('')
  167. const formStatus = ref<string>('')
  168. const currentDate = ref<string>(getTodayStr())
  169. let searchTimer: number | null = null
  170. const getPageTitle = (): string => {
  171. if (!isEdit.value) {
  172. return '新增生产汇报'
  173. }
  174. if (formStatus.value === 'PREPARE') {
  175. return '编辑生产汇报'
  176. }
  177. return '查看生产汇报'
  178. }
  179. const showRightBtn = computed(() => {
  180. return isEdit.value && formStatus.value === 'PREPARE'
  181. })
  182. const rightBtnText = computed(() => {
  183. return isEdit.value && formStatus.value === 'PREPARE' ? '删除' : ''
  184. })
  185. const rightBtnStyle = computed(() => {
  186. return {
  187. backgroundColor: '#ff4d4f',
  188. color: '#ffffff',
  189. fontSize: '28rpx',
  190. padding: '8rpx 20rpx',
  191. borderRadius: '8rpx'
  192. }
  193. })
  194. const formData = ref<UTSJSONObject>({
  195. productCode: '',
  196. productName: '',
  197. specification: '',
  198. quantity: '',
  199. unitOfMeasure: '',
  200. unitName: '',
  201. reportDate: '',
  202. startTime: '',
  203. endTime: '',
  204. remark: ''
  205. })
  206. const selectedProduct = ref<ProductItem | null>(null)
  207. const showProductPicker = ref<boolean>(false)
  208. const keyword = ref<string>("")
  209. const onReportDateChange = (e: any): void => {
  210. formData.value['reportDate'] = e.detail.value as string
  211. }
  212. const onStartTimeChange = (e: any): void => {
  213. formData.value['startTime'] = e.detail.value as string
  214. }
  215. const onEndTimeChange = (e: any): void => {
  216. formData.value['endTime'] = e.detail.value as string
  217. }
  218. const productList = ref<ProductItem[]>([])
  219. const page = ref<number>(1)
  220. const pageSize: number = 10
  221. const total = ref<number>(0)
  222. const loading = ref<boolean>(false)
  223. const totalPages = computed((): number => {
  224. if (total.value <= 0) return 1
  225. return Math.ceil(total.value / pageSize)
  226. })
  227. const loadProducts = (): void => {
  228. loading.value = true
  229. getProductList(page.value, pageSize, keyword.value).then((res: any) => {
  230. const data = res as UTSJSONObject
  231. const rows = data['rows']
  232. const totalNum = data['total']
  233. if (totalNum != null) {
  234. total.value = totalNum as number
  235. }
  236. if (rows != null) {
  237. const listData = rows as UTSJSONObject[]
  238. const arr: ProductItem[] = []
  239. listData.forEach((item: UTSJSONObject) => {
  240. const it: ProductItem = {
  241. itemId: item['itemId'] != null ? item['itemId'].toString() : '',
  242. itemCode: item['itemCode'] != null ? item['itemCode'].toString() : '',
  243. itemName: item['itemName'] != null ? item['itemName'].toString() : '',
  244. itemTypeName: item['itemTypeName'] != null ? item['itemTypeName'].toString() : '',
  245. specification: item['specification'] != null ? item['specification'].toString() : '',
  246. measureName: item['measureName'] != null ? item['measureName'].toString() : '',
  247. unitOfMeasure: item['unitOfMeasure'] != null ? item['unitOfMeasure'].toString() : ''
  248. }
  249. arr.push(it)
  250. })
  251. productList.value = arr
  252. } else {
  253. productList.value = []
  254. }
  255. loading.value = false
  256. }).catch(() => {
  257. productList.value = []
  258. loading.value = false
  259. })
  260. }
  261. const selectProduct = (item: ProductItem): void => {
  262. selectedProduct.value = item
  263. formData.value['productCode'] = item.itemCode
  264. formData.value['productName'] = item.itemName
  265. formData.value['specification'] = item.specification
  266. formData.value['unitOfMeasure'] = item.unitOfMeasure || ''
  267. showProductPicker.value = false
  268. }
  269. const openProductPicker = (): void => {
  270. if (isEdit.value) return
  271. showProductPicker.value = true
  272. loadProducts()
  273. }
  274. const closeProductPicker = (): void => {
  275. showProductPicker.value = false
  276. keyword.value = ''
  277. page.value = 1
  278. }
  279. const prevPage = (): void => {
  280. if (page.value > 1) {
  281. page.value--
  282. loadProducts()
  283. }
  284. }
  285. const nextPage = (): void => {
  286. if (page.value < totalPages.value) {
  287. page.value++
  288. loadProducts()
  289. }
  290. }
  291. const handleSearch = (): void => {
  292. const timer = searchTimer
  293. if (timer != null) {
  294. clearTimeout(timer)
  295. }
  296. searchTimer = setTimeout(() => {
  297. page.value = 1
  298. loadProducts()
  299. }, 300)
  300. }
  301. const clearKeyword = (): void => {
  302. keyword.value = ''
  303. page.value = 1
  304. loadProducts()
  305. }
  306. const loadDetail = (): void => {
  307. if (reportId.value.length === 0) return
  308. getProReportById(reportId.value).then((response: any) => {
  309. const res = response as UTSJSONObject
  310. const data = res["data"] as UTSJSONObject
  311. formData.value['productCode'] = data['productCode'] != null ? data['productCode'].toString() : ''
  312. formData.value['productName'] = data['productName'] != null ? data['productName'].toString() : ''
  313. formData.value['specification'] = data['specification'] != null ? data['specification'].toString() : ''
  314. formData.value['quantity'] = data['quantity'] != null ? data['quantity'].toString() : ''
  315. formData.value['unitOfMeasure'] = data['unitOfMeasure'] != null ? data['unitOfMeasure'].toString() : ''
  316. formData.value['unitName'] = data['unitName'] != null ? data['unitName'].toString() : ''
  317. formData.value['reportDate'] = data['reportDate'] != null ? data['reportDate'].toString() : ''
  318. formData.value['startTime'] = data['startTime'] != null ? extractTime(data['startTime'].toString()) : ''
  319. formData.value['endTime'] = data['endTime'] != null ? extractTime(data['endTime'].toString()) : ''
  320. formData.value['remark'] = data['remark'] != null ? data['remark'].toString() : ''
  321. formStatus.value = data['status'] != null ? data['status'].toString() : ''
  322. const pc = formData.value['productCode'] as string
  323. const pn = formData.value['productName'] as string
  324. const spec = formData.value['specification'] as string
  325. selectedProduct.value = {
  326. itemId: data['productId'] != null ? data['productId'].toString() : '',
  327. itemCode: pc,
  328. itemName: pn,
  329. itemTypeName: '',
  330. specification: spec,
  331. measureName: ''
  332. }
  333. }).catch((e) => {
  334. const error = e as UTSError
  335. const errMsg = error?.message
  336. uni.showToast({ title: errMsg.toString(), icon: 'none' })
  337. })
  338. }
  339. const buildSubmitData = (): UTSJSONObject => {
  340. const data = new UTSJSONObject()
  341. if (isEdit.value && reportId.value.length > 0) {
  342. data['reportId'] = parseInt(reportId.value)
  343. }
  344. data['productId'] = selectedProduct.value?.itemId ? parseInt(selectedProduct.value.itemId) : null
  345. data['productCode'] = formData.value['productCode']
  346. data['productName'] = formData.value['productName']
  347. data['specification'] = formData.value['specification']
  348. data['quantity'] = formData.value['quantity']
  349. data['unitName'] = '包'
  350. data['attr1'] = 'APP'
  351. const userInfo = getUserInfo()
  352. if (userInfo != null) {
  353. data['reporter'] = userInfo['nickName'] || userInfo['userName'] || userInfo['name'] || ''
  354. data['reporterId'] = userInfo['userId'] || userInfo['id'] || ''
  355. }
  356. const reportDate = formData.value['reportDate'] || currentDate.value
  357. data['reportDate'] = reportDate
  358. const startTime = formData.value['startTime']
  359. data['startTime'] = startTime ? `${reportDate} ${startTime}:00` : ''
  360. const endTime = formData.value['endTime']
  361. data['endTime'] = endTime ? `${reportDate} ${endTime}:00` : ''
  362. const remark = formData.value['remark']
  363. if (remark && remark.trim().length > 0) {
  364. data['remark'] = remark.trim()
  365. }
  366. return data
  367. }
  368. const handleSave = (): void => {
  369. if (selectedProduct.value == null) {
  370. uni.showToast({ title: '请选择产品', icon: 'none' })
  371. return
  372. }
  373. const quantity = formData.value['quantity']
  374. const quantityStr = String(quantity).trim()
  375. if (!quantityStr) {
  376. uni.showToast({ title: '请输入汇报数量', icon: 'none' })
  377. return
  378. }
  379. const num = Number(quantityStr)
  380. if (isNaN(num) || num <= 0) {
  381. uni.showToast({ title: '汇报数量必须大于0', icon: 'none' })
  382. return
  383. }
  384. const startTime = formData.value['startTime']
  385. if (!startTime || String(startTime).trim().length === 0) {
  386. uni.showToast({ title: '请选择开始时间', icon: 'none' })
  387. return
  388. }
  389. const endTime = formData.value['endTime']
  390. if (!endTime || String(endTime).trim().length === 0) {
  391. uni.showToast({ title: '请选择结束时间', icon: 'none' })
  392. return
  393. }
  394. const startStr = String(startTime).trim()
  395. const endStr = String(endTime).trim()
  396. if (startStr > endStr) {
  397. uni.showToast({ title: '开始时间不能大于结束时间', icon: 'none' })
  398. return
  399. }
  400. const data = buildSubmitData()
  401. const api = isEdit.value ? updateProReport : addProReport
  402. api(data).then((res: any) => {
  403. uni.showToast({ title: '保存成功', icon: 'success' })
  404. uni.setStorageSync('needRefresh', 'true')
  405. setTimeout(() => {
  406. uni.navigateBack()
  407. }, 1500)
  408. }).catch((e) => {
  409. const error = e as UTSError
  410. const errMsg = error?.message
  411. uni.showToast({ title: errMsg.toString(), icon: 'none' })
  412. })
  413. }
  414. const handleConfirm = (): void => {
  415. uni.showModal({
  416. title: '提示',
  417. content: '确认该生产汇报?',
  418. success: (res) => {
  419. if (res.confirm) {
  420. const data = new UTSJSONObject()
  421. data['reportId'] = parseInt(reportId.value)
  422. data['status'] = 'CONFIRMED'
  423. confirmProReport(data).then((res: any) => {
  424. uni.showToast({ title: '确认成功', icon: 'success' })
  425. uni.setStorageSync('needRefresh', 'true')
  426. setTimeout(() => {
  427. uni.navigateBack()
  428. }, 1500)
  429. }).catch((e) => {
  430. const error = e as UTSError
  431. const errMsg = error?.message
  432. uni.showToast({ title: errMsg.toString(), icon: 'none' })
  433. })
  434. }
  435. }
  436. })
  437. }
  438. const handleSaveAndConfirm = (): void => {
  439. if (selectedProduct.value == null) {
  440. uni.showToast({ title: '请选择产品', icon: 'none' })
  441. return
  442. }
  443. const quantity = formData.value['quantity']
  444. const quantityStr = String(quantity).trim()
  445. if (!quantityStr) {
  446. uni.showToast({ title: '请输入汇报数量', icon: 'none' })
  447. return
  448. }
  449. const num = Number(quantityStr)
  450. if (isNaN(num) || num <= 0) {
  451. uni.showToast({ title: '汇报数量必须大于0', icon: 'none' })
  452. return
  453. }
  454. const startTime = formData.value['startTime']
  455. if (!startTime || String(startTime).trim().length === 0) {
  456. uni.showToast({ title: '请选择开始时间', icon: 'none' })
  457. return
  458. }
  459. const endTime = formData.value['endTime']
  460. if (!endTime || String(endTime).trim().length === 0) {
  461. uni.showToast({ title: '请选择结束时间', icon: 'none' })
  462. return
  463. }
  464. const startStr = String(startTime).trim()
  465. const endStr = String(endTime).trim()
  466. if (startStr > endStr) {
  467. uni.showToast({ title: '开始时间不能大于结束时间', icon: 'none' })
  468. return
  469. }
  470. const data = buildSubmitData()
  471. data['status'] = 'CONFIRMED'
  472. addProReport(data).then((res: any) => {
  473. uni.showToast({ title: '保存并确认成功', icon: 'success' })
  474. uni.setStorageSync('needRefresh', 'true')
  475. setTimeout(() => {
  476. uni.navigateBack()
  477. }, 1500)
  478. }).catch((e) => {
  479. const error = e as UTSError
  480. const errMsg = error?.message
  481. uni.showToast({ title: errMsg.toString(), icon: 'none' })
  482. })
  483. }
  484. const handleDelete = (): void => {
  485. uni.showModal({
  486. title: '提示',
  487. content: '确定要删除该生产汇报吗?',
  488. success: (res) => {
  489. if (res.confirm) {
  490. delProReport(reportId.value).then((res: any) => {
  491. uni.showToast({ title: '删除成功', icon: 'success' })
  492. uni.setStorageSync('needRefresh', 'true')
  493. setTimeout(() => {
  494. uni.navigateBack()
  495. }, 1500)
  496. }).catch((e) => {
  497. const error = e as UTSError
  498. const errMsg = error?.message
  499. uni.showToast({ title: errMsg.toString(), icon: 'none' })
  500. })
  501. }
  502. }
  503. })
  504. }
  505. onLoad((options: any) => {
  506. const params = options as UTSJSONObject
  507. if (params != null && params['id'] != null) {
  508. isEdit.value = true
  509. reportId.value = params['id'].toString()
  510. loadDetail()
  511. } else {
  512. formData.value['reportDate'] = getTodayStr()
  513. formStatus.value = 'PREPARE'
  514. }
  515. })
  516. </script>
  517. <style lang="scss">
  518. .page-container {
  519. flex: 1;
  520. background-color: #e8f0f9;
  521. }
  522. .page-content {
  523. flex: 1;
  524. padding: 20rpx;
  525. padding-bottom: 130rpx;
  526. }
  527. .section {
  528. margin-bottom: 20rpx;
  529. background: #ffffff;
  530. border-radius: 16rpx;
  531. padding: 20rpx;
  532. }
  533. .section-header {
  534. flex-direction: row;
  535. align-items: center;
  536. margin-bottom: 20rpx;
  537. }
  538. .section-indicator {
  539. width: 6rpx;
  540. height: 32rpx;
  541. background-color: #007aff;
  542. border-radius: 3rpx;
  543. margin-right: 12rpx;
  544. }
  545. .section-title {
  546. font-size: 32rpx;
  547. color: #333333;
  548. font-weight: bold;
  549. }
  550. .form-card {
  551. background-color: #f8f9fa;
  552. border-radius: 8rpx;
  553. padding: 20rpx;
  554. }
  555. .form-row {
  556. flex-direction: row;
  557. align-items: center;
  558. margin-bottom: 20rpx;
  559. &:last-child {
  560. margin-bottom: 0;
  561. }
  562. }
  563. .form-label {
  564. width: 140rpx;
  565. font-size: 28rpx;
  566. color: #666666;
  567. white-space: nowrap;
  568. }
  569. .form-input {
  570. flex: 1;
  571. height: 60rpx;
  572. padding: 0 16rpx;
  573. background-color: #ffffff;
  574. border-radius: 8rpx;
  575. font-size: 28rpx;
  576. color: #333333;
  577. border: 1rpx solid #e5e5e5;
  578. }
  579. .form-input-static {
  580. flex: 1;
  581. height: 60rpx;
  582. padding: 0 16rpx;
  583. background-color: #f0f0f0;
  584. border-radius: 8rpx;
  585. font-size: 28rpx;
  586. color: #666666;
  587. line-height: 60rpx;
  588. border: 1rpx solid #e5e5e5;
  589. }
  590. .form-input.disabled {
  591. background-color: #f0f0f0;
  592. color: #666666;
  593. line-height: 60rpx;
  594. }
  595. .input-with-btn {
  596. flex: 1;
  597. display: flex;
  598. flex-direction: row;
  599. align-items: center;
  600. }
  601. .input-with-btn .form-input {
  602. flex: 1;
  603. height: 60rpx;
  604. border-radius: 8rpx 0 0 8rpx;
  605. border-right: none;
  606. }
  607. .picker-btn {
  608. width: 100rpx;
  609. height: 60rpx;
  610. line-height: 60rpx;
  611. text-align: center;
  612. background-color: #409eff;
  613. color: #ffffff;
  614. font-size: 26rpx;
  615. border-radius: 0 8rpx 8rpx 0;
  616. }
  617. .datetime-pickers {
  618. flex: 1;
  619. flex-direction: row;
  620. gap: 12rpx;
  621. }
  622. .picker-input {
  623. line-height: 60rpx;
  624. }
  625. .date-part {
  626. flex: 2;
  627. }
  628. .time-part {
  629. flex: 1;
  630. }
  631. .form-textarea {
  632. flex: 1;
  633. min-height: 120rpx;
  634. padding: 16rpx;
  635. background-color: #ffffff;
  636. border-radius: 8rpx;
  637. font-size: 28rpx;
  638. color: #333333;
  639. border: 1rpx solid #e5e5e5;
  640. }
  641. .bottom-buttons {
  642. position: fixed;
  643. bottom: 0;
  644. left: 0;
  645. right: 0;
  646. padding: 20rpx 30rpx;
  647. background-color: #ffffff;
  648. border-top: 1rpx solid #e5e5e5;
  649. display: flex;
  650. flex-direction: row;
  651. gap: 20rpx;
  652. }
  653. .save-btn {
  654. flex: 1;
  655. height: 80rpx;
  656. line-height: 80rpx;
  657. background-color: #ffffff;
  658. color: #007aff;
  659. font-size: 28rpx;
  660. font-weight: 600;
  661. border-radius: 20rpx;
  662. border: 2rpx solid #007aff;
  663. }
  664. .delete-btn {
  665. flex: 1;
  666. height: 80rpx;
  667. line-height: 80rpx;
  668. background-color: #ff4d4f;
  669. color: #ffffff;
  670. font-size: 28rpx;
  671. font-weight: 600;
  672. border-radius: 20rpx;
  673. border: none;
  674. }
  675. .confirm-btn {
  676. flex: 1;
  677. height: 80rpx;
  678. line-height: 80rpx;
  679. background-color: #007aff;
  680. color: #ffffff;
  681. font-size: 28rpx;
  682. font-weight: 600;
  683. border-radius: 20rpx;
  684. border: none;
  685. }
  686. .select-product-btn {
  687. padding: 30rpx;
  688. align-items: center;
  689. justify-content: center;
  690. border: 2rpx dashed #007aff;
  691. border-radius: 12rpx;
  692. background-color: #f0f7ff;
  693. }
  694. .select-product-text {
  695. font-size: 28rpx;
  696. color: #007aff;
  697. font-weight: 500;
  698. }
  699. .selected-product-card {
  700. flex-direction: row;
  701. align-items: center;
  702. justify-content: space-between;
  703. padding: 20rpx;
  704. background-color: #ffffff;
  705. border-radius: 8rpx;
  706. border: 1rpx solid #e5e5e5;
  707. }
  708. .selected-product-info {
  709. flex: 1;
  710. flex-direction: column;
  711. }
  712. .selected-product-name {
  713. font-size: 28rpx;
  714. color: #333333;
  715. font-weight: bold;
  716. }
  717. .selected-product-code {
  718. font-size: 24rpx;
  719. color: #666666;
  720. margin-top: 6rpx;
  721. }
  722. .selected-product-spec {
  723. font-size: 24rpx;
  724. color: #999999;
  725. margin-top: 4rpx;
  726. }
  727. .change-product-btn {
  728. padding: 8rpx 20rpx;
  729. background-color: #007aff;
  730. border-radius: 8rpx;
  731. margin-left: 16rpx;
  732. }
  733. .change-product-text {
  734. color: #ffffff;
  735. font-size: 24rpx;
  736. }
  737. /* Product picker modal styles */
  738. .picker-modal {
  739. position: fixed;
  740. top: 0;
  741. left: 0;
  742. right: 0;
  743. bottom: 0;
  744. z-index: 1000;
  745. display: flex;
  746. align-items: center;
  747. justify-content: center;
  748. }
  749. .modal-mask {
  750. position: absolute;
  751. top: 0;
  752. left: 0;
  753. right: 0;
  754. bottom: 0;
  755. background-color: rgba(0, 0, 0, 0.5);
  756. }
  757. .modal-content {
  758. position: relative;
  759. width: 90%;
  760. max-height: 80%;
  761. background-color: #ffffff;
  762. border-radius: 16rpx;
  763. padding: 30rpx;
  764. box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
  765. display: flex;
  766. flex-direction: column;
  767. }
  768. .modal-header {
  769. flex-direction: row;
  770. justify-content: space-between;
  771. align-items: center;
  772. margin-bottom: 20rpx;
  773. padding-bottom: 20rpx;
  774. border-bottom: 1rpx solid #e5e5e5;
  775. }
  776. .modal-title {
  777. font-size: 32rpx;
  778. font-weight: bold;
  779. color: #333333;
  780. }
  781. .modal-close {
  782. font-size: 28rpx;
  783. color: #999999;
  784. padding: 8rpx 16rpx;
  785. border-radius: 8rpx;
  786. background-color: #f5f5f5;
  787. }
  788. .search-bar {
  789. padding: 10rpx 0;
  790. }
  791. .search-box {
  792. flex-direction: row;
  793. align-items: center;
  794. height: 72rpx;
  795. padding: 0 24rpx;
  796. background-color: #f5f5f5;
  797. border-radius: 16rpx;
  798. }
  799. .search-input {
  800. flex: 1;
  801. font-size: 28rpx;
  802. color: #333333;
  803. }
  804. .search-btn {
  805. padding: 10rpx 20rpx;
  806. background-color: #007aff;
  807. border-radius: 8rpx;
  808. margin-left: 10rpx;
  809. }
  810. .search-btn-text {
  811. color: #ffffff;
  812. font-size: 24rpx;
  813. }
  814. .clear-btn {
  815. width: 36rpx;
  816. height: 36rpx;
  817. border-radius: 18rpx;
  818. background-color: #cccccc;
  819. align-items: center;
  820. justify-content: center;
  821. margin-left: 10rpx;
  822. }
  823. .clear-btn-text {
  824. color: #ffffff;
  825. font-size: 24rpx;
  826. font-weight: bold;
  827. }
  828. .product-list-scroll {
  829. max-height: 500rpx;
  830. flex: 1;
  831. }
  832. .list-item {
  833. margin: 3rpx;
  834. padding: 10rpx 10rpx 0rpx 10rpx;
  835. border-bottom: 1px solid #f9f9f9;
  836. }
  837. .item-header {
  838. flex-direction: row;
  839. align-items: flex-start;
  840. margin-bottom: 10rpx;
  841. }
  842. .item-info {
  843. flex: 1;
  844. min-width: 0;
  845. }
  846. .item-name-row {
  847. flex-wrap: wrap;
  848. align-items: flex-start;
  849. }
  850. .item-name {
  851. font-size: 28rpx;
  852. color: #333333;
  853. font-weight: bold;
  854. word-break: break-all;
  855. overflow: hidden;
  856. text-overflow: ellipsis;
  857. display: -webkit-box;
  858. -webkit-line-clamp: 2;
  859. -webkit-box-orient: vertical;
  860. }
  861. .item-sub-row {
  862. flex-direction: row;
  863. align-items: center;
  864. margin-top: 6rpx;
  865. }
  866. .item-sub-title {
  867. font-size: 23rpx;
  868. color: #797979;
  869. margin-right: 16rpx;
  870. }
  871. .item-code {
  872. font-size: 23rpx;
  873. color: #999999;
  874. }
  875. .add-btn {
  876. width: 44rpx;
  877. height: 44rpx;
  878. border-radius: 22rpx;
  879. background-color: #165DFF;
  880. color: #FFFFFF;
  881. align-items: center;
  882. justify-content: center;
  883. margin-left: 20rpx;
  884. flex-shrink: 0;
  885. }
  886. .add-icon {
  887. color: #ffffff;
  888. }
  889. .empty-tip {
  890. align-items: center;
  891. justify-content: center;
  892. padding: 40rpx;
  893. }
  894. .empty-tip-text {
  895. color: #999999;
  896. font-size: 28rpx;
  897. }
  898. .pagination {
  899. flex-direction: row;
  900. align-items: center;
  901. justify-content: center;
  902. padding: 20rpx;
  903. gap: 30rpx;
  904. }
  905. .page-btn {
  906. padding: 10rpx 20rpx;
  907. background-color: #165DFF;
  908. border-radius: 8rpx;
  909. }
  910. .page-btn.disabled {
  911. background-color: #cccccc;
  912. }
  913. .page-btn-text {
  914. color: #FFFFFF;
  915. font-size: 26rpx;
  916. }
  917. .page-info {
  918. font-size: 26rpx;
  919. color: #666666;
  920. }
  921. </style>