|
|
@@ -0,0 +1,285 @@
|
|
|
+<template>
|
|
|
+ <div class="c-dt-editor" v-loading="loading">
|
|
|
+ <div class="c-dt-title">维度阈值显隐配置(五维/子项/自定义指标)</div>
|
|
|
+ <div class="c-dt-hint">小于阈值显示、≥阈值隐藏;家庭取平均。可针对商品/文章/活动/小游戏分别配置。</div>
|
|
|
+
|
|
|
+ <el-table :data="rows" border size="small" class="c-dt-table">
|
|
|
+ <el-table-column label="维度" min-width="220">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <template v-if="scope.row.isCustom">
|
|
|
+ <el-input v-model="scope.row.dimensionCode" placeholder="维度码(如 CUSTOM_X)" size="small" class="c-dt-input" />
|
|
|
+ <el-input v-model="scope.row.dimensionName" placeholder="指标名称" size="small" class="c-dt-input c-dt-name-input mt-6" />
|
|
|
+ </template>
|
|
|
+ <el-select
|
|
|
+ v-else
|
|
|
+ v-model="scope.row.dimensionCode"
|
|
|
+ placeholder="选择维度"
|
|
|
+ filterable
|
|
|
+ size="small"
|
|
|
+ class="c-dt-dim-select"
|
|
|
+ @change="onDimensionChange(scope.row)"
|
|
|
+ >
|
|
|
+ <el-option-group
|
|
|
+ v-for="g in dimensionGroups"
|
|
|
+ :key="g.label"
|
|
|
+ :label="g.label"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="opt in g.options"
|
|
|
+ :key="opt.dimensionCode"
|
|
|
+ :label="opt.dimensionName"
|
|
|
+ :value="opt.dimensionCode"
|
|
|
+ />
|
|
|
+ </el-option-group>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="阈值 (0-100)" width="150">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input-number
|
|
|
+ v-model="scope.row.threshold"
|
|
|
+ :min="0"
|
|
|
+ :max="100"
|
|
|
+ controls-position="right"
|
|
|
+ size="small"
|
|
|
+ class="c-dt-number"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="80" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button type="text" size="mini" class="c-dt-del" @click="removeRow(scope.$index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="c-dt-empty" v-if="!loading && rows.length === 0">
|
|
|
+ <span>暂无配置</span>
|
|
|
+ <el-button type="primary" size="small" icon="el-icon-plus" @click="addRow(0)">添加维度</el-button>
|
|
|
+ <el-button size="small" @click="addRow(1)">添加自定义指标</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="c-dt-actions" v-if="rows.length > 0">
|
|
|
+ <el-button type="primary" @click="handleSave" :loading="saving">保存配置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getDimensionTree } from '@/api/dimension'
|
|
|
+import { getContentDimensionThresholdList, batchSaveContentDimensionThreshold } from '@/api/content-dimension-threshold'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ContentDimensionThresholdEditor',
|
|
|
+ props: {
|
|
|
+ contentType: {
|
|
|
+ type: String,
|
|
|
+ required: true,
|
|
|
+ validator: function(val) {
|
|
|
+ return ['PRODUCT', 'ARTICLE', 'ACTIVITY', 'MINIGAME'].indexOf(val) >= 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ contentId: {
|
|
|
+ type: [Number, String],
|
|
|
+ required: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ saving: false,
|
|
|
+ flatOptions: [],
|
|
|
+ rows: []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ dimensionGroups() {
|
|
|
+ const map = {}
|
|
|
+ this.flatOptions.forEach(function(opt) {
|
|
|
+ var g = map[opt.group]
|
|
|
+ if (!g) {
|
|
|
+ g = []
|
|
|
+ map[opt.group] = g
|
|
|
+ }
|
|
|
+ g.push(opt)
|
|
|
+ })
|
|
|
+ var groups = []
|
|
|
+ Object.keys(map).forEach(function(label) {
|
|
|
+ groups.push({ label: label, options: map[label] })
|
|
|
+ })
|
|
|
+ return groups
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadDimensionTree()
|
|
|
+ this.loadList()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async loadDimensionTree() {
|
|
|
+ try {
|
|
|
+ var res = await getDimensionTree()
|
|
|
+ if (res.code === 200 && Array.isArray(res.data)) {
|
|
|
+ this.flatOptions = this.flattenTree(res.data)
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('[ContentDimensionThreshold] 加载维度树失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ flattenTree(nodes) {
|
|
|
+ var result = []
|
|
|
+ var walk = function(list, groupName) {
|
|
|
+ ;(list || []).forEach(function(n) {
|
|
|
+ var g = groupName || (n.dimensionName || '其他')
|
|
|
+ if (n.dimensionCode) {
|
|
|
+ result.push({ dimensionCode: n.dimensionCode, dimensionName: n.dimensionName, group: g })
|
|
|
+ }
|
|
|
+ if (n.children && n.children.length) {
|
|
|
+ walk(n.children, g)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ walk(nodes, null)
|
|
|
+ return result
|
|
|
+ },
|
|
|
+ async loadList() {
|
|
|
+ if (!this.contentId) return
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ var res = await getContentDimensionThresholdList({
|
|
|
+ contentType: this.contentType,
|
|
|
+ contentId: Number(this.contentId)
|
|
|
+ })
|
|
|
+ if (res.code === 200 && Array.isArray(res.data)) {
|
|
|
+ this.rows = res.data.map(function(item) {
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ dimensionCode: item.dimensionCode || '',
|
|
|
+ dimensionName: item.dimensionName || '',
|
|
|
+ threshold: item.threshold != null ? Number(item.threshold) : 60,
|
|
|
+ isCustom: item.isCustom === 1 ? 1 : 0,
|
|
|
+ enabled: item.enabled != null ? Number(item.enabled) : 1,
|
|
|
+ _key: item.id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.rows = []
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('[ContentDimensionThreshold] 加载阈值列表失败', e)
|
|
|
+ this.rows = []
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addRow(isCustom) {
|
|
|
+ var row = {
|
|
|
+ isCustom: isCustom === 1 ? 1 : 0,
|
|
|
+ dimensionCode: '',
|
|
|
+ dimensionName: '',
|
|
|
+ threshold: 60,
|
|
|
+ _key: Date.now() + Math.random()
|
|
|
+ }
|
|
|
+ this.rows.push(row)
|
|
|
+ },
|
|
|
+ removeRow(idx) {
|
|
|
+ this.rows.splice(idx, 1)
|
|
|
+ },
|
|
|
+ onDimensionChange(row) {
|
|
|
+ var opt = this.flatOptions.find(function(o) { return o.dimensionCode === row.dimensionCode })
|
|
|
+ if (opt) {
|
|
|
+ row.dimensionName = opt.dimensionName
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async handleSave() {
|
|
|
+ this.saving = true
|
|
|
+ try {
|
|
|
+ var items = this.rows.map(function(r) {
|
|
|
+ return {
|
|
|
+ dimensionCode: r.dimensionCode,
|
|
|
+ dimensionName: r.dimensionName,
|
|
|
+ threshold: Number(r.threshold) || 0,
|
|
|
+ isCustom: r.isCustom === 1 ? 1 : 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ var res = await batchSaveContentDimensionThreshold({
|
|
|
+ contentType: this.contentType,
|
|
|
+ contentId: Number(this.contentId),
|
|
|
+ items: items
|
|
|
+ })
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success('保存成功')
|
|
|
+ await this.loadList()
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.message || '保存失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('保存失败')
|
|
|
+ } finally {
|
|
|
+ this.saving = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.c-dt-editor {
|
|
|
+ margin-top: 16px;
|
|
|
+ padding: 16px;
|
|
|
+ border: 1px solid var(--adm-border-base, #e4e7ed);
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fafbfc;
|
|
|
+}
|
|
|
+.c-dt-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--adm-text-88, #303133);
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+.c-dt-hint {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--adm-text-45, #909399);
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+.c-dt-table {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.c-dt-table /deep/ .el-table__cell {
|
|
|
+ padding: 6px 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.c-dt-dim-select {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.c-dt-number {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.c-dt-number /deep/ .el-input__inner {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.c-dt-input {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.c-dt-name-input {
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+.c-dt-del {
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+.c-dt-empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px 0;
|
|
|
+ color: var(--adm-text-45, #909399);
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+.c-dt-empty .el-button {
|
|
|
+ margin-left: 8px;
|
|
|
+}
|
|
|
+.c-dt-actions {
|
|
|
+ margin-top: 12px;
|
|
|
+ text-align: right;
|
|
|
+}
|
|
|
+.mt-6 {
|
|
|
+ margin-top: 6px;
|
|
|
+}
|
|
|
+</style>
|