EnergyBehaviorConfig.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <template>
  2. <div class="energy-behavior-config admin-page">
  3. <div class="header">
  4. <h2>能量行为配置</h2>
  5. <div class="filters">
  6. <el-input v-model="keyword" placeholder="搜索行为名称/编码..." prefix-icon="el-icon-search" style="width:260px" clearable @keyup.enter.native="loadList" @clear="loadList" />
  7. <el-select v-model="enabledFilter" placeholder="状态筛选" @change="loadList" style="width: 140px;" clearable>
  8. <el-option label="全部" value="" />
  9. <el-option label="已启用" value="1" />
  10. <el-option label="已禁用" value="0" />
  11. </el-select>
  12. </div>
  13. </div>
  14. <el-table :data="list" v-loading="loading" border stripe highlight-current-row>
  15. <el-table-column prop="behaviorCode" label="行为编码" width="160" />
  16. <el-table-column prop="behaviorName" label="行为名称" width="130" />
  17. <el-table-column prop="description" label="描述" min-width="160" show-overflow-tooltip />
  18. <el-table-column label="默认值" width="80">
  19. <template slot-scope="{ row }">
  20. <el-tag size="mini" type="info">{{ row.defaultAmount }}</el-tag>
  21. </template>
  22. </el-table-column>
  23. <el-table-column prop="amountSource" label="值来源" width="120">
  24. <template slot-scope="{ row }">
  25. <el-tag size="mini" :type="sourceTypeTag(row.amountSource)">{{ sourceTypeLabel(row.amountSource) }}</el-tag>
  26. </template>
  27. </el-table-column>
  28. <el-table-column label="维度分配" min-width="200">
  29. <template slot-scope="{ row }">
  30. <span v-if="!row.dimensionAssign" style="color:#999">未配置</span>
  31. <el-tag v-for="d in parseDimAssign(row.dimensionAssign)" :key="d.dim" size="mini" :style="{ background: dimColor(d.dim), color: '#fff', marginRight: '4px' }">
  32. {{ dimLabel(d.dim) }} {{ d.ratio }}%
  33. </el-tag>
  34. </template>
  35. </el-table-column>
  36. <el-table-column label="每日上限" width="80">
  37. <template slot-scope="{ row }">
  38. <span>{{ row.dailyLimit || '不限' }}</span>
  39. </template>
  40. </el-table-column>
  41. <el-table-column label="过期天数" width="80">
  42. <template slot-scope="{ row }">
  43. <span>{{ row.expireDays || '永久' }}</span>
  44. </template>
  45. </el-table-column>
  46. <el-table-column label="状态" width="80">
  47. <template slot-scope="{ row }">
  48. <el-switch :value="row.enabled === 1" @change="toggleStatus(row)" />
  49. </template>
  50. </el-table-column>
  51. <el-table-column label="操作" width="120" fixed="right">
  52. <template slot-scope="{ row }">
  53. <el-button size="mini" type="primary" @click="showEdit(row)">编辑</el-button>
  54. </template>
  55. </el-table-column>
  56. </el-table>
  57. <el-dialog title="编辑能量行为配置" :visible.sync="dialogVisible" width="700px" :close-on-click-modal="false">
  58. <el-form :model="form" label-width="110px" size="small">
  59. <el-row :gutter="20">
  60. <el-col :span="12">
  61. <el-form-item label="行为编码">
  62. <el-input v-model="form.behaviorCode" disabled />
  63. </el-form-item>
  64. </el-col>
  65. <el-col :span="12">
  66. <el-form-item label="行为名称">
  67. <el-input v-model="form.behaviorName" disabled />
  68. </el-form-item>
  69. </el-col>
  70. </el-row>
  71. <el-form-item label="描述">
  72. <el-input v-model="form.description" disabled />
  73. </el-form-item>
  74. <el-row :gutter="20">
  75. <el-col :span="8">
  76. <el-form-item label="默认能量值">
  77. <el-input-number v-model="form.defaultAmount" :min="0" :max="9999" />
  78. </el-form-item>
  79. </el-col>
  80. <el-col :span="8">
  81. <el-form-item label="值来源">
  82. <el-select v-model="form.amountSource" style="width:100%">
  83. <el-option label="固定值" value="fixed" />
  84. <el-option label="跟随积分" value="points" />
  85. <el-option label="跟随金额" value="order_amount" />
  86. <el-option label="从配置表读取" value="config" />
  87. </el-select>
  88. </el-form-item>
  89. </el-col>
  90. <el-col :span="8">
  91. <el-form-item label="过期天数">
  92. <el-input-number v-model="form.expireDays" :min="0" :max="3650" />
  93. </el-form-item>
  94. </el-col>
  95. </el-row>
  96. <el-row :gutter="20">
  97. <el-col :span="12">
  98. <el-form-item label="每日上限">
  99. <el-input-number v-model="form.dailyLimit" :min="0" :max="9999" />
  100. </el-form-item>
  101. </el-col>
  102. <el-col :span="12">
  103. <el-form-item label="冷却时间(秒)">
  104. <el-input-number v-model="form.cooldownSeconds" :min="0" :max="86400" />
  105. </el-form-item>
  106. </el-col>
  107. </el-row>
  108. <el-form-item label="维度分配">
  109. <div v-for="(d, i) in dimAssignList" :key="i" style="display:flex;align-items:center;margin-bottom:8px;gap:8px">
  110. <el-select v-model="d.dim" style="width:120px" @change="normalizeDimRatio">
  111. <el-option v-for="dim in dimensions" :key="dim.code" :label="dim.name" :value="dim.code" />
  112. </el-select>
  113. <el-slider v-model="d.ratio" :min="0" :max="100" style="flex:1" />
  114. <span style="width:40px;text-align:right">{{ d.ratio }}%</span>
  115. <el-button type="danger" size="mini" icon="el-icon-delete" circle @click="removeDim(i)" v-if="dimAssignList.length > 1" />
  116. </div>
  117. <el-button size="mini" @click="addDim" v-if="dimAssignList.length < 5">+ 添加维度</el-button>
  118. </el-form-item>
  119. </el-form>
  120. <span slot="footer">
  121. <el-button @click="dialogVisible = false">取消</el-button>
  122. <el-button type="primary" @click="saveConfig">保存</el-button>
  123. </span>
  124. </el-dialog>
  125. </div>
  126. </template>
  127. <script>
  128. import { getEnergyBehaviorList, updateEnergyBehavior, toggleEnergyBehavior } from '@/api/energy'
  129. const DIMENSION_MAP = {
  130. body: { name: '身', color: '#FF8C42' },
  131. mind: { name: '心', color: '#FF6B9D' },
  132. wisdom: { name: '智', color: '#6366F1' },
  133. action: { name: '行', color: '#10B981' },
  134. wealth: { name: '富', color: '#F59E0B' }
  135. }
  136. export default {
  137. name: 'EnergyBehaviorConfig',
  138. data() {
  139. return {
  140. keyword: '',
  141. enabledFilter: '',
  142. loading: false,
  143. list: [],
  144. dialogVisible: false,
  145. form: {},
  146. dimAssignList: [],
  147. dimensions: Object.entries(DIMENSION_MAP).map(([code, v]) => ({ code, name: v.name }))
  148. }
  149. },
  150. mounted() {
  151. this.loadList()
  152. },
  153. methods: {
  154. async loadList() {
  155. this.loading = true
  156. try {
  157. const res = await getEnergyBehaviorList()
  158. if (res.code === 200) {
  159. this.list = (res.data || []).filter(r => {
  160. if (this.keyword) {
  161. const kw = this.keyword.toLowerCase()
  162. if (!r.behaviorCode.toLowerCase().includes(kw) && !r.behaviorName.toLowerCase().includes(kw)) return false
  163. }
  164. if (this.enabledFilter !== '') {
  165. if (r.enabled !== parseInt(this.enabledFilter)) return false
  166. }
  167. return true
  168. })
  169. }
  170. } finally {
  171. this.loading = false
  172. }
  173. },
  174. showEdit(row) {
  175. this.form = { ...row }
  176. this.dimAssignList = this.parseDimAssign(row.dimensionAssign)
  177. if (this.dimAssignList.length === 0) {
  178. this.dimAssignList = [{ dim: 'action', ratio: 100 }]
  179. }
  180. this.dialogVisible = true
  181. },
  182. parseDimAssign(json) {
  183. if (!json) return []
  184. try { return JSON.parse(json) } catch { return [] }
  185. },
  186. async saveConfig() {
  187. const dimensionAssign = JSON.stringify(this.dimAssignList.filter(d => d.ratio > 0))
  188. const payload = { ...this.form, dimensionAssign }
  189. const res = await updateEnergyBehavior(payload)
  190. if (res.code === 200) {
  191. this.$message.success('保存成功')
  192. this.dialogVisible = false
  193. this.loadList()
  194. } else {
  195. this.$message.error(res.message || '保存失败')
  196. }
  197. },
  198. async toggleStatus(row) {
  199. const res = await toggleEnergyBehavior(row.id)
  200. if (res.code === 200) {
  201. this.$message.success(res.message)
  202. this.loadList()
  203. }
  204. },
  205. addDim() {
  206. const used = this.dimAssignList.map(d => d.dim)
  207. const next = this.dimensions.find(d => !used.includes(d.code))
  208. if (next) this.dimAssignList.push({ dim: next.code, ratio: 0 })
  209. },
  210. removeDim(i) {
  211. this.dimAssignList.splice(i, 1)
  212. this.normalizeDimRatio()
  213. },
  214. normalizeDimRatio() {
  215. // 自动调整,确保总和为100
  216. },
  217. sourceTypeLabel(v) {
  218. return { fixed: '固定值', points: '跟随积分', order_amount: '跟随金额', config: '配置表读取' }[v] || v
  219. },
  220. sourceTypeTag(v) {
  221. return { fixed: '', points: 'warning', order_amount: 'danger', config: 'info' }[v] || ''
  222. },
  223. dimLabel(code) { return (DIMENSION_MAP[code] || {}).name || code },
  224. dimColor(code) { return (DIMENSION_MAP[code] || {}).color || '#999' }
  225. }
  226. }
  227. </script>
  228. <style scoped>
  229. .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
  230. .filters { display: flex; gap: 10px; align-items: center; }
  231. </style>