detail.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <template>
  2. <view class="detail" @click="handleClickOutside">
  3. <u-navbar class="nav_bar" title="商品详情档案" :autoBack="true" :placeholder="true" rightIcon="more-dot-fill"
  4. v-hideNav></u-navbar>
  5. <u-swiper :list="imgsUrl" keyName="url" indicator indicatorMode="line" circular>
  6. <!-- <template v-slot:item="{ item, index }">
  7. <view style="width: 100%; height: 600rpx;">
  8. <image :src="item.url" mode="aspectFill" style="width: 100%; height: 100%; object-fit: cover;"></image>
  9. </view>
  10. </template> -->
  11. </u-swiper>
  12. <!-- 立即下架按钮 -->
  13. <view class="immediate_off_shelf">
  14. <u-button type="error" shape="circle" size="large" @click="handleOffShelf">
  15. <u-icon :name="downStatus == '0' ? 'arrow-upward' : 'arrow-downward'" size="38rpx"
  16. color="#ffffff"></u-icon>
  17. <text>{{ downStatus == '0' ? '立即上架' : '立即下架' }}</text>
  18. </u-button>
  19. </view>
  20. <!-- 轮播图下方操作按钮 -->
  21. <view class="swiper_actions">
  22. <view class="action_item" @click="handleDownload">
  23. <u-icon name="download" size="44rpx" color="#606266"></u-icon>
  24. <text class="action_text">下载图文</text>
  25. </view>
  26. <view class="action_item">
  27. <u-icon name="share" size="44rpx" color="#606266"></u-icon>
  28. <text class="action_text">分享</text>
  29. </view>
  30. <view class="action_item" @click="handleEditImgs">
  31. <u-icon name="camera" size="44rpx" color="#606266"></u-icon>
  32. <text class="action_text">编辑图片</text>
  33. <u-modal :show="showEditImgsModal" title="编辑图片" showCancelButton @confirm="confirmEditImgs" @cancel="cancelEditImgs">
  34. <imgs-row-scroll v-if="tempImgsUrl.length > 0" :isShowDeleteIcon="true" keyName="url"
  35. @deleteImgInfo="getDeleteImgInfo" imgMode="aspectFill" :totalWidth="400" :images="tempImgsUrl"
  36. :previewEnabled="true" :imageWidth="150" :imageHeight="150"></imgs-row-scroll>
  37. <u-upload @afterRead="afterRead" multiple></u-upload>
  38. </u-modal>
  39. </view>
  40. <view class="action_item">
  41. <u-icon name="cut" size="44rpx" color="#606266"></u-icon>
  42. <text class="action_text">智能抠图</text>
  43. </view>
  44. </view>
  45. <!-- 核心信息区域 -->
  46. <view class="core_info">
  47. <view class="section_title">核心信息</view>
  48. <view class="info_row">
  49. <view class="info_label">品牌</view>
  50. <view class="info_value" @click.stop="showBrandlList()">
  51. <!-- <u-input v-if="editMode.dictLabel" v-model="tempValues.dictLabel" :autoFocus="true" @blur="!globalEditMode && saveEditField('dictLabel', tempValues.dictLabel, 'dictLabel')" /> -->
  52. <span>{{ coreInfo.dictLabel || '-' }}</span>
  53. <BrandList ref="brandListRef" @selectedBrand="handleSelectedBrand"></BrandList>
  54. </view>
  55. </view>
  56. <view class="info_row">
  57. <view class="info_label">型号</view>
  58. <view class="info_value" @click.stop="toggleEditField('model', coreInfo.model)">
  59. <u-input v-if="editMode.model" v-model="tempValues.model" :autoFocus="true"
  60. @blur="!globalEditMode && saveEditField('model', tempValues.model, 'model')" />
  61. <span v-else>{{ coreInfo.model || '-' }}</span>
  62. </view>
  63. </view>
  64. <view class="info_row">
  65. <view class="info_label">独立编码</view>
  66. <view class="info_value code" @click.stop="toggleEditField('code', coreInfo.code)">
  67. <u-input v-if="editMode.code" v-model="tempValues.code" :autoFocus="true"
  68. @blur="!globalEditMode && saveEditField('code', tempValues.code, 'code')" />
  69. <span v-else>{{ coreInfo.code || '-' }}</span>
  70. </view>
  71. </view>
  72. <view class="info_row">
  73. <view class="info_label">入库日期</view>
  74. <view class="info_value">{{ coreInfo.warehouseDate || '-' }}</view>
  75. </view>
  76. <view class="info_row">
  77. <view class="info_label">付款方式</view>
  78. <view class="info_value">{{ coreInfo.payType || '-' }}</view>
  79. </view>
  80. <view class="info_row col">
  81. <view class="info_label">备注信息</view>
  82. <view class="info_value note" @click.stop="toggleEditField('note', coreInfo.note)">
  83. <textarea v-if="editMode.note" v-model="tempValues.note" :autoFocus="true" rows="3"
  84. @blur="!globalEditMode && saveEditField('note', tempValues.note, 'note')" />
  85. <span v-else>{{ coreInfo.note || '-' }}</span>
  86. </view>
  87. </view>
  88. </view>
  89. <!-- 财务与价格区域 -->
  90. <view class="finance_price">
  91. <view class="section_title">财务与价格</view>
  92. <view class="price_group">
  93. <view class="price_item">
  94. <view class="price_label">原始成本</view>
  95. <view class="price_value original"
  96. @click.stop="toggleEditField('originalCost', coreInfo.originalCost)">
  97. <u-input v-if="editMode.originalCost" v-model="tempValues.originalCost" :autoFocus="true"
  98. @blur="!globalEditMode && saveEditField('originalCost', tempValues.originalCost, 'originalCost')" />
  99. <span v-else>¥{{ coreInfo.originalCost || '-' }}</span>
  100. </view>
  101. </view>
  102. <view class="price_item">
  103. <view class="price_label">附加成本</view>
  104. <view class="price_value additional"
  105. @click.stop="toggleEditField('additionalCost', coreInfo.additionalCost)">
  106. <u-input v-if="editMode.additionalCost" v-model="tempValues.additionalCost" :autoFocus="true"
  107. @blur="!globalEditMode && saveEditField('additionalCost', tempValues.additionalCost, 'additionalCost')" />
  108. <span v-else>¥{{ coreInfo.additionalCost || '-' }}</span>
  109. </view>
  110. </view>
  111. </view>
  112. <view class="price_group">
  113. <view class="price_item">
  114. <view class="price_label">代理价格</view>
  115. <view class="price_value agent" @click.stop="toggleEditField('agentPrice', coreInfo.agentPrice)">
  116. <u-input v-if="editMode.agentPrice" v-model="tempValues.agentPrice" :autoFocus="true"
  117. @blur="!globalEditMode && saveEditField('agentPrice', tempValues.agentPrice, 'agentPrice')" />
  118. <span v-else>¥{{ coreInfo.agentPrice || '-' }}</span>
  119. </view>
  120. </view>
  121. <view class="price_item">
  122. <view class="price_label">建议售价</view>
  123. <view class="price_value suggested"
  124. @click.stop="toggleEditField('suggestedPrice', coreInfo.suggestedPrice)">
  125. <u-input v-if="editMode.suggestedPrice" v-model="tempValues.suggestedPrice" :autoFocus="true"
  126. @blur="!globalEditMode && saveEditField('suggestedPrice', tempValues.suggestedPrice, 'suggestedPrice')" />
  127. <span v-else>¥{{ coreInfo.suggestedPrice || '-' }}</span>
  128. </view>
  129. </view>
  130. </view>
  131. </view>
  132. <!-- 溯源与位置区域 -->
  133. <view class="traceability_location">
  134. <view class="section_title">溯源与位置</view>
  135. <view class="info_row">
  136. <view class="info_label">商品位置</view>
  137. <view class="info_value" @click.stop="toggleEditField('location', coreInfo.location)">
  138. <u-input v-if="editMode.location" v-model="tempValues.location" :autoFocus="true"
  139. @blur="!globalEditMode && saveEditField('location', tempValues.location, 'location')" />
  140. <span v-else>{{ coreInfo.location || '-' }}</span>
  141. </view>
  142. </view>
  143. <view class="info_row">
  144. <view class="info_label">回收人员</view>
  145. <view class="info_value" @click.stop="showRecyclePersonPicker">
  146. <!-- <u-input v-if="editMode.recyclePerson" v-model="tempValues.recyclePerson" :autoFocus="true" @blur="!globalEditMode && saveEditField('recyclePerson', tempValues.recyclePerson, 'recyclePerson')" /> -->
  147. <span>{{ coreInfo.recyclePerson || '-' }}</span>
  148. <personPicker ref="recyclePersonPickerRef" title="请选择回收人员"
  149. @selectPerson="handleSelectRecyclePerson">
  150. </personPicker>
  151. </view>
  152. </view>
  153. <view class="info_row">
  154. <view class="info_label">鉴定人员</view>
  155. <view class="info_value" @click.stop="showIdentifyingPersonPicker">
  156. <!-- <u-input v-if="editMode.identifyingPerson" v-model="tempValues.identifyingPerson" :autoFocus="true" @blur="!globalEditMode && saveEditField('identifyingPerson', tempValues.identifyingPerson, 'identifyingPerson')" /> -->
  157. <span>{{ coreInfo.identifyingPerson || '-' }}</span>
  158. <personPicker ref="identifyingPersonPickerRef" title="请选择鉴定人员"
  159. @selectPerson="handleSelectIdentifyingPerson"></personPicker>
  160. </view>
  161. </view>
  162. <view class="action_button">
  163. <u-button type="primary" plain size="mini" @click="viewLog">查看操作日志({{ coreInfo.logTotal ||
  164. '-'}}条)</u-button>
  165. <u-modal :show="logShow" title="日志" :closeOnClickOverlay="true" :showConfirmButton="false"
  166. @close="logShow = false">
  167. <view class="log_list">
  168. <view v-for="value in logList" :key="value.id" class="log_item">
  169. {{ value.createTime }}{{ value.name }}{{ value.operation }}
  170. </view>
  171. </view>
  172. </u-modal>
  173. </view>
  174. </view>
  175. <!-- 底部功能按钮栏 -->
  176. <view class="bottom_bar">
  177. <view class="bar_item" @click.stop="globalEditMode ? handleClickOutside() : enterGlobalEdit()">
  178. <u-icon :name="globalEditMode ? 'checkbox-mark' : 'edit-pen'" size="46rpx"
  179. :color="globalEditMode ? '#10b981' : '#9ca3af'"></u-icon>
  180. <text class="bar_text">{{ globalEditMode ? '确定' : '编辑' }}</text>
  181. </view>
  182. <view class="bar_item" @click.stop="lockGoods">
  183. <u-icon :name="lockStatus === '1' ? 'lock-open' : 'lock'" size="46rpx" color="#9ca3af"></u-icon>
  184. <text class="bar_text">{{ lockStatus === '1' ? '解锁' : '锁单' }}</text>
  185. </view>
  186. <view class="bar_item">
  187. <image class="bar_icon" src="../../../static//icons/print.png" alt=""></image>
  188. <text class="bar_text">打印</text>
  189. </view>
  190. <view :class="['bar_item', { 'disabled': lockStatus === '1' }]" @click="handleOpenOrder">
  191. <view class="primary">
  192. <u-icon name="bag" size="44rpx" color="#fff"></u-icon>
  193. <text class="bar_text">开单</text>
  194. </view>
  195. </view>
  196. </view>
  197. </view>
  198. </template>
  199. <script>
  200. import BrandList from '@/components/brand-list/index.vue'
  201. import personPicker from '@/components/person-picker/index.vue'
  202. import imgsRowScroll from '@/components/imgs-row-scroll/index.vue'
  203. export default {
  204. components: {
  205. BrandList,
  206. personPicker,
  207. imgsRowScroll,
  208. },
  209. data() {
  210. return {
  211. // 核心信息模拟数据
  212. coreInfo: {},
  213. // 编辑状态管理
  214. editMode: {
  215. model: false,
  216. code: false,
  217. note: false,
  218. originalCost: false,
  219. additionalCost: false,
  220. agentPrice: false,
  221. suggestedPrice: false,
  222. location: false,
  223. },
  224. // 全局编辑模式
  225. globalEditMode: false,
  226. // 临时存储编辑值
  227. tempValues: {},
  228. goodsId: '',
  229. logShow: false,
  230. logList: [],
  231. imgsUrl: [],
  232. lockStatus: '',//锁单状态 0:未锁单 1:已锁单
  233. downStatus: '',//下架状态 0:已下架 1:已上架
  234. showEditImgsModal: false,
  235. tempImgsUrl: [],
  236. }
  237. },
  238. onLoad(options) {
  239. this.goodsId = options.id;
  240. this.getGoodsDetail();
  241. },
  242. methods: {
  243. // 锁单
  244. lockGoods() {
  245. uni.$u.api.wareHouseLock({
  246. id: this.goodsId,
  247. lockStatus: this.lockStatus === '1' ? '0' : '1',
  248. }).then(res => {
  249. uni.showToast({
  250. title: this.lockStatus === '1' ? '解锁成功' : '锁单成功',
  251. icon: 'success'
  252. })
  253. this.getGoodsDetail();
  254. })
  255. },
  256. showRecyclePersonPicker() {
  257. this.$refs.recyclePersonPickerRef.open();
  258. },
  259. // 处理选中回收人员
  260. handleSelectRecyclePerson(person) {
  261. this.coreInfo.recyclePerson = person.label;
  262. this.coreInfo.recyclePersonId = person.id;
  263. this.submitEdit(this.coreInfo)
  264. },
  265. // 显示鉴定人员选择器
  266. showIdentifyingPersonPicker() {
  267. this.$refs.identifyingPersonPickerRef.open();
  268. },
  269. // 处理选中鉴定人员
  270. handleSelectIdentifyingPerson(person) {
  271. this.coreInfo.identifyingPerson = person.label;
  272. this.coreInfo.identifyingPersonId = person.id;
  273. this.submitEdit(this.coreInfo)
  274. },
  275. // 显示品牌列表
  276. showBrandlList() {
  277. this.$refs.brandListRef.showBrandList();
  278. },
  279. // 处理选中品牌
  280. handleSelectedBrand(info) {
  281. this.coreInfo.dictLabel = info.dictLabel;
  282. this.coreInfo.dictValue = info.dictValue;
  283. this.submitEdit(this.coreInfo)
  284. },
  285. // 获取商品详情
  286. getGoodsDetail() {
  287. uni.$u.api.wareHouseDetail({
  288. id: this.goodsId,
  289. userId: this.$store.state.user.userInfo.userId,
  290. }).then(res => {
  291. this.coreInfo = res.data;
  292. this.imgsUrl = res.data.imgsUrl || [];
  293. this.lockStatus = res.data.lockStatus;
  294. this.downStatus = res.data.downStatus;
  295. });
  296. },
  297. // 打开销售业务开单页面
  298. handleOpenOrder() {
  299. if (this.lockStatus === '1') {
  300. uni.showToast({
  301. title: '已锁单,不能开单',
  302. icon: 'none'
  303. })
  304. return
  305. }
  306. const params = {
  307. id: this.goodsId,
  308. url: this.imgsUrl[0].url,
  309. dictLabel: this.coreInfo.dictLabel,
  310. dictValue: this.coreInfo.dictValue,
  311. model: this.coreInfo.model,
  312. totalCost: Number(this.coreInfo.originalCost) + Number(this.coreInfo.additionalCost),
  313. suggestedPrice: this.coreInfo.suggestedPrice,
  314. recyclePerson: this.coreInfo.recyclePerson,
  315. recyclePersonId: this.coreInfo.recyclePersonId,
  316. warehouseDate: this.coreInfo.warehouseDate,
  317. }
  318. uni.navigateTo({
  319. url: '/pages/wareHouse/components/openOrder?params=' + JSON.stringify(params),
  320. });
  321. },
  322. viewLog() {
  323. uni.$u.api.wareHouseLog({
  324. id: this.goodsId,
  325. userId: this.$store.state.user.userInfo.userId,
  326. }).then(res => {
  327. this.logList = res.data;
  328. this.logShow = true;
  329. });
  330. },
  331. // 立即下架按钮
  332. handleOffShelf() {
  333. uni.$u.api.wareHouseDown({
  334. id: this.goodsId,
  335. downStatus: this.downStatus === '1' ? '0' : '1',
  336. }).then(res => {
  337. uni.showToast({
  338. title: this.downStatus === '1' ? '上架成功' : '下架成功',
  339. icon: 'success'
  340. })
  341. this.getGoodsDetail();
  342. })
  343. },
  344. // 切换单个字段的编辑状态
  345. toggleEditField(field, value) {
  346. // 如果不是全局编辑模式
  347. if (!this.globalEditMode) {
  348. // 重置所有编辑状态
  349. this.resetEditMode();
  350. // 设置当前字段为编辑状态
  351. this.editMode[field] = true;
  352. // 保存临时值
  353. this.tempValues[field] = value;
  354. }
  355. },
  356. // 保存单个字段的编辑值
  357. saveEditField(field, newValue, fieldName) {
  358. this.coreInfo[fieldName] = newValue;
  359. this.editMode[field] = false;
  360. delete this.tempValues[field];
  361. console.log(`保存${field}字段值: ${newValue}`);
  362. this.submitEdit(this.coreInfo);
  363. },
  364. // 进入全局编辑模式
  365. enterGlobalEdit() {
  366. this.globalEditMode = true;
  367. // 设置所有可编辑字段为编辑状态
  368. this.editMode = {
  369. model: true,
  370. code: true,
  371. note: true,
  372. originalCost: true,
  373. additionalCost: true,
  374. agentPrice: true,
  375. suggestedPrice: true,
  376. location: true,
  377. };
  378. // 保存所有字段的临时值
  379. this.tempValues = { ...this.coreInfo }
  380. },
  381. // 重置所有编辑状态
  382. resetEditMode() {
  383. Object.keys(this.editMode).forEach(key => {
  384. this.editMode[key] = false;
  385. });
  386. },
  387. submitEdit(info) {
  388. const data = {
  389. id: this.goodsId,
  390. imgsUrl: this.imgsUrl,
  391. dictLabel: info.dictLabel,
  392. dictValue: info.dictValue,
  393. model: info.model,
  394. code: info.code,
  395. warehouseDate: info.warehouseDate,
  396. payType: info.payType,
  397. note: info.note,
  398. originalCost: info.originalCost,
  399. additionalCost: info.additionalCost,
  400. agentPrice: info.agentPrice,
  401. suggestedPrice: info.suggestedPrice,
  402. location: info.location,
  403. recyclePerson: info.recyclePerson,
  404. recyclePersonId: info.recyclePersonId,
  405. identifyingPerson: info.identifyingPerson,
  406. identifyingPersonId: info.identifyingPersonId,
  407. }
  408. uni.$u.api.wareHouseUpdate(data).then(res => {
  409. uni.showToast({
  410. title: '编辑成功',
  411. icon: 'success'
  412. });
  413. this.getGoodsDetail();
  414. this.$emit('editSuccess')
  415. })
  416. },
  417. // 点击页面其他地方保存所有编辑
  418. handleClickOutside() {
  419. if (this.globalEditMode) {
  420. this.submitEdit(this.tempValues)
  421. // 全局编辑模式下,点击外部区域重置所有编辑
  422. this.globalEditMode = false;
  423. // 退出所有编辑状态
  424. this.resetEditMode();
  425. // 清空临时值,恢复初始状态
  426. this.tempValues = {};
  427. } else {
  428. // 保存当前正在编辑的字段
  429. Object.keys(this.editMode).forEach(key => {
  430. if (this.editMode[key]) {
  431. this.saveEditField(key, this.tempValues[key], key);
  432. }
  433. });
  434. }
  435. },
  436. handleDownload() {
  437. if (this.imgsUrl.length == 0) {
  438. uni.showToast({
  439. title: '暂无图片',
  440. icon: 'none'
  441. })
  442. return;
  443. }
  444. uni.showModal({
  445. title: '保存图片',
  446. content: `是否将 ${this.imgsUrl.length} 张图片保存到本地相册?`,
  447. confirmText: '保存',
  448. success: (res) => {
  449. if (res.confirm) {
  450. this.saveImagesToLocal(this.imgsUrl);
  451. }
  452. }
  453. })
  454. },
  455. saveImagesToLocal(allUrls) {
  456. uni.showToast({
  457. title: '图片保存中...',
  458. icon: 'loading'
  459. })
  460. allUrls.forEach((item, index) => {
  461. uni.downloadFile({
  462. url: item.url,
  463. success: (res) => {
  464. if (res.statusCode === 200) {
  465. uni.saveImageToPhotosAlbum({
  466. filePath: res.tempFilePath,
  467. success() {
  468. uni.showToast({
  469. title: '保存成功'
  470. })
  471. },
  472. fail: (err) => {
  473. console.error('保存到相册失败:', err);
  474. // 如果是权限问题,尝试请求权限
  475. if (err.errMsg.includes('auth denied')) {
  476. uni.showModal({
  477. title: '权限不足',
  478. content: '需要访问相册权限来保存图片,是否去设置?',
  479. success: (modalRes) => {
  480. if (modalRes.confirm) {
  481. // 打开设置页面
  482. uni.openSetting({
  483. success: (settingRes) => {
  484. console.log('设置页面结果:', settingRes);
  485. }
  486. });
  487. }
  488. }
  489. });
  490. }
  491. reject(err);
  492. }
  493. })
  494. }
  495. }
  496. })
  497. })
  498. },
  499. handleEditImgs() {
  500. this.showEditImgsModal = true;
  501. this.tempImgsUrl = this.imgsUrl
  502. },
  503. getDeleteImgInfo(imgInfo) {
  504. this.tempImgsUrl = imgInfo.newImages
  505. },
  506. afterRead(info) {
  507. info.file.forEach(item=>{
  508. uni.$u.api.uploadFile(item.url).then((res) => {
  509. this.tempImgsUrl.push({url:res.data.url});
  510. uni.$u.toast("文件上传成功");
  511. this.showEditImgsModal = false;
  512. }).catch(() => {
  513. uni.$u.toast("上传文件失败");
  514. })
  515. })
  516. },
  517. confirmEditImgs() {
  518. this.imgsUrl = this.tempImgsUrl
  519. this.submitEdit(this.coreInfo)
  520. this.showEditImgsModal = false;
  521. },
  522. cancelEditImgs() {
  523. this.tempImgsUrl = []
  524. this.showEditImgsModal = false;
  525. }
  526. }
  527. }
  528. </script>
  529. <style lang="scss" scoped>
  530. @import "../styles/detail.scss";
  531. </style>