You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
682 lines
22 KiB
682 lines
22 KiB
<template> |
|
<div class="app-container points-management"> |
|
<el-tabs v-model="activeTab"> |
|
<el-tab-pane label="积分规则" name="rules"> |
|
<data-list-panel modern-table> |
|
<template #filters> |
|
<el-form |
|
ref="queryForm" |
|
:model="queryParams" |
|
size="small" |
|
:inline="true" |
|
class="data-list-filter-form" |
|
> |
|
<el-form-item v-if="isSuperAdmin" label="厂区" prop="deptId"> |
|
<el-select |
|
v-model="queryParams.deptId" |
|
clearable |
|
filterable |
|
placeholder="全部厂区" |
|
@change="handleQuery" |
|
> |
|
<el-option |
|
v-for="item in factoryOptions" |
|
:key="String(item.deptId || item.id)" |
|
:label="item.deptName || item.label || item.name" |
|
:value="String(item.deptId || item.id)" |
|
/> |
|
</el-select> |
|
</el-form-item> |
|
<el-form-item label="隐患等级" prop="hazardLevel"> |
|
<el-select |
|
v-model="queryParams.hazardLevel" |
|
clearable |
|
placeholder="全部等级" |
|
@change="handleQuery" |
|
> |
|
<el-option |
|
v-for="item in levelOptions" |
|
:key="item.value" |
|
:label="item.label" |
|
:value="item.value" |
|
/> |
|
</el-select> |
|
</el-form-item> |
|
<el-form-item label="启用状态" prop="enabled"> |
|
<el-select |
|
v-model="queryParams.enabled" |
|
clearable |
|
placeholder="全部状态" |
|
@change="handleQuery" |
|
> |
|
<el-option label="启用" :value="1" /> |
|
<el-option label="停用" :value="0" /> |
|
</el-select> |
|
</el-form-item> |
|
<el-form-item> |
|
<el-button |
|
type="primary" |
|
icon="el-icon-search" |
|
@click="handleQuery" |
|
>查询</el-button> |
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> |
|
</el-form-item> |
|
</el-form> |
|
</template> |
|
<template #actions> |
|
<div class="data-list-actions"> |
|
<el-button |
|
type="primary" |
|
size="small" |
|
icon="el-icon-plus" |
|
@click="openAdd" |
|
>新建</el-button> |
|
</div> |
|
</template> |
|
<template #header> |
|
<div class="rule-heading"> |
|
<div class="rule-heading__title">积分规则配置 · 多档位</div> |
|
<div class="rule-heading__description"> |
|
同一隐患等级支持 A/B/C 多档积分标准,档位由后端规则引擎按“等级 × 档位”匹配。 |
|
</div> |
|
</div> |
|
</template> |
|
<el-table |
|
v-loading="loading" |
|
:data="ruleList" |
|
border |
|
class="data-list-table" |
|
empty-text="暂无积分规则" |
|
> |
|
<el-table-column label="隐患等级" min-width="180"> |
|
<template slot-scope="scope"> |
|
<el-tag :type="levelTagType(scope.row)" size="small"> |
|
{{ levelLabel(scope.row) }} |
|
</el-tag> |
|
</template> |
|
</el-table-column> |
|
<el-table-column |
|
label="厂区" |
|
min-width="160" |
|
show-overflow-tooltip |
|
> |
|
<template slot-scope="scope"> |
|
{{ factoryLabel(scope.row) }} |
|
</template> |
|
</el-table-column> |
|
<el-table-column label="档位" width="120" align="center"> |
|
<template slot-scope="scope"> |
|
<span class="tier-text">{{ tierLabel(scope.row) }}</span> |
|
</template> |
|
</el-table-column> |
|
<el-table-column prop="points" label="积分" width="120" align="center" /> |
|
<el-table-column label="启用" width="120" align="center"> |
|
<template slot-scope="scope"> |
|
<el-switch |
|
:value="toEnabled(scope.row.enabled)" |
|
:active-value="1" |
|
:inactive-value="0" |
|
@change="toggleEnabled(scope.row, $event)" |
|
/> |
|
</template> |
|
</el-table-column> |
|
<el-table-column |
|
label="操作" |
|
width="160" |
|
align="center" |
|
fixed="right" |
|
class-name="data-list-operation-column" |
|
> |
|
<template slot-scope="scope"> |
|
<el-button size="small" @click="openDetail(scope.row)">详情</el-button> |
|
<el-button |
|
type="danger" |
|
size="small" |
|
@click="handleDelete(scope.row)" |
|
>删除</el-button> |
|
</template> |
|
</el-table-column> |
|
</el-table> |
|
<template #footer> |
|
<pagination |
|
v-show="total > 0" |
|
:total="total" |
|
:page.sync="queryParams.pageNum" |
|
:limit.sync="queryParams.pageSize" |
|
@pagination="getList" |
|
/> |
|
<div class="rule-version"> |
|
<div class="rule-version__actions"> |
|
<el-button |
|
type="primary" |
|
size="small" |
|
@click="showPendingAction('保存为新版本 V4')" |
|
>保存为新版本 V4</el-button> |
|
<el-button |
|
size="small" |
|
@click="showPendingAction('历史月份试算')" |
|
>历史月份试算</el-button> |
|
</div> |
|
<div class="rule-version__note"> |
|
档位标准业主正在内部商讨,以上为演示配置;档位匹配逻辑由后端规则引擎判定(等级 × 档位 → 积分)。 |
|
</div> |
|
</div> |
|
</template> |
|
</data-list-panel> |
|
</el-tab-pane> |
|
><el-tab-pane label="申诉处理" name="appeal"> |
|
<appeal-ledger-panel v-if="activeTab === 'appeal'" /> |
|
</el-tab-pane><el-tab-pane |
|
label="手动调整" |
|
name="adjust" |
|
disabled /><el-tab-pane label="流水冲正" name="reverse"> |
|
<transaction-reverse-panel v-if="activeTab === 'reverse'" /> |
|
</el-tab-pane></el-tabs |
|
><el-dialog |
|
title="积分规则详情" |
|
:visible.sync="detailVisible" |
|
width="560px" |
|
append-to-body |
|
><el-descriptions v-if="detail" :column="2" border size="small" |
|
><el-descriptions-item label="厂区">{{ |
|
factoryLabel(detail) |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="隐患等级">{{ |
|
levelLabel(detail) |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="档位">{{ |
|
tierLabel(detail) |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="积分">{{ |
|
detail.points == null ? "—" : detail.points |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="启用状态">{{ |
|
toEnabled(detail.enabled) ? "启用" : "停用" |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="优先级">{{ |
|
detail.priority || "—" |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="规则编码">{{ |
|
detail.ruleCode || "—" |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="规则名称">{{ |
|
detail.ruleName || "—" |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="备注" :span="2">{{ |
|
detail.remark || "—" |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="创建时间">{{ |
|
formatTime(detail.createTime) |
|
}}</el-descriptions-item |
|
><el-descriptions-item label="更新时间">{{ |
|
formatTime(detail.updateTime) |
|
}}</el-descriptions-item></el-descriptions |
|
><el-empty v-else description="暂无详情" /></el-dialog |
|
><el-dialog |
|
title="新建积分规则" |
|
:visible.sync="addVisible" |
|
width="560px" |
|
append-to-body |
|
><el-form |
|
ref="addForm" |
|
:model="addForm" |
|
:rules="addRules" |
|
label-width="92px" |
|
size="small" |
|
><el-form-item label="所属厂区" prop="deptId" |
|
><el-select |
|
v-model="addForm.deptId" |
|
:disabled="!isSuperAdmin" |
|
:loading="factoryLoading" |
|
filterable |
|
placeholder="请选择厂区" |
|
style="width: 100%" |
|
><el-option |
|
v-for="item in displayedFactoryOptions" |
|
:key="factoryId(item)" |
|
:label="factoryName(item)" |
|
:value="factoryId(item)" /></el-select></el-form-item |
|
><el-form-item label="隐患等级" prop="hazardLevel" |
|
><el-select |
|
v-model="addForm.hazardLevel" |
|
placeholder="请选择隐患等级" |
|
style="width: 100%" |
|
@change="suggestTier" |
|
><el-option |
|
v-for="item in levelOptions" |
|
:key="item.value" |
|
:label="item.label" |
|
:value="item.value" /></el-select></el-form-item |
|
><el-form-item label="档位" prop="hazardCategory" |
|
><el-input |
|
v-model.trim="addForm.hazardCategory" |
|
maxlength="4" |
|
placeholder="如 A、B、C" |
|
@input="normalizeTier" /></el-form-item |
|
><el-form-item label="积分" prop="points" |
|
><el-input-number |
|
v-model="addForm.points" |
|
:min="0" |
|
:precision="0" |
|
style="width: 100%" /></el-form-item |
|
><el-form-item label="优先级" |
|
><el-input-number |
|
v-model="addForm.priority" |
|
:min="1" |
|
:precision="0" |
|
style="width: 100%" /></el-form-item |
|
><el-form-item label="规则编码" |
|
><el-input v-model.trim="addForm.ruleCode" /></el-form-item |
|
><el-form-item |
|
label="规则名称" |
|
prop="ruleName" |
|
><el-input v-model.trim="addForm.ruleName" /></el-form-item |
|
><el-form-item label="备注" |
|
><el-input |
|
v-model.trim="addForm.remark" |
|
type="textarea" |
|
:rows="2" /></el-form-item></el-form |
|
><span slot="footer" |
|
><el-button @click="addVisible = false">取消</el-button |
|
><el-button |
|
type="primary" |
|
:loading="saving" |
|
:disabled="factoryLoading || !canSubmitAdd" |
|
@click="submitAdd" |
|
>确定</el-button |
|
></span |
|
></el-dialog |
|
> |
|
</div> |
|
</template> |
|
<script> |
|
import { |
|
listPointsRules, |
|
togglePointsRule, |
|
deletePointsRule, |
|
getPointsRule, |
|
addPointsRule, |
|
} from "@/api/points/rule"; |
|
import { |
|
listFactoryDepartments, |
|
getMyFactoryDepartment, |
|
} from "@/api/system/dept"; |
|
import TransactionReversePanel from "./components/TransactionReversePanel"; |
|
import AppealLedgerPanel from "@/views/points/appeal/components/AppealLedgerPanel"; |
|
const LEVELS = [ |
|
{ value: "general", label: "一般隐患" }, |
|
{ value: "major", label: "较大隐患" }, |
|
{ value: "critical", label: "重大隐患" }, |
|
]; |
|
export default { |
|
name: "PointsManagement", |
|
components: { TransactionReversePanel, AppealLedgerPanel }, |
|
data() { |
|
const deptRequired = (rule, value, callback) => { |
|
if (!value) callback(new Error("请选择所属厂区")); |
|
else callback(); |
|
}; |
|
return { |
|
activeTab: "rules", |
|
loading: false, |
|
factoryLoading: false, |
|
saving: false, |
|
total: 0, |
|
ruleList: [], |
|
factoryOptions: [], |
|
currentFactory: null, |
|
detail: null, |
|
detailVisible: false, |
|
addVisible: false, |
|
levelOptions: LEVELS, |
|
queryParams: { |
|
pageNum: 1, |
|
pageSize: 10, |
|
deptId: undefined, |
|
hazardLevel: undefined, |
|
enabled: undefined, |
|
}, |
|
addForm: { |
|
deptId: "", |
|
hazardLevel: "", |
|
hazardCategory: "", |
|
points: null, |
|
priority: 1, |
|
ruleCode: "", |
|
ruleName: "", |
|
remark: "", |
|
}, |
|
addRules: { |
|
deptId: [{ validator: deptRequired, trigger: "change" }], |
|
hazardLevel: [ |
|
{ required: true, message: "请选择隐患等级", trigger: "change" }, |
|
], |
|
hazardCategory: [ |
|
{ required: true, message: "请输入档位", trigger: "blur" }, |
|
{ |
|
pattern: /^[A-Z]+$/, |
|
message: "档位请输入大写英文字母", |
|
trigger: "blur", |
|
}, |
|
], |
|
points: [{ required: true, message: "请输入积分", trigger: "change" }], |
|
ruleName: [ |
|
{ required: true, message: '请输入规则名称', trigger: 'blur' } |
|
] |
|
}, |
|
}; |
|
}, |
|
computed: { |
|
isSuperAdmin() { |
|
return (this.$store.getters.roles || []).indexOf("admin") !== -1; |
|
}, |
|
displayedFactoryOptions() { |
|
if (this.isSuperAdmin) return this.factoryOptions; |
|
return this.currentFactory ? [this.currentFactory] : []; |
|
}, |
|
canSubmitAdd() { |
|
return this.isSuperAdmin || Boolean(this.factoryId(this.currentFactory)); |
|
}, |
|
}, |
|
created() { |
|
this.getList(); |
|
this.loadFactoryScope(); |
|
}, |
|
methods: { |
|
getList() { |
|
this.loading = true; |
|
const params = Object.assign({}, this.queryParams); |
|
if (!this.isSuperAdmin) delete params.deptId; |
|
listPointsRules(params) |
|
.then((res) => { |
|
const page = res.data || res; |
|
this.ruleList = this.sortRules(page.list || page.rows || []); |
|
this.total = Number( |
|
page.total || page.totalCount || this.ruleList.length |
|
); |
|
}) |
|
.catch(() => { |
|
this.ruleList = []; |
|
this.total = 0; |
|
this.$modal.msgError("积分规则加载失败"); |
|
}) |
|
.finally(() => { |
|
this.loading = false; |
|
}); |
|
}, |
|
loadFactoryScope() { |
|
this.factoryLoading = true; |
|
const request = this.isSuperAdmin |
|
? listFactoryDepartments() |
|
: getMyFactoryDepartment(); |
|
request |
|
.then((res) => { |
|
const data = res.data || res; |
|
if (this.isSuperAdmin) { |
|
this.factoryOptions = Array.isArray(data) |
|
? data |
|
: data.list || data.rows || []; |
|
return; |
|
} |
|
this.currentFactory = data || null; |
|
if (this.addVisible) { |
|
this.addForm.deptId = this.factoryId(this.currentFactory); |
|
} |
|
}) |
|
.catch(() => { |
|
this.factoryOptions = []; |
|
this.currentFactory = null; |
|
this.$modal.msgError("厂区信息加载失败"); |
|
}) |
|
.finally(() => { |
|
this.factoryLoading = false; |
|
}); |
|
}, |
|
sortRules(rows) { |
|
const order = { general: 0, major: 1, critical: 2 }; |
|
return rows |
|
.slice() |
|
.sort( |
|
(a, b) => |
|
(order[String(a.hazardLevel).toLowerCase()] == null |
|
? 99 |
|
: order[String(a.hazardLevel).toLowerCase()]) - |
|
(order[String(b.hazardLevel).toLowerCase()] == null |
|
? 99 |
|
: order[String(b.hazardLevel).toLowerCase()]) || |
|
this.tierLabel(a).localeCompare(this.tierLabel(b)) || |
|
Number(b.priority || 0) - Number(a.priority || 0) |
|
); |
|
}, |
|
handleQuery() { |
|
this.queryParams.pageNum = 1; |
|
this.getList(); |
|
}, |
|
resetQuery() { |
|
this.$refs.queryForm.resetFields(); |
|
this.handleQuery(); |
|
}, |
|
openAdd() { |
|
this.addForm = { |
|
deptId: this.isSuperAdmin ? "" : this.factoryId(this.currentFactory), |
|
hazardLevel: "", |
|
hazardCategory: "", |
|
points: null, |
|
priority: 1, |
|
ruleCode: "", |
|
ruleName: "", |
|
remark: "", |
|
}; |
|
this.addVisible = true; |
|
this.$nextTick(() => this.$refs.addForm.clearValidate()); |
|
}, |
|
suggestTier() { |
|
this.addForm.hazardCategory = this.nextTier(this.addForm.hazardLevel); |
|
this.addForm.priority = this.nextPriority(this.addForm.hazardLevel); |
|
}, |
|
nextPriority(level) { |
|
const values = this.ruleList |
|
.filter( |
|
(row) => |
|
String(row.hazardLevel).toLowerCase() === |
|
String(level).toLowerCase() |
|
) |
|
.map((row) => Number(row.priority) || 0); |
|
return (values.length ? Math.max.apply(null, values) : 0) + 1; |
|
}, |
|
nextTier(level) { |
|
const letters = this.ruleList |
|
.filter( |
|
(row) => |
|
String(row.hazardLevel).toLowerCase() === |
|
String(level).toLowerCase() |
|
) |
|
.map(this.tierLabel); |
|
let max = 0; |
|
letters.forEach((value) => { |
|
let n = 0; |
|
for (let i = 0; i < value.length; i++) |
|
n = n * 26 + value.charCodeAt(i) - 64; |
|
max = Math.max(max, n); |
|
}); |
|
return this.numberToLetters(max + 1); |
|
}, |
|
numberToLetters(number) { |
|
let result = ""; |
|
while (number > 0) { |
|
result = String.fromCharCode(65 + ((number - 1) % 26)) + result; |
|
number = Math.floor((number - 1) / 26); |
|
} |
|
return result || "A"; |
|
}, |
|
normalizeTier(value) { |
|
this.addForm.hazardCategory = String(value || "") |
|
.replace(/[^a-z]/gi, "") |
|
.toUpperCase(); |
|
}, |
|
submitAdd() { |
|
if (!this.canSubmitAdd) { |
|
this.$modal.msgError("当前厂区信息未加载,暂不能新增积分规则"); |
|
return; |
|
} |
|
this.$refs.addForm.validate((valid) => { |
|
if (!valid) return; |
|
const payload = { |
|
hazardLevel: this.addForm.hazardLevel, |
|
hazardCategory: this.addForm.hazardCategory, |
|
points: this.addForm.points, |
|
enabled: 1, |
|
priority: this.addForm.priority, |
|
ruleCode: this.addForm.ruleCode || undefined, |
|
ruleName: this.addForm.ruleName, |
|
remark: this.addForm.remark || undefined, |
|
}; |
|
if (this.isSuperAdmin) { |
|
payload.deptId = this.addForm.deptId; |
|
const item = this.factoryOptions.find( |
|
(row) => this.factoryId(row) === String(payload.deptId) |
|
); |
|
if (item) payload.deptName = this.factoryName(item); |
|
} |
|
this.saving = true; |
|
addPointsRule(payload) |
|
.then(() => { |
|
this.$modal.msgSuccess("新增积分规则成功"); |
|
this.addVisible = false; |
|
this.getList(); |
|
}) |
|
.finally(() => { |
|
this.saving = false; |
|
}); |
|
}); |
|
}, |
|
toggleEnabled(row, value) { |
|
const old = this.toEnabled(row.enabled); |
|
togglePointsRule(row.id, Number(value)) |
|
.then(() => { |
|
row.enabled = Number(value); |
|
this.$modal.msgSuccess("操作成功"); |
|
}) |
|
.catch(() => { |
|
row.enabled = old; |
|
}); |
|
}, |
|
handleDelete(row) { |
|
this.$confirm("确认删除该积分规则吗?删除后不可恢复。", "删除确认", { |
|
type: "warning", |
|
}) |
|
.then(() => deletePointsRule(row.id)) |
|
.then(() => { |
|
this.$modal.msgSuccess("删除成功"); |
|
this.getList(); |
|
}); |
|
}, |
|
openDetail(row) { |
|
this.detailVisible = true; |
|
this.detail = null; |
|
getPointsRule(row.id).then((res) => { |
|
this.detail = res.data || res; |
|
}); |
|
}, |
|
showPendingAction(name) { |
|
this.$modal.msgInfo(`${name}功能暂未接入接口`); |
|
}, |
|
factoryId(item) { |
|
const value = |
|
item && item.deptId !== undefined ? item.deptId : item && item.id; |
|
return value === undefined || value === null ? "" : String(value); |
|
}, |
|
factoryName(item) { |
|
return (item && (item.deptName || item.label || item.name)) || ""; |
|
}, |
|
factoryLabel(row) { |
|
return this.factoryName(row) || this.factoryId(row) || "全局规则"; |
|
}, |
|
toEnabled(value) { |
|
return Number(value) === 1 ? 1 : 0; |
|
}, |
|
tierLabel(row) { |
|
return String(row.hazardCategory || "—").toUpperCase(); |
|
}, |
|
levelLabel(row) { |
|
return ( |
|
row.hazardLevelName || |
|
( |
|
LEVELS.find( |
|
(item) => item.value === String(row.hazardLevel).toLowerCase() |
|
) || {} |
|
).label || |
|
row.hazardLevel || |
|
"—" |
|
); |
|
}, |
|
levelTagType(row) { |
|
return String(row.hazardLevel).toLowerCase() === "critical" |
|
? "danger" |
|
: String(row.hazardLevel).toLowerCase() === "major" |
|
? "warning" |
|
: "info"; |
|
}, |
|
formatTime(value) { |
|
return value ? String(value).replace("T", " ").slice(0, 19) : "—"; |
|
}, |
|
}, |
|
}; |
|
</script> |
|
<style lang="scss" scoped> |
|
.points-management { |
|
min-height: calc(100vh - 52px); |
|
background: #f0f2f5; |
|
} |
|
.rule-heading { |
|
min-width: 0; |
|
} |
|
.rule-heading__title { |
|
color: #1a1a2e; |
|
font-size: 14px; |
|
font-weight: 600; |
|
line-height: 20px; |
|
} |
|
.rule-heading__description { |
|
margin-top: 6px; |
|
color: #9898ae; |
|
font-size: 11px; |
|
font-weight: 400; |
|
line-height: 18px; |
|
} |
|
.tier-text { |
|
display: inline-flex; |
|
padding: 1px 8px; |
|
color: #3b5fd9; |
|
background: #eef1fe; |
|
border: 1px solid #c7d2f8; |
|
border-radius: 8px; |
|
font-size: 11px; |
|
line-height: 18px; |
|
font-weight: 500; |
|
} |
|
.rule-version { |
|
margin-top: 12px; |
|
padding-top: 16px; |
|
border-top: 1px solid #f0f0f2; |
|
} |
|
.rule-version__actions { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
} |
|
.rule-version__actions .el-button + .el-button { |
|
margin-left: 0; |
|
} |
|
.rule-version__note { |
|
margin-top: 10px; |
|
color: #9898ae; |
|
font-size: 11px; |
|
line-height: 18px; |
|
} |
|
@media (max-width: 800px) { |
|
.rule-version__actions { |
|
flex-wrap: wrap; |
|
} |
|
} |
|
</style>
|
|
|