ProductProfitRate.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. <template>
  2. <div class="product-profit-rate">
  3. <div class="header">
  4. <h2>产品利润率设置</h2>
  5. <div class="filters">
  6. <el-input v-model="searchText" placeholder="商品名称搜索" style="width: 200px;" clearable @keyup.enter.native="loadList" />
  7. <el-select v-model="typeFilter" placeholder="商品类型" @change="loadList" style="width: 140px;">
  8. <el-option label="全部类型" value="" />
  9. <el-option label="实物商品" value="physical" />
  10. <el-option label="虚拟商品" value="virtual" />
  11. <el-option label="优惠券" value="coupon" />
  12. </el-select>
  13. <el-button type="primary" @click="loadList">查询</el-button>
  14. </div>
  15. </div>
  16. <el-table :data="list" v-loading="loading" border stripe>
  17. <el-table-column prop="id" label="ID" width="70" />
  18. <el-table-column label="商品图片" width="70">
  19. <template slot-scope="{ row }">
  20. <img v-if="row.coverImage" :src="row.coverImage" class="product-thumb" />
  21. <span v-else>-</span>
  22. </template>
  23. </el-table-column>
  24. <el-table-column prop="name" label="商品名称" min-width="160" show-overflow-tooltip />
  25. <el-table-column label="类型" width="90">
  26. <template slot-scope="{ row }">
  27. {{ typeLabel(row.productType) }}
  28. </template>
  29. </el-table-column>
  30. <el-table-column prop="price" label="售价(元)" width="90">
  31. <template slot-scope="{ row }">
  32. ¥{{ (row.price / 100).toFixed(2) }}
  33. </template>
  34. </el-table-column>
  35. <el-table-column label="当前利润率" width="120">
  36. <template slot-scope="{ row }">
  37. <span v-if="row.profitRate != null">{{ (row.profitRate * 100).toFixed(1) }}%</span>
  38. <span v-else class="text-muted">未设置</span>
  39. </template>
  40. </el-table-column>
  41. <el-table-column label="新利润率" width="180">
  42. <template slot-scope="{ row }">
  43. <el-input-number
  44. v-if="editId === row.id"
  45. v-model="editRate"
  46. :min="0"
  47. :max="1"
  48. :step="0.01"
  49. :precision="2"
  50. size="small"
  51. style="width: 140px;"
  52. />
  53. <span v-else class="text-muted">-</span>
  54. </template>
  55. </el-table-column>
  56. <el-table-column label="操作" width="150" fixed="right">
  57. <template slot-scope="{ row }">
  58. <el-button
  59. v-if="editId !== row.id"
  60. type="primary"
  61. size="small"
  62. @click="startEdit(row)"
  63. >设置</el-button>
  64. <template v-else>
  65. <el-button type="success" size="small" @click="saveRate(row)">保存</el-button>
  66. <el-button size="small" @click="cancelEdit">取消</el-button>
  67. </template>
  68. </template>
  69. </el-table-column>
  70. </el-table>
  71. <el-pagination
  72. @size-change="onPageChange"
  73. @current-change="onPageChange"
  74. :current-page="page"
  75. :page-size="size"
  76. :total="total"
  77. layout="total, prev, pager, next"
  78. style="margin-top: 20px; text-align: right;"
  79. />
  80. </div>
  81. </template>
  82. <script>
  83. import { getProductList, updateProductProfitRate } from '@/api/admin'
  84. export default {
  85. data() {
  86. return {
  87. list: [],
  88. loading: false,
  89. page: 1,
  90. size: 20,
  91. total: 0,
  92. searchText: '',
  93. typeFilter: '',
  94. editId: null,
  95. editRate: 0
  96. }
  97. },
  98. mounted() {
  99. this.loadList()
  100. },
  101. methods: {
  102. async loadList() {
  103. this.loading = true
  104. try {
  105. const res = await getProductList({ status: '', keyword: this.searchText, productType: this.typeFilter })
  106. this.list = res.data || []
  107. this.total = (res.data || []).length
  108. } catch (e) {
  109. this.$message.error('加载失败')
  110. } finally {
  111. this.loading = false
  112. }
  113. },
  114. onPageChange(val) {
  115. this.page = val
  116. this.loadList()
  117. },
  118. typeLabel(type) {
  119. const map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
  120. return map[type] || type
  121. },
  122. startEdit(row) {
  123. this.editId = row.id
  124. this.editRate = row.profitRate != null ? row.profitRate : 0
  125. },
  126. cancelEdit() {
  127. this.editId = null
  128. this.editRate = 0
  129. },
  130. async saveRate(row) {
  131. if (this.editRate == null) {
  132. return this.$message.warning('请设置利润率')
  133. }
  134. try {
  135. await updateProductProfitRate(row.id, this.editRate)
  136. this.$message.success('设置成功')
  137. this.editId = null
  138. this.loadList()
  139. } catch (e) {
  140. this.$message.error('设置失败')
  141. }
  142. }
  143. }
  144. }
  145. </script>
  146. <style scoped>
  147. .product-profit-rate {
  148. padding: 20px;
  149. }
  150. .header {
  151. display: flex;
  152. justify-content: space-between;
  153. align-items: center;
  154. margin-bottom: 20px;
  155. }
  156. .header h2 {
  157. margin: 0;
  158. }
  159. .filters {
  160. display: flex;
  161. gap: 10px;
  162. align-items: center;
  163. }
  164. .product-thumb {
  165. width: 50px;
  166. height: 50px;
  167. object-fit: cover;
  168. border-radius: 4px;
  169. }
  170. .text-muted {
  171. color: #999;
  172. }
  173. </style>