Просмотр исходного кода

feat(web): add family metric aggregation config admin page

Sisyphus Agent 1 неделя назад
Родитель
Сommit
003c659408

+ 32 - 0
cfc-web/src/api/admin.js

@@ -136,6 +136,38 @@ export function setMemberAdminRole(familyId, memberId, isAdmin) {
   })
 }
 
+// 家庭数据汇总方式配置(五维家庭汇总:合计 / 平均 / 汇集)
+export function getFamilyMetricAggList() {
+  return request({
+    url: '/api/admin/family-metric-agg/list',
+    method: 'post'
+  })
+}
+
+export function saveFamilyMetricAgg(data) {
+  return request({
+    url: '/api/admin/family-metric-agg/save',
+    method: 'post',
+    data
+  })
+}
+
+export function deleteFamilyMetricAgg(data) {
+  return request({
+    url: '/api/admin/family-metric-agg/delete',
+    method: 'post',
+    data
+  })
+}
+
+export function updateFamilyMetricAggStatus(data) {
+  return request({
+    url: '/api/admin/family-metric-agg/update-status',
+    method: 'post',
+    data
+  })
+}
+
 // 任务模板管理
 export function getTaskTemplateList(params) {
   return request({

+ 6 - 0
cfc-web/src/router/index.js

@@ -55,6 +55,12 @@ const routes = [
         component: () => import('@/views/Children.vue'),
         meta: { title: '孩子管理', perm: 'family:children' }
       },
+      {
+        path: 'family-metric-agg-config',
+        name: 'FamilyMetricAggConfig',
+        component: () => import('@/views/FamilyMetricAggConfig.vue'),
+        meta: { title: '汇总方式配置', perm: 'family:list' }
+      },
       {
         path: 'points',
         name: 'Points',

+ 278 - 0
cfc-web/src/views/FamilyMetricAggConfig.vue

@@ -0,0 +1,278 @@
+<template>
+  <div class="page-container">
+    <el-card>
+      <PageHeader slot="header" title="家庭数据汇总方式配置" :count="tableData.length">
+        <el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增配置</el-button>
+        <el-button size="small" @click="loadList" :loading="loading">刷新</el-button>
+      </PageHeader>
+
+      <div class="adm-table-wrap">
+        <el-table
+          :data="tableData"
+          border
+          v-loading="loading"
+          class="full-width"
+          height="100%"
+        >
+          <el-table-column prop="metricKey" label="数据项标识" min-width="140"></el-table-column>
+          <el-table-column prop="metricName" label="名称" min-width="140"></el-table-column>
+          <el-table-column label="所属维度" width="100">
+            <template slot-scope="scope">
+              {{ dimensionCodeText(scope.row.dimensionCode) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="汇总方式" width="100">
+            <template slot-scope="scope">
+              <StatusTag :type="aggTypeTagType(scope.row.aggType)" :text="aggTypeText(scope.row.aggType)" size="small" />
+            </template>
+          </el-table-column>
+          <el-table-column label="值类型" width="100">
+            <template slot-scope="scope">
+              {{ valueTypeText(scope.row.valueType) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="sortOrder" label="排序" width="80"></el-table-column>
+          <el-table-column label="状态" width="90">
+            <template slot-scope="scope">
+              <StatusTag
+                size="small"
+                :type="scope.row.status === 'active' ? 'success' : 'info'"
+                :text="scope.row.status === 'active' ? '启用' : '停用'"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="230" fixed="right">
+            <template slot-scope="{ row }">
+              <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+              <el-button
+                size="mini"
+                :type="row.status === 'active' ? 'warning' : 'success'"
+                @click="handleToggleStatus(row)"
+              >{{ row.status === 'active' ? '停用' : '启用' }}</el-button>
+              <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-card>
+
+    <!-- 新增/编辑配置弹窗 -->
+    <el-dialog
+      :title="form.id ? '编辑配置' : '新增配置'"
+      :visible.sync="dialogVisible"
+      width="560px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="form" :rules="formRules" ref="form" label-width="100px">
+        <el-form-item label="数据项标识" prop="metricKey">
+          <el-input v-model="form.metricKey" :disabled="!!form.id" placeholder="请输入数据项标识,如 height"></el-input>
+        </el-form-item>
+        <el-form-item label="名称" prop="metricName">
+          <el-input v-model="form.metricName" placeholder="请输入名称,如 身高"></el-input>
+        </el-form-item>
+        <el-form-item label="所属维度" prop="dimensionCode">
+          <el-select v-model="form.dimensionCode" placeholder="请选择维度" class="full-width">
+            <el-option v-for="item in dimensionOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="汇总方式" prop="aggType">
+          <el-select v-model="form.aggType" placeholder="请选择汇总方式" class="full-width">
+            <el-option v-for="item in aggTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="值类型" prop="valueType">
+          <el-select v-model="form.valueType" placeholder="请选择值类型" class="full-width">
+            <el-option v-for="item in valueTypeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="展示模式" prop="displayMode">
+          <el-input v-model="form.displayMode" placeholder="展示模式标识,如 chart / percent(可选)"></el-input>
+        </el-form-item>
+        <el-form-item label="排序" prop="sortOrder">
+          <el-input-number v-model="form.sortOrder" :min="0" :max="9999" :step="1"></el-input-number>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select v-model="form.status" class="full-width">
+            <el-option label="启用" value="active"></el-option>
+            <el-option label="停用" value="disabled"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input type="textarea" v-model="form.remark" :rows="2" placeholder="备注说明(可选)"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getFamilyMetricAggList, saveFamilyMetricAgg, deleteFamilyMetricAgg, updateFamilyMetricAggStatus } from '@/api/admin'
+
+const DIMENSION_OPTIONS = [
+  { value: 'body', label: '身' },
+  { value: 'mind', label: '心' },
+  { value: 'wisdom', label: '智' },
+  { value: 'action', label: '行' },
+  { value: 'wealth', label: '富' },
+  { value: 'common', label: '通用' }
+]
+const DIMENSION_TEXT = { body: '身', mind: '心', wisdom: '智', action: '行', wealth: '富', common: '通用' }
+const AGG_TYPE_OPTIONS = [
+  { value: 'SUM', label: '合计' },
+  { value: 'AVG', label: '平均' },
+  { value: 'COLLECT', label: '汇集' }
+]
+const AGG_TYPE_TEXT = { SUM: '合计', AVG: '平均', COLLECT: '汇集' }
+const VALUE_TYPE_OPTIONS = [
+  { value: 'NUMBER', label: '数值' },
+  { value: 'COLLECTION', label: '集合' }
+]
+const VALUE_TYPE_TEXT = { NUMBER: '数值', COLLECTION: '集合' }
+
+export default {
+  name: 'FamilyMetricAggConfig',
+  data() {
+    return {
+      loading: false,
+      submitting: false,
+      tableData: [],
+      dialogVisible: false,
+      form: this.emptyForm(),
+      formRules: {
+        metricKey: [{ required: true, message: '请输入数据项标识', trigger: 'blur' }],
+        metricName: [{ required: true, message: '请输入名称', trigger: 'blur' }],
+        aggType: [{ required: true, message: '请选择汇总方式', trigger: 'change' }],
+        valueType: [{ required: true, message: '请选择值类型', trigger: 'change' }]
+      }
+    }
+  },
+  mounted() {
+    this.loadList()
+  },
+  methods: {
+    loadList() {
+      this.loading = true
+      return getFamilyMetricAggList()
+        .then(res => {
+          this.tableData = res.data || []
+        })
+        .catch(error => {
+          this.$message.error(error.message || '加载汇总方式配置失败')
+        })
+        .finally(() => {
+          this.loading = false
+        })
+    },
+    dimensionCodeText(code) {
+      return DIMENSION_TEXT[code] || code || '-'
+    },
+    aggTypeText(type) {
+      return AGG_TYPE_TEXT[type] || type || '-'
+    },
+    aggTypeTagType(type) {
+      if (type === 'SUM') return 'primary'
+      if (type === 'AVG') return 'warning'
+      return 'info'
+    },
+    valueTypeText(type) {
+      return VALUE_TYPE_TEXT[type] || type || '-'
+    },
+    emptyForm() {
+      return {
+        id: null,
+        metricKey: '',
+        metricName: '',
+        dimensionCode: 'common',
+        aggType: 'SUM',
+        valueType: 'NUMBER',
+        displayMode: '',
+        sortOrder: 0,
+        status: 'active',
+        remark: ''
+      }
+    },
+    handleAdd() {
+      this.form = this.emptyForm()
+      this.dialogVisible = true
+      this.$nextTick(() => {
+        if (this.$refs.form) this.$refs.form.clearValidate()
+      })
+    },
+    handleEdit(row) {
+      this.form = {
+        id: row.id,
+        metricKey: row.metricKey,
+        metricName: row.metricName,
+        dimensionCode: row.dimensionCode,
+        aggType: row.aggType,
+        valueType: row.valueType,
+        displayMode: row.displayMode || '',
+        sortOrder: row.sortOrder,
+        status: row.status || 'active',
+        remark: row.remark || ''
+      }
+      this.dialogVisible = true
+      this.$nextTick(() => {
+        if (this.$refs.form) this.$refs.form.clearValidate()
+      })
+    },
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+        this.submitting = true
+        saveFamilyMetricAgg(this.form)
+          .then(() => {
+            this.$message.success('保存成功')
+            this.dialogVisible = false
+            this.loadList()
+          })
+          .catch(error => {
+            this.$message.error(error.message || '保存失败')
+          })
+          .finally(() => {
+            this.submitting = false
+          })
+      })
+    },
+    handleToggleStatus(row) {
+      const newStatus = row.status === 'active' ? 'disabled' : 'active'
+      const action = newStatus === 'active' ? '启用' : '停用'
+      this.$confirm(`确定${action}数据项「${row.metricName}」吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return updateFamilyMetricAggStatus({ id: row.id, status: newStatus })
+      }).then(() => {
+        this.$message.success(`${action}成功`)
+        this.loadList()
+      }).catch(() => {})
+    },
+    handleDelete(row) {
+      this.$confirm(`确定删除数据项「${row.metricName}」吗?删除后不可恢复。`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return deleteFamilyMetricAgg({ id: row.id })
+      }).then(() => {
+        this.$message.success('删除成功')
+        this.loadList()
+      }).catch(() => {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-container {
+  padding: var(--spacing-lg);
+}
+.full-width {
+  width: 100%;
+}
+</style>

+ 1 - 0
cfc-web/src/views/Layout.vue

@@ -184,6 +184,7 @@ export default {
             { path: '/points-log', label: '积分记录', icon: 'el-icon-document', perm: 'family:points,family:bound' },
             { path: '/wishes', label: '心愿管理', icon: 'el-icon-star-off', perm: 'family:wishes' },
             { path: '/rewards', label: '奖励管理', icon: 'el-icon-s-goods', perm: 'reward:list' },
+            { path: '/family-metric-agg-config', label: '汇总方式配置', icon: 'el-icon-s-operation', perm: 'family:list' },
           ]},
 
         // ===== 3. 方案中心 (plan:*) =====