|
|
@@ -0,0 +1,240 @@
|
|
|
+<template>
|
|
|
+ <div class="report-types admin-page">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header">
|
|
|
+ <span>报告类型管理</span>
|
|
|
+ <div style="float: right; display: flex; gap: 10px;">
|
|
|
+ <el-input v-model="keyword" placeholder="搜索类型..." prefix-icon="el-icon-search" size="small" clearable style="width: 200px;" />
|
|
|
+ <el-button type="primary" size="small" @click="handleCreate">+ 新增类型</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="filteredTypes" v-loading="loading" border stripe>
|
|
|
+ <el-table-column prop="id" label="ID" width="60" />
|
|
|
+ <el-table-column prop="typeId" label="类型标识" width="140" />
|
|
|
+ <el-table-column prop="displayName" label="显示名称" min-width="160" />
|
|
|
+ <el-table-column prop="family" label="家族" width="100" />
|
|
|
+ <el-table-column prop="parserPath" label="解析器路径" width="200" />
|
|
|
+ <el-table-column prop="minConfidence" label="阈值" width="70" />
|
|
|
+ <el-table-column label="状态" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.isActive ? 'success' : 'info'" size="small">
|
|
|
+ {{ row.isActive ? '启用' : '停用' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="source" label="来源" width="100" />
|
|
|
+ <el-table-column label="操作" width="220" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
|
|
|
+ <el-button size="mini" :type="row.isActive ? 'warning' : 'success'" @click="handleToggle(row)">
|
|
|
+ {{ row.isActive ? '停用' : '启用' }}
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
|
|
|
+ <el-button size="mini">
|
|
|
+ 更多<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item command="fingerprints" icon="el-icon-s-operation">指纹规则</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="delete" icon="el-icon-delete" divided>删除</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 编辑/新增对话框 -->
|
|
|
+ <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑报告类型' : '新增报告类型'" width="550px">
|
|
|
+ <el-form :model="form" label-width="100px" size="small">
|
|
|
+ <el-form-item label="类型标识" required>
|
|
|
+ <el-input v-model="form.typeId" placeholder="如 dan_a1 / gut_beijing" :disabled="isEdit" />
|
|
|
+ <div class="el-form-item-msg">唯一标识,创建后不可修改</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="显示名称" required>
|
|
|
+ <el-input v-model="form.displayName" placeholder="如 A1 儿童核心认知发展" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="所属家族">
|
|
|
+ <el-input v-model="form.family" placeholder="如 dan / gut_flora / custom" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="描述">
|
|
|
+ <el-input v-model="form.description" type="textarea" :rows="2" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="解析器路径">
|
|
|
+ <el-input v-model="form.parserPath" placeholder="如 dan_reports/extract_all_types.py" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="识别阈值">
|
|
|
+ <el-input-number v-model="form.minConfidence" :min="1" :max="100" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit" :loading="submitting">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 指纹规则管理对话框 -->
|
|
|
+ <el-dialog :visible.sync="fpDialogVisible" title="指纹规则管理" width="700px" :close-on-click-modal="false">
|
|
|
+ <div slot="header">
|
|
|
+ <span>指纹规则 - {{ currentType?.typeId }}</span>
|
|
|
+ <el-button type="primary" size="small" style="float:right" @click="handleAddFp">+ 新增指纹</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="fingerprints" v-loading="fpLoading" border stripe size="small">
|
|
|
+ <el-table-column prop="pattern" label="文本特征" min-width="250" />
|
|
|
+ <el-table-column prop="weight" label="权重" width="80" />
|
|
|
+ <el-table-column label="类型" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.isExclusion ? 'danger' : 'success'" size="mini">
|
|
|
+ {{ row.isExclusion ? '排除' : '正向' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sortOrder" label="排序" width="60" />
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button size="mini" type="danger" @click="handleDeleteFp(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <el-dialog :visible.sync="fpFormVisible" title="新增指纹规则" width="450px" :append-to-body="true">
|
|
|
+ <el-form :model="fpForm" label-width="80px" size="small">
|
|
|
+ <el-form-item label="文本特征" required>
|
|
|
+ <el-input v-model="fpForm.pattern" placeholder="如 GROWTH MINDSET" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="权重">
|
|
|
+ <el-input-number v-model="fpForm.weight" :min="-100" :max="100" />
|
|
|
+ <div class="el-form-item-msg">正=正向匹配加分,负=排除扣分</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="排除规则">
|
|
|
+ <el-switch v-model="fpForm.isExclusion" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="排序">
|
|
|
+ <el-input-number v-model="fpForm.sortOrder" :min="0" :max="999" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="fpFormVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmitFp" :loading="fpSubmitting">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getReportTypes, saveReportType, toggleReportType, deleteReportType,
|
|
|
+ getFingerprints, saveFingerprint, deleteFingerprint } from '@/api/reportParser'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'ReportTypeManagement',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false, submitting: false, fpLoading: false, fpSubmitting: false,
|
|
|
+ types: [], keyword: '',
|
|
|
+ dialogVisible: false, isEdit: false,
|
|
|
+ form: { typeId: '', displayName: '', family: '', description: '', parserPath: '', minConfidence: 10 },
|
|
|
+ fpDialogVisible: false, currentType: null,
|
|
|
+ fingerprints: [], fpFormVisible: false,
|
|
|
+ fpForm: { pattern: '', weight: 10, isExclusion: false, sortOrder: 0 },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ filteredTypes() {
|
|
|
+ if (!this.keyword) return this.types
|
|
|
+ const kw = this.keyword.toLowerCase()
|
|
|
+ return this.types.filter(t =>
|
|
|
+ (t.typeId || '').toLowerCase().includes(kw) ||
|
|
|
+ (t.displayName || '').toLowerCase().includes(kw) ||
|
|
|
+ (t.family || '').toLowerCase().includes(kw))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() { this.fetchTypes() },
|
|
|
+ methods: {
|
|
|
+ async fetchTypes() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ const res = await getReportTypes({ page: 1, size: 999 })
|
|
|
+ this.types = (res.data?.records || res.data || [])
|
|
|
+ } catch (e) { this.$message.error('加载失败') }
|
|
|
+ this.loading = false
|
|
|
+ },
|
|
|
+ handleCreate() {
|
|
|
+ this.isEdit = false
|
|
|
+ this.form = { typeId: '', displayName: '', family: '', description: '', parserPath: '', minConfidence: 10 }
|
|
|
+ this.dialogVisible = true
|
|
|
+ },
|
|
|
+ handleEdit(row) {
|
|
|
+ this.isEdit = true
|
|
|
+ this.form = { ...row }
|
|
|
+ this.dialogVisible = true
|
|
|
+ },
|
|
|
+ async handleSubmit() {
|
|
|
+ this.submitting = true
|
|
|
+ try {
|
|
|
+ await saveReportType(this.form)
|
|
|
+ this.$message.success('保存成功')
|
|
|
+ this.dialogVisible = false
|
|
|
+ this.fetchTypes()
|
|
|
+ } catch (e) { this.$message.error('保存失败') }
|
|
|
+ this.submitting = false
|
|
|
+ },
|
|
|
+ async handleToggle(row) {
|
|
|
+ try {
|
|
|
+ await toggleReportType({ typeId: row.typeId, isActive: !row.isActive })
|
|
|
+ this.$message.success('已更新')
|
|
|
+ this.fetchTypes()
|
|
|
+ } catch (e) { this.$message.error('操作失败') }
|
|
|
+ },
|
|
|
+ handleActionCmd(row, cmd) {
|
|
|
+ if (cmd === 'fingerprints') this.handleFingerprints(row)
|
|
|
+ if (cmd === 'delete') this.handleDelete(row)
|
|
|
+ },
|
|
|
+ async handleDelete(row) {
|
|
|
+ await this.$confirm('确定删除此类型及其指纹规则?', '提示', { type: 'warning' })
|
|
|
+ try {
|
|
|
+ await deleteReportType({ typeId: row.typeId })
|
|
|
+ this.$message.success('已删除')
|
|
|
+ this.fetchTypes()
|
|
|
+ } catch (e) { this.$message.error('删除失败') }
|
|
|
+ },
|
|
|
+ async handleFingerprints(row) {
|
|
|
+ this.currentType = row
|
|
|
+ this.fpDialogVisible = true
|
|
|
+ this.fingerprints = []
|
|
|
+ this.fpLoading = true
|
|
|
+ try {
|
|
|
+ const res = await getFingerprints({ typeId: row.typeId })
|
|
|
+ this.fingerprints = res.data || []
|
|
|
+ } catch (e) { this.$message.error('加载指纹失败') }
|
|
|
+ this.fpLoading = false
|
|
|
+ },
|
|
|
+ handleAddFp() {
|
|
|
+ this.fpForm = { pattern: '', weight: 10, isExclusion: false, sortOrder: 0 }
|
|
|
+ this.fpFormVisible = true
|
|
|
+ },
|
|
|
+ async handleSubmitFp() {
|
|
|
+ this.fpSubmitting = true
|
|
|
+ try {
|
|
|
+ await saveFingerprint({ ...this.fpForm, typeId: this.currentType.typeId })
|
|
|
+ this.$message.success('保存成功')
|
|
|
+ this.fpFormVisible = false
|
|
|
+ this.handleFingerprints(this.currentType)
|
|
|
+ } catch (e) { this.$message.error('保存失败') }
|
|
|
+ this.fpSubmitting = false
|
|
|
+ },
|
|
|
+ async handleDeleteFp(row) {
|
|
|
+ await this.$confirm('确定删除此指纹规则?', '提示', { type: 'warning' })
|
|
|
+ try {
|
|
|
+ await deleteFingerprint({ id: row.id })
|
|
|
+ this.$message.success('已删除')
|
|
|
+ this.handleFingerprints(this.currentType)
|
|
|
+ } catch (e) { this.$message.error('删除失败') }
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.el-form-item-msg { font-size: 12px; color: #999; margin-top: 2px; }
|
|
|
+</style>
|