巴青农资商城

detail.vue 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <template>
  2. <el-drawer
  3. :title="'商品详情 · ' + (detail.goodsName || detail.goodsSn || '')"
  4. :visible.sync="localVisible"
  5. direction="rtl"
  6. size="72%"
  7. append-to-body
  8. custom-class="detail-drawer"
  9. >
  10. <div v-loading="loading" class="drawer-content">
  11. <h4 class="section-header">基础信息</h4>
  12. <el-descriptions :column="2" border size="small" class="mb16">
  13. <el-descriptions-item label="商品编号">{{ detail.goodsSn || '—' }}</el-descriptions-item>
  14. <el-descriptions-item label="商品状态">
  15. <el-tag size="small" :type="goodsStatusTag(detail.goodsStatus)">{{ goodsStatusLabel(detail.goodsStatus) }}</el-tag>
  16. </el-descriptions-item>
  17. <el-descriptions-item label="商品名称" :span="2">{{ detail.goodsName || '—' }}</el-descriptions-item>
  18. <el-descriptions-item label="售价">{{ detail.salePrice != null ? detail.salePrice : '—' }}</el-descriptions-item>
  19. <el-descriptions-item label="库存">{{ detail.stock != null ? detail.stock : '—' }}</el-descriptions-item>
  20. <el-descriptions-item label="销量">{{ detail.salesCount != null ? detail.salesCount : '—' }}</el-descriptions-item>
  21. <el-descriptions-item label="主图">
  22. <image-preview v-if="detail.mainPic" :src="detail.mainPic" :width="80" :height="80" />
  23. <span v-else>—</span>
  24. </el-descriptions-item>
  25. <el-descriptions-item v-if="detail.subPics && detail.subPics.length" label="副图" :span="2">
  26. <div class="sub-pics-row">
  27. <image-preview
  28. v-for="(pic, idx) in detail.subPics"
  29. :key="idx"
  30. :src="pic"
  31. :width="60"
  32. :height="60"
  33. class="sub-pic-item"
  34. />
  35. </div>
  36. </el-descriptions-item>
  37. </el-descriptions>
  38. <h4 class="section-header">分类信息</h4>
  39. <el-descriptions :column="1" border size="small" class="mb16">
  40. <el-descriptions-item label="商品分类">{{ detail.categoryPath || '—' }}</el-descriptions-item>
  41. <el-descriptions-item label="店铺商品分类">{{ detail.shopCategoryPath || detail.shopCategoryName || '—' }}</el-descriptions-item>
  42. <el-descriptions-item label="属性模版">{{ detail.attrTemplateName || '—' }}</el-descriptions-item>
  43. </el-descriptions>
  44. <h4 v-if="hasAttrList(detail.attributes)" class="section-header">商品属性</h4>
  45. <el-table v-if="hasAttrList(detail.attributes)" border size="small" :data="detail.attributes" class="mb16">
  46. <el-table-column label="属性名" prop="itemName" width="120" />
  47. <el-table-column label="属性值">
  48. <template slot-scope="scope">
  49. <span>{{ formatAttrValues(scope.row.values) }}</span>
  50. </template>
  51. </el-table-column>
  52. </el-table>
  53. <h4 class="section-header">配送运费</h4>
  54. <el-descriptions :column="2" border size="small" class="mb16">
  55. <el-descriptions-item label="运费方式">{{ freightTypeLabel(detail.freightType) }}</el-descriptions-item>
  56. <el-descriptions-item v-if="detail.freightType === '1'" label="固定金额">
  57. {{ detail.fixedFreightAmount != null ? detail.fixedFreightAmount : '—' }}
  58. </el-descriptions-item>
  59. <el-descriptions-item v-if="detail.freightType === '2'" label="运费模版">
  60. {{ detail.freightTemplateName || '—' }}
  61. </el-descriptions-item>
  62. <el-descriptions-item v-if="detail.freightType === '2' && detail.freightDesc" label="运费说明" :span="2">
  63. {{ detail.freightDesc }}
  64. </el-descriptions-item>
  65. </el-descriptions>
  66. <h4 v-if="hasAttrList(detail.specs)" class="section-header">商品规格</h4>
  67. <el-table v-if="hasAttrList(detail.specs)" border size="small" :data="detail.specs" class="mb16">
  68. <el-table-column label="规格名" prop="itemName" width="120" />
  69. <el-table-column label="规格值">
  70. <template slot-scope="scope">
  71. <span>{{ formatAttrValues(scope.row.values) }}</span>
  72. </template>
  73. </el-table-column>
  74. </el-table>
  75. <h4 class="section-header">状态信息</h4>
  76. <el-descriptions :column="2" border size="small" class="mb16">
  77. <el-descriptions-item label="提交时间">{{ parseTime(detail.submitTime) || '—' }}</el-descriptions-item>
  78. <el-descriptions-item label="审核时间">{{ parseTime(detail.auditTime) || '—' }}</el-descriptions-item>
  79. <el-descriptions-item v-if="detail.goodsStatus === '3'" label="驳回原因" :span="2">
  80. <span class="reject-reason">{{ detail.rejectReason || '—' }}</span>
  81. </el-descriptions-item>
  82. <el-descriptions-item v-if="detail.goodsStatus === '4'" label="下架时间">{{ parseTime(detail.offShelfTime) || '—' }}</el-descriptions-item>
  83. </el-descriptions>
  84. <h4 class="section-header">商品服务快照</h4>
  85. <el-table v-if="detail.services && detail.services.length" border size="small" :data="detail.services" class="mb16">
  86. <el-table-column label="服务名称" prop="serviceName" />
  87. <el-table-column label="简介" prop="serviceIntro" :show-overflow-tooltip="true" />
  88. <el-table-column label="图标" width="80">
  89. <template slot-scope="scope">
  90. <image-preview :src="scope.row.serviceIcon" :width="40" :height="40" />
  91. </template>
  92. </el-table-column>
  93. </el-table>
  94. <div v-else class="empty-tip mb16">暂无勾选服务项</div>
  95. <h4 v-if="detail.detailContent" class="section-header">商品详情</h4>
  96. <div v-if="detail.detailContent" class="detail-content mb16" v-html="detail.detailContent"></div>
  97. <div class="action-bar">
  98. <el-button v-if="canShowEdit" type="primary" plain @click="handleEdit" v-hasPermi="['agri:seller:goods:edit']">编辑</el-button>
  99. <el-button v-if="detail.canSubmit" type="success" @click="handleSubmit" v-hasPermi="['agri:seller:goods:edit']">提交上架</el-button>
  100. <el-button v-if="detail.canOffShelf" type="warning" plain @click="handleOffShelf" v-hasPermi="['agri:seller:goods:offshelf']">下架</el-button>
  101. <el-button v-if="detail.canDelete" type="danger" plain @click="handleDelete" v-hasPermi="['agri:seller:goods:remove']">删除</el-button>
  102. </div>
  103. </div>
  104. </el-drawer>
  105. </template>
  106. <script>
  107. import {
  108. getSellerGoods,
  109. submitSellerGoods,
  110. offShelfSellerGoods,
  111. delSellerGoods
  112. } from "@/api/agri/seller/goods"
  113. export default {
  114. name: "SellerGoodsDetail",
  115. props: {
  116. visible: { type: Boolean, default: false },
  117. goodsId: { type: [Number, String], default: null }
  118. },
  119. data() {
  120. return {
  121. localVisible: false,
  122. loading: false,
  123. detail: {}
  124. }
  125. },
  126. computed: {
  127. /** 未上架 / 审核失败 / 已下架可编辑;优先用后端 canEdit */
  128. canShowEdit() {
  129. if (this.detail.canEdit === true) return true
  130. if (this.detail.canEdit === false) return false
  131. const status = this.detail.goodsStatus
  132. return status === "0" || status === "3" || status === "4"
  133. }
  134. },
  135. watch: {
  136. visible(val) {
  137. this.localVisible = val
  138. if (val && this.goodsId) {
  139. this.loadDetail()
  140. }
  141. },
  142. localVisible(val) {
  143. this.$emit("update:visible", val)
  144. }
  145. },
  146. methods: {
  147. /** 加载商品详情 */
  148. loadDetail() {
  149. this.loading = true
  150. getSellerGoods(this.goodsId).then(response => {
  151. this.detail = response.data || {}
  152. this.loading = false
  153. }).catch(() => {
  154. this.loading = false
  155. })
  156. },
  157. goodsStatusLabel(status) {
  158. const map = { "0": "未上架", "1": "待审核", "2": "出售中", "3": "审核失败", "4": "已下架" }
  159. return map[status] || this.detail.goodsStatusLabel || "—"
  160. },
  161. goodsStatusTag(status) {
  162. const map = { "0": "", "1": "warning", "2": "success", "3": "danger", "4": "info" }
  163. return map[status] || "info"
  164. },
  165. freightTypeLabel(type) {
  166. const map = { "1": "固定运费", "2": "运费模版" }
  167. return map[type] || "—"
  168. },
  169. /** 是否有属性/规格项 */
  170. hasAttrList(list) {
  171. return list && list.length > 0
  172. },
  173. /** 格式化属性值列表 */
  174. formatAttrValues(values) {
  175. if (!values || !values.length) {
  176. return "—"
  177. }
  178. return values.join("、")
  179. },
  180. /** 跳转编辑 */
  181. handleEdit() {
  182. if (!this.canShowEdit) {
  183. this.$modal.msgWarning("当前状态不可编辑;出售中须先下架,待审核须等待平台审核结果")
  184. return
  185. }
  186. this.$emit("edit", this.detail)
  187. this.localVisible = false
  188. },
  189. /** 提交上架 */
  190. handleSubmit() {
  191. this.$modal.confirm("确认提交上架该商品?").then(() => {
  192. return submitSellerGoods(this.goodsId)
  193. }).then(response => {
  194. const data = response.data || {}
  195. const label = data.goodsStatusLabel || "操作成功"
  196. this.$modal.msgSuccess("提交成功,当前状态:" + label)
  197. this.localVisible = false
  198. this.$emit("success")
  199. }).catch(() => {})
  200. },
  201. /** 下架 */
  202. handleOffShelf() {
  203. this.$modal.confirm("下架后 C 端不可购买,是否继续?").then(() => {
  204. return offShelfSellerGoods(this.goodsId)
  205. }).then(() => {
  206. this.$modal.msgSuccess("下架成功")
  207. this.localVisible = false
  208. this.$emit("success")
  209. }).catch(() => {})
  210. },
  211. /** 删除 */
  212. handleDelete() {
  213. this.$modal.confirm("确认删除该商品?删除后不可恢复。").then(() => {
  214. return delSellerGoods(this.goodsId)
  215. }).then(() => {
  216. this.$modal.msgSuccess("删除成功")
  217. this.localVisible = false
  218. this.$emit("success")
  219. }).catch(() => {})
  220. }
  221. }
  222. }
  223. </script>
  224. <style scoped>
  225. .drawer-content {
  226. padding: 0 20px 20px;
  227. }
  228. .section-header {
  229. margin: 16px 0 10px;
  230. font-size: 15px;
  231. color: #303133;
  232. border-left: 3px solid #409EFF;
  233. padding-left: 8px;
  234. }
  235. .mb16 {
  236. margin-bottom: 16px;
  237. }
  238. .reject-reason {
  239. color: #F56C6C;
  240. }
  241. .empty-tip {
  242. color: #909399;
  243. font-size: 13px;
  244. }
  245. .detail-content {
  246. padding: 12px;
  247. background: #fafafa;
  248. border-radius: 4px;
  249. line-height: 1.6;
  250. }
  251. .action-bar {
  252. margin-top: 24px;
  253. padding-top: 16px;
  254. border-top: 1px solid #EBEEF5;
  255. }
  256. .sub-pics-row {
  257. display: flex;
  258. flex-wrap: wrap;
  259. gap: 8px;
  260. }
  261. .sub-pic-item {
  262. flex-shrink: 0;
  263. }
  264. </style>