purchase-publish.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. <template>
  2. <view class="purchase-publish-container">
  3. <!-- 表单内容 -->
  4. <view class="form-container">
  5. <!-- 收购标题 -->
  6. <view class="form-item">
  7. <view class="item-label">
  8. <text class="label-text">收购标题</text>
  9. <text class="required">*</text>
  10. </view>
  11. <view class="item-content">
  12. <input class="form-input" v-model="formData.title" placeholder="请输入收购标题"
  13. placeholder-style="color: #999;" maxlength="30" @input="onTitleInput" />
  14. <view class="char-count">{{ titleLength }}/30</view>
  15. </view>
  16. </view>
  17. <!-- 收购品类 -->
  18. <view class="form-item">
  19. <view class="item-label">
  20. <text class="label-text">收购品类</text>
  21. <text class="required">*</text>
  22. </view>
  23. <view class="item-content">
  24. <view class="category-selector" @click="showCategoryPicker = true">
  25. <text class="selector-text" :class="{ placeholder: !formData.categoryId }">
  26. <!-- {{ formData.categoryLabel || '请选择产品分类' }} -->
  27. {{getDictLabel('agricultural_category',formData.categoryId) || '请选择产品分类' }}
  28. </text>
  29. <text class="arrow-icon">></text>
  30. </view>
  31. </view>
  32. </view>
  33. <view class="form-item">
  34. <!-- 所在地 -->
  35. <view class="item-label">
  36. <text class="label-text">所在地</text>
  37. <text class="required">*</text>
  38. </view>
  39. <LocationPicker v-model="formData.location" mode="edit" />
  40. </view>
  41. <!-- 收购数量 -->
  42. <view class="form-item">
  43. <view class="item-label">
  44. <text class="label-text">收购数量</text>
  45. <text class="required">*</text>
  46. </view>
  47. <view class="item-content">
  48. <view class="input-with-unit">
  49. <input class="form-input" v-model="formData.quantity" placeholder="请输入收购数量"
  50. placeholder-style="color: #999;" type="number" />
  51. <!-- <text class="unit-text">斤</text> -->
  52. </view>
  53. </view>
  54. </view>
  55. <!-- 单位-->
  56. <view class="form-item">
  57. <view class="item-label">
  58. <text class="label-text">单位</text>
  59. <text class="required">*</text>
  60. </view>
  61. <view class="item-content">
  62. <view class="category-selector" @click="showUnitPicker = true">
  63. <text class="selector-text" :class="{ placeholder: !formData.unit }">
  64. <!-- {{ formData.dictLabel || '请选择价格单位' }} -->
  65. {{ getDictLabel('agricultural_unit',formData.unit) || '请选择价格单位'}}
  66. </text>
  67. <text class="arrow-icon">></text>
  68. </view>
  69. </view>
  70. </view>
  71. <!-- 单价预算 -->
  72. <view class="form-item">
  73. <view class="item-label">
  74. <text class="label-text">单价预算</text>
  75. <text class="required">*</text>
  76. </view>
  77. <view class="item-content">
  78. <view class="input-with-unit">
  79. <text class="currency-symbol">¥</text>
  80. <input class="form-input" v-model="formData.price" placeholder="请输入单价预算"
  81. placeholder-style="color: #999;" type="digit" />
  82. <!-- <text class="unit-text">元/斤</text> -->
  83. </view>
  84. </view>
  85. </view>
  86. <!-- 补充说明 -->
  87. <view class="form-item">
  88. <view class="item-label">
  89. <text class="label-text">补充说明</text>
  90. </view>
  91. <view class="item-content">
  92. <textarea class="form-textarea" v-model="formData.description" placeholder="请描述交货要求、时间等补充信息"
  93. placeholder-style="color: #999;" maxlength="200" auto-height @input="onDescInput" />
  94. <view class="char-count">{{ descLength }}/200</view>
  95. </view>
  96. </view>
  97. <!-- 上传参考图片 -->
  98. <view class="form-item">
  99. <view class="item-label">
  100. <text class="label-text">参考图片</text>
  101. <text class="optional">(最多6张)</text>
  102. </view>
  103. <view class="item-content">
  104. <view class="image-upload-area">
  105. <view class="image-item" v-for="(image, index) in formData.images" :key="index">
  106. <image class="uploaded-image" :src="image.url" mode="aspectFill"></image>
  107. <view class="image-delete" @click="removeImage(index)">×</view>
  108. </view>
  109. <view class="image-upload-btn" v-if="formData.images.length < 6" @click="chooseImage">
  110. <text class="upload-icon">+</text>
  111. <text class="upload-text">上传图片</text>
  112. </view>
  113. </view>
  114. </view>
  115. </view>
  116. <!-- 联系人信息 -->
  117. <view class="form-item">
  118. <view class="item-label">
  119. <text class="label-text">联系人信息</text>
  120. </view>
  121. <view class="item-content">
  122. <view class="contact-input-row">
  123. <text class="contact-label">联系人:</text>
  124. <input class="contact-input" v-model="formData.contactName" placeholder="请输入联系人姓名"
  125. placeholder-style="color: #999;" maxlength="10" />
  126. </view>
  127. <view class="contact-input-row">
  128. <text class="contact-label">电话:</text>
  129. <input class="contact-input" v-model="formData.contactPhone" placeholder="请输入联系电话"
  130. placeholder-style="color: #999;" type="number" maxlength="11" />
  131. </view>
  132. </view>
  133. </view>
  134. </view>
  135. <!-- 底部提交按钮 -->
  136. <view class="bottom-action-bar">
  137. <view class="submit-btn" @click="submitForm">
  138. <text class="btn-text">{{ isEditMode ? '保存修改' : '提交发布' }}</text>
  139. </view>
  140. </view>
  141. <!-- 品类选择弹窗 -->
  142. <view class="picker-modal" v-if="showCategoryPicker" @click="showCategoryPicker = false">
  143. <view class="picker-content" @click.stop>
  144. <view class="picker-header">
  145. <text class="picker-title">选择收购品类</text>
  146. <text class="picker-close" @click="showCategoryPicker = false">×</text>
  147. </view>
  148. <view class="picker-options">
  149. <view class="picker-option"
  150. v-for="category in dictDataOptions.agricultural_category"
  151. :key="category.dictCode"
  152. @click="selectCategory(category)"
  153. :class="{ 'active': category.dictValue == formData.categoryId }">
  154. <text class="option-text">{{ category.dictLabel }}</text>
  155. </view>
  156. </view>
  157. </view>
  158. </view>
  159. <!-- 单位选择弹窗 -->
  160. <view class="picker-modal" v-if="showUnitPicker" @click="showUnitPicker = false">
  161. <view class="picker-content" @click.stop>
  162. <view class="picker-header">
  163. <text class="picker-title">选择价格单位</text>
  164. <text class="picker-close" @click="showUnitPicker = false">×</text>
  165. </view>
  166. <view class="picker-options">
  167. <view class="picker-option"
  168. v-for="unit in dictDataOptions.agricultural_unit"
  169. :key="unit.dictCode"
  170. @click="selectUnit(unit)"
  171. :class="{ 'active': unit.dictValue == formData.unit }">
  172. <text class="option-text">{{ unit.dictLabel }}</text>
  173. </view>
  174. </view>
  175. </view>
  176. </view>
  177. </view>
  178. </template>
  179. <script>
  180. import LocationPicker from "@/components/common/LocationPicker.vue"
  181. import api from "@/config/api.js";
  182. import storage from "@/utils/storage.js";
  183. import {
  184. addProductInfo,
  185. getProductInfoById,
  186. editProductInfo
  187. } from '@/api/services/productInfo.js';
  188. import dictMixin from '@/utils/mixins/dictMixin';
  189. import {
  190. getFormattedTime
  191. } from '@/utils/dateUtils'
  192. export default {
  193. mixins: [dictMixin],
  194. components: {
  195. LocationPicker
  196. },
  197. data() {
  198. return {
  199. dictTypeList: ['agricultural_unit', 'agricultural_category'],
  200. isEditMode: false, // 是否为编辑模式
  201. editItemId: '', // 编辑的收购信息ID
  202. formData: {
  203. title: '',
  204. categoryId: '',
  205. price: '',
  206. description: '',
  207. imageUrl: '',
  208. images: [],
  209. unit: '',
  210. quantity: '',
  211. contactName: '',
  212. contactPhone: '',
  213. location: '',
  214. publishTime: getFormattedTime(), // 发布时间
  215. userId: storage.getUserInfo().userid,
  216. type: 1, // 收购
  217. status: 1, // 审核中
  218. },
  219. // categoryOptions: ['蔬菜', '水果', '粮食', '其他'],
  220. dictDataOptions: [],
  221. showCategoryPicker: false,
  222. showUnitPicker: false,
  223. titleLength: 0,
  224. descLength: 0
  225. }
  226. },
  227. onLoad(options) {
  228. this.dictDataOptions = this.dictData
  229. // 检查是否为编辑模式
  230. if (options.action === 'edit' && options.id) {
  231. this.isEditMode = true;
  232. this.editItemId = options.id;
  233. // 设置页面标题
  234. uni.setNavigationBarTitle({
  235. title: '编辑收购信息'
  236. });
  237. // 加载收购信息数据
  238. this.loadPurchaseData();
  239. } else {
  240. // 新建模式
  241. this.isEditMode = false;
  242. uni.setNavigationBarTitle({
  243. title: '发布收购信息'
  244. });
  245. }
  246. },
  247. methods: {
  248. // 加载收购信息数据(编辑模式)
  249. loadPurchaseData() {
  250. // 模拟从API加载数据,实际应用中需要根据ID调用相应的API
  251. uni.showLoading({
  252. title: '加载中...'
  253. });
  254. getProductInfoById(this.editItemId).then(res=>{
  255. if (res.data.code === 200) {
  256. // const { data } = res.data;
  257. // this.formData = data
  258. Object.assign(this.formData, res.data.data) // 保持响应式
  259. // 更新字符计数
  260. this.nameLength = this.formData.title.length;
  261. this.descLength = this.formData.description.length;
  262. console.log("this.goodsDetail", this.formData);
  263. // 处理图片数据
  264. if (this.formData.imageUrl) {
  265. try {
  266. this.formData.images = this.formData.imageUrl.split(',').map(url => ({ url,status: 'success' }))
  267. console.log('解析后的图片数据:', this.formData.images);
  268. } catch (e) {
  269. console.error('解析图片数据失败:', e);
  270. this.formData.images = [];
  271. }
  272. } else {
  273. this.formData.images = [];
  274. }
  275. uni.hideLoading();
  276. } else {
  277. uni.showToast({
  278. title: res.data.msg || '获取农品信息失败',
  279. icon: 'none'
  280. });
  281. }
  282. })
  283. },
  284. // 标题输入处理
  285. onTitleInput(e) {
  286. this.titleLength = e.detail.value.length;
  287. },
  288. // 描述输入处理
  289. onDescInput(e) {
  290. this.descLength = e.detail.value.length;
  291. },
  292. // 选择分类
  293. selectCategory(category) {
  294. this.formData.categoryId = category.dictValue;
  295. this.formData.categoryLabel = category.dictLabel;
  296. this.showCategoryPicker = false;
  297. },
  298. // 选择单位
  299. selectUnit(unit) {
  300. this.formData.unit = unit.dictValue;
  301. this.formData.dictLabel = unit.dictLabel;
  302. this.showUnitPicker = false;
  303. },
  304. getDictLabel(dictKey, value) {
  305. if (!this.dictData || !this.dictData[dictKey]) {
  306. return ''
  307. }
  308. const list = this.dictData[dictKey] || []
  309. const item = list.find(u => u.dictValue == value)
  310. return item ? item.dictLabel : ''
  311. },
  312. // 选择图片
  313. chooseImage() {
  314. uni.chooseImage({
  315. count: 6 - this.formData.images.length,
  316. sizeType: ['original', 'compressed'],
  317. sourceType: ['album', 'camera'],
  318. success: (res) => {
  319. console.log('选择图片成功:', res);
  320. // 验证文件类型和大小
  321. const validFiles = [];
  322. const invalidFiles = [];
  323. const maxSize = 5 * 1024 * 1024; // 5MB 最大限制
  324. // 检查每个文件
  325. res.tempFiles.forEach((file, index) => {
  326. // 检查文件类型
  327. const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name);
  328. // 检查文件大小
  329. const isValidSize = file.size <= maxSize;
  330. if (isImage && isValidSize) {
  331. validFiles.push(res.tempFilePaths[index]);
  332. } else {
  333. invalidFiles.push({
  334. path: file.path,
  335. size: file.size,
  336. reason: !isImage ? '文件格式不支持' : '文件大于5MB'
  337. });
  338. }
  339. });
  340. // 显示无效文件提示
  341. if (invalidFiles.length > 0) {
  342. uni.showToast({
  343. title: `${invalidFiles.length}个文件无效,请检查格式和大小`,
  344. icon: 'none',
  345. duration: 2000
  346. });
  347. }
  348. // 如果有有效文件,则上传
  349. if (validFiles.length > 0) {
  350. this.uploadImages(validFiles);
  351. }
  352. },
  353. fail: (err) => {
  354. console.error('选择图片失败:', err);
  355. uni.showToast({
  356. title: '选择图片失败',
  357. icon: 'none'
  358. });
  359. }
  360. });
  361. },
  362. // 上传图片到服务器
  363. uploadImages(tempFilePaths) {
  364. uni.showLoading({
  365. title: '上传中...',
  366. mask: true
  367. });
  368. // 上传成功的图片计数
  369. let successCount = 0;
  370. let failCount = 0;
  371. const totalFiles = tempFilePaths.length;
  372. const newImages = [];
  373. // 遍历处理每张图片
  374. tempFilePaths.forEach((path, index) => {
  375. // 调用上传API
  376. uni.uploadFile({
  377. // url: api.serve + '/base/tasks/uploadTaskImage',
  378. url: api.serve + '/file/upload',
  379. filePath: path,
  380. name: 'file', // 文件参数名称,需要与后端接口匹配
  381. formData: {
  382. type: 'task', // 标识文件类型,用于后端区分不同业务的文件
  383. // directory: '/opt/app/nongxiaoyu/uploadImage' // 指定保存目录
  384. },
  385. header: {
  386. 'Authorization': `Bearer ${storage.getAccessToken()}`
  387. },
  388. success: (res) => {
  389. try {
  390. const response = JSON.parse(res.data);
  391. uni.showToast({
  392. title: `返回: ${response.data}`,
  393. icon: 'none',
  394. });
  395. if (response.code === 200) {
  396. // 获取返回的URL
  397. const imageUrl = response.data.url;
  398. console.log('上传成功,返回的图片URL:', imageUrl);
  399. // 上传成功,将图片信息添加到数组
  400. newImages.push({
  401. url: imageUrl, // 保存原始URL,在显示时会通过getImageUrl方法处理
  402. path: path, // 保存本地路径用于预览
  403. status: 'success',
  404. fileName: response.data.fileName || '' // 保存文件名,如果后端返回的话
  405. });
  406. successCount++;
  407. } else {
  408. failCount++;
  409. console.error('上传失败:', response.msg);
  410. }
  411. } catch (e) {
  412. failCount++;
  413. uni.showToast({
  414. title: `解析响应失败: ${e}`,
  415. icon: 'none',
  416. });
  417. console.error('解析响应失败:', e);
  418. }
  419. },
  420. fail: (err) => {
  421. failCount++;
  422. console.error('上传请求失败:', err);
  423. uni.showToast({
  424. title: `上传请求失败: ${err}`,
  425. icon: 'none',
  426. });
  427. },
  428. complete: () => {
  429. // 当所有文件都已处理完成
  430. if (successCount + failCount === totalFiles) {
  431. if (newImages.length > 0) {
  432. // 将新上传的图片添加到已有图片列表
  433. this.formData.images = [...this.formData.images, ...newImages];
  434. // 更新taskImages字段,将图片URL用逗号连接
  435. this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
  436. this.$set(this.formData, 'images', this.formData.images) // 确保视图刷新
  437. // 显示成功提示
  438. uni.hideLoading();
  439. uni.showToast({
  440. title: `成功上传${successCount}张图片`,
  441. icon: 'success'
  442. });
  443. } else {
  444. // 全部失败
  445. uni.showToast({
  446. title: `图片上传: ${newImages}`,
  447. icon: 'none',
  448. });
  449. uni.hideLoading();
  450. // uni.showToast({
  451. // title: '图片上传失败',
  452. // icon: 'none',
  453. // });
  454. }
  455. }
  456. }
  457. });
  458. });
  459. },
  460. removeImage(index) {
  461. uni.showModal({
  462. title: '确认删除',
  463. content: '确定要删除这张图片吗?',
  464. success: (res) => {
  465. if (res.confirm) {
  466. this.formData.images.splice(index, 1);
  467. // 更新taskImages字段
  468. this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
  469. this.$set(this.formData, 'images', this.formData.images) // 确保视图刷新
  470. uni.showToast({
  471. title: '已删除',
  472. icon: 'none'
  473. });
  474. }
  475. }
  476. });
  477. },
  478. // 表单验证
  479. validateForm() {
  480. if (!this.formData.title.trim()) {
  481. uni.showToast({
  482. title: '请输入收购标题',
  483. icon: 'none'
  484. });
  485. return false;
  486. }
  487. if (!this.formData.categoryId) {
  488. uni.showToast({
  489. title: '请选择收购品类',
  490. icon: 'none'
  491. });
  492. return false;
  493. }
  494. if (!this.formData.quantity || this.formData.quantity <= 0) {
  495. uni.showToast({
  496. title: '请输入有效的收购数量',
  497. icon: 'none'
  498. });
  499. return false;
  500. }
  501. if (!this.formData.price || this.formData.price <= 0) {
  502. uni.showToast({
  503. title: '请输入有效的单价预算',
  504. icon: 'none'
  505. });
  506. return false;
  507. }
  508. if (!this.formData.contactName.trim()) {
  509. uni.showToast({
  510. title: '请输入联系人姓名',
  511. icon: 'none'
  512. });
  513. return false;
  514. }
  515. if (!this.formData.contactPhone.trim()) {
  516. uni.showToast({
  517. title: '请输入联系电话',
  518. icon: 'none'
  519. });
  520. return false;
  521. }
  522. // 简单的手机号格式验证
  523. const phoneRegex = /^1[3-9]\d{9}$/;
  524. if (!phoneRegex.test(this.formData.contactPhone)) {
  525. uni.showToast({
  526. title: '请输入正确的手机号码',
  527. icon: 'none'
  528. });
  529. return false;
  530. }
  531. return true;
  532. },
  533. // 提交表单
  534. submitForm() {
  535. if (!this.validateForm()) {
  536. return;
  537. }
  538. // 显示加载提示
  539. const loadingTitle = this.isEditMode ? '保存中...' : '提交中...';
  540. uni.showLoading({
  541. title: loadingTitle
  542. });
  543. uni.hideLoading();
  544. if (this.isEditMode) {
  545. editProductInfo(this.formData).then(res=>{
  546. if(res.data.code === 200){
  547. uni.showModal({
  548. title: '保存成功',
  549. content: '您的修改已保存成功!',
  550. showCancel: false,
  551. success: () => {
  552. uni.navigateBack();
  553. }
  554. });
  555. }else{
  556. uni.showToast({
  557. title: res.data.msg || '提交失败,请稍后重试',
  558. icon: 'none'
  559. });
  560. }
  561. }).catch(err => {
  562. console.error("提交异常:", err);
  563. uni.showToast({
  564. title: '网络错误,请检查后重试',
  565. icon: 'none'
  566. });
  567. });
  568. } else {
  569. // 新建模式 - 提交审核
  570. console.log("this.formData",this.formData);
  571. // const data = {
  572. // userId: this.currentUserInfo.userid,
  573. // type: 0 ,// 出售
  574. // status: 1 ,// 审核中
  575. // }
  576. // this.formData = {...this.formData , ...data}
  577. addProductInfo(this.formData).then(res=>{
  578. console.log("新增出售农产品",res);
  579. if(res.data.code === 200){
  580. uni.showModal({
  581. title: '提交成功',
  582. content: '您的收购农产品信息已提交审核,审核通过后将在收购页面展示。',
  583. showCancel: false,
  584. success: () => {
  585. uni.navigateBack();
  586. }
  587. });
  588. }else{
  589. uni.showToast({
  590. title: res.data.msg || '提交失败,请稍后重试',
  591. icon: 'none'
  592. });
  593. }
  594. }).catch(err => {
  595. console.error("提交异常:", err);
  596. uni.showToast({
  597. title: '网络错误,请检查后重试',
  598. icon: 'none'
  599. });
  600. });
  601. }
  602. }
  603. }
  604. }
  605. </script>
  606. <style lang="scss">
  607. .purchase-publish-container {
  608. min-height: 100vh;
  609. background-color: #f5f5f5;
  610. padding-bottom: calc(128rpx + env(safe-area-inset-bottom));
  611. }
  612. .form-container {
  613. padding: 20rpx;
  614. }
  615. .form-item {
  616. background-color: #fff;
  617. border-radius: 12rpx;
  618. padding: 24rpx;
  619. margin-bottom: 16rpx;
  620. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
  621. }
  622. .item-label {
  623. display: flex;
  624. align-items: center;
  625. margin-bottom: 16rpx;
  626. }
  627. .label-text {
  628. font-size: 28rpx;
  629. color: #333;
  630. font-weight: bold;
  631. }
  632. .required {
  633. color: #ff4d4f;
  634. font-size: 28rpx;
  635. margin-left: 4rpx;
  636. }
  637. .optional {
  638. color: #999;
  639. font-size: 24rpx;
  640. margin-left: 8rpx;
  641. }
  642. .item-content {
  643. position: relative;
  644. }
  645. .form-input {
  646. width: 100%;
  647. height: 44rpx;
  648. font-size: 28rpx;
  649. color: #333;
  650. line-height: 44rpx;
  651. }
  652. .form-textarea {
  653. width: 100%;
  654. min-height: 120rpx;
  655. font-size: 28rpx;
  656. color: #333;
  657. line-height: 1.5;
  658. }
  659. .char-count {
  660. position: absolute;
  661. right: 0;
  662. bottom: -4rpx;
  663. font-size: 24rpx;
  664. color: #999;
  665. }
  666. // 品类选择器
  667. .category-selector {
  668. display: flex;
  669. align-items: center;
  670. justify-content: space-between;
  671. padding: 12rpx 0;
  672. border-bottom: 1rpx solid #f0f0f0;
  673. }
  674. .selector-text {
  675. font-size: 28rpx;
  676. color: #333;
  677. &.placeholder {
  678. color: #999;
  679. }
  680. }
  681. .arrow-icon {
  682. font-size: 24rpx;
  683. color: #999;
  684. }
  685. // 带单位的输入框
  686. .input-with-unit {
  687. display: flex;
  688. align-items: center;
  689. border-bottom: 1rpx solid #f0f0f0;
  690. padding: 12rpx 0;
  691. }
  692. .currency-symbol {
  693. font-size: 28rpx;
  694. color: #333;
  695. margin-right: 8rpx;
  696. }
  697. .unit-text {
  698. font-size: 28rpx;
  699. color: #666;
  700. margin-left: 8rpx;
  701. white-space: nowrap;
  702. }
  703. // 图片上传
  704. .image-upload-area {
  705. display: flex;
  706. flex-wrap: wrap;
  707. gap: 16rpx;
  708. }
  709. .image-item {
  710. position: relative;
  711. width: 160rpx;
  712. height: 160rpx;
  713. border-radius: 8rpx;
  714. overflow: hidden;
  715. }
  716. .uploaded-image {
  717. width: 100%;
  718. height: 100%;
  719. }
  720. .image-delete {
  721. position: absolute;
  722. top: -8rpx;
  723. right: -8rpx;
  724. width: 32rpx;
  725. height: 32rpx;
  726. background-color: #ff4d4f;
  727. color: #fff;
  728. border-radius: 50%;
  729. display: flex;
  730. align-items: center;
  731. justify-content: center;
  732. font-size: 20rpx;
  733. font-weight: bold;
  734. }
  735. .image-upload-btn {
  736. width: 160rpx;
  737. height: 160rpx;
  738. border: 2rpx dashed #ddd;
  739. border-radius: 8rpx;
  740. display: flex;
  741. flex-direction: column;
  742. align-items: center;
  743. justify-content: center;
  744. background-color: #fafafa;
  745. }
  746. .upload-icon {
  747. font-size: 48rpx;
  748. color: #999;
  749. margin-bottom: 8rpx;
  750. }
  751. .upload-text {
  752. font-size: 24rpx;
  753. color: #999;
  754. }
  755. // 联系人信息
  756. .contact-input-row {
  757. display: flex;
  758. align-items: center;
  759. margin-bottom: 20rpx;
  760. &:last-child {
  761. margin-bottom: 0;
  762. }
  763. }
  764. .contact-label {
  765. font-size: 28rpx;
  766. color: #333;
  767. width: 120rpx;
  768. font-weight: bold;
  769. }
  770. .contact-input {
  771. flex: 1;
  772. height: 44rpx;
  773. font-size: 28rpx;
  774. color: #333;
  775. line-height: 44rpx;
  776. border-bottom: 1rpx solid #f0f0f0;
  777. padding: 12rpx 0;
  778. }
  779. // 底部提交按钮
  780. .bottom-action-bar {
  781. position: fixed;
  782. bottom: 0;
  783. left: 0;
  784. right: 0;
  785. width: 100%;
  786. background-color: #fff;
  787. border-top: 1rpx solid #f0f0f0;
  788. padding: 20rpx 30rpx;
  789. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  790. z-index: 1000;
  791. box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
  792. box-sizing: border-box;
  793. }
  794. .submit-btn {
  795. width: 100%;
  796. height: 88rpx;
  797. background-color: #4CAF50;
  798. border-radius: 12rpx;
  799. display: flex;
  800. align-items: center;
  801. justify-content: center;
  802. transition: background-color 0.2s ease;
  803. box-sizing: border-box;
  804. &:active {
  805. background-color: #45a049;
  806. }
  807. .btn-text {
  808. font-size: 32rpx;
  809. color: #fff;
  810. font-weight: bold;
  811. }
  812. }
  813. // 品类选择弹窗
  814. .picker-modal {
  815. position: fixed;
  816. top: 0;
  817. left: 0;
  818. width: 100%;
  819. height: 100%;
  820. background-color: rgba(0, 0, 0, 0.5);
  821. z-index: 2000;
  822. display: flex;
  823. align-items: flex-end;
  824. }
  825. .picker-content {
  826. width: 100%;
  827. background-color: #fff;
  828. border-radius: 24rpx 24rpx 0 0;
  829. padding: 0;
  830. }
  831. .picker-header {
  832. display: flex;
  833. justify-content: space-between;
  834. align-items: center;
  835. padding: 30rpx;
  836. border-bottom: 1rpx solid #f0f0f0;
  837. }
  838. .picker-title {
  839. font-size: 32rpx;
  840. font-weight: bold;
  841. color: #333;
  842. }
  843. .picker-close {
  844. font-size: 36rpx;
  845. color: #999;
  846. }
  847. .picker-options {
  848. padding: 20rpx 0;
  849. }
  850. .picker-option {
  851. padding: 24rpx 30rpx;
  852. border-bottom: 1rpx solid #f8f8f8;
  853. transition: background-color 0.2s ease;
  854. &:last-child {
  855. border-bottom: none;
  856. }
  857. &:active {
  858. background-color: #f5f5f5;
  859. }
  860. }
  861. .option-text {
  862. font-size: 30rpx;
  863. color: #333;
  864. }
  865. </style>