feat(权限): 新增 v-permission 按钮级权限指令并移除前端权限别名

This commit is contained in:
yelan
2026-09-24 10:44:00 +08:00
parent e5b5412322
commit ec24e85829
11 changed files with 219 additions and 90 deletions
@@ -1,14 +1,16 @@
<script setup lang="ts">
import { DOC_TEMPLATE_PERMISSIONS } from '../permissions'
withDefaults(
defineProps<{
modelValue: string
total: number
/** 只在演示模式下展示「重置演示数据」:真实环境不该出现这个自测入口 */
showReset?: boolean
canCreate?: boolean
canExport?: boolean
/** 模板包导出接口尚未接入,先只在演示模式展示入口;功能上线后放开这里即可 */
showExport?: boolean
}>(),
{ showReset: false, canCreate: false, canExport: false },
{ showReset: false, showExport: false },
)
const emit = defineEmits<{
@@ -42,11 +44,22 @@ function handleInput(event: Event) {
aria-label="搜索模板名称、编号或科室"
@input="handleInput"
/>
<button v-if="canCreate" type="button" class="tool-button primary" @click="emit('add')">
<button
v-permission="DOC_TEMPLATE_PERMISSIONS.create"
type="button"
class="tool-button primary"
@click="emit('add')"
>
<span aria-hidden="true">+</span>
新增文档
</button>
<button v-if="canExport" type="button" class="tool-button secondary" @click="emit('export')">
<button
v-if="showExport"
v-permission="DOC_TEMPLATE_PERMISSIONS.export"
type="button"
class="tool-button secondary"
@click="emit('export')"
>
导出模板包
</button>
<button
@@ -5,13 +5,10 @@ import type { TemplateVersionAction } from '@/api/management/documents'
import type { DocumentTemplate } from '../types'
import { readStatusLabel, readStatusTone } from '../mock'
import { DOC_TEMPLATE_PERMISSIONS, workflowPermission } from '../permissions'
const props = defineProps<{
template: DocumentTemplate
canEdit: boolean
canInitiate: boolean
canWorkflow: (action: TemplateVersionAction) => boolean
canRemove: boolean
/** 演示模式下「删除」是真删;真实模式下语义是下架(模板无 DELETE 接口,走归档) */
isMock: boolean
}>()
@@ -31,16 +28,10 @@ const statusLabel = computed(() => readStatusLabel(props.template))
const statusTone = computed(() => readStatusTone(props.template))
/** 只有已发布 / 待复审的模板可以发起签署;草稿与已归档不可用 */
const canInitiateTemplate = computed(
() => props.canInitiate && Boolean(props.template.currentVersionId || props.isMock),
)
const hasInitiableVersion = computed(() => Boolean(props.template.currentVersionId || props.isMock))
const initiateTitle = computed(() =>
canInitiateTemplate.value
? '用此模板发起签署'
: props.canInitiate
? '仅已发布或待复审的模板可发起签署'
: '当前账号没有发起签署任务的权限',
hasInitiableVersion.value ? '用此模板发起签署' : '仅已发布或待复审的模板可发起签署',
)
/**
@@ -78,7 +69,7 @@ const primaryAction = computed<{ key: TemplateVersionAction; label: string } | n
*/
const secondaryActions = computed<{ key: TemplateVersionAction; label: string; title: string }[]>(
() => {
if (props.template.backendStatus === 'PENDING_REVIEW' && props.canWorkflow('reject')) {
if (props.template.backendStatus === 'PENDING_REVIEW') {
return [
{
key: 'reject',
@@ -94,7 +85,7 @@ const secondaryActions = computed<{ key: TemplateVersionAction; label: string; t
/**
* 本次交付暂不包含「版本历史」与「删除 / 下架」入口,先隐藏对应按钮。
* 相关 emit、页面处理函数与接口调用保持原样,需要时把开关改回 true 即可恢复。
* 权限码已按 doc:template:hist / doc:template:stop 接好,需要恢复时把开关改回 true 即可。
*/
const showVersionAction = false
const showRemoveAction = false
@@ -124,11 +115,17 @@ const removeTitle = computed(() => (props.isMock ? '删除此文书' : '下架
</span>
<div class="template-actions">
<button type="button" class="card-button" @click="emit('preview', template)">预览</button>
<button v-if="canEdit" type="button" class="card-button" @click="emit('edit', template)">
<button
v-permission="DOC_TEMPLATE_PERMISSIONS.edit"
type="button"
class="card-button"
@click="emit('edit', template)"
>
编辑
</button>
<button
v-if="showVersionAction && canInitiate"
v-if="showVersionAction"
v-permission="DOC_TEMPLATE_PERMISSIONS.history"
type="button"
class="card-button"
title="查看该文书的版本历史"
@@ -137,7 +134,8 @@ const removeTitle = computed(() => (props.isMock ? '删除此文书' : '下架
版本
</button>
<button
v-if="canInitiateTemplate"
v-if="hasInitiableVersion"
v-permission="DOC_TEMPLATE_PERMISSIONS.initiate"
type="button"
class="card-button primary"
:title="initiateTitle"
@@ -147,7 +145,8 @@ const removeTitle = computed(() => (props.isMock ? '删除此文书' : '下架
</button>
<!-- 还不能签署时,把「当前该做的下一步」放在同一位置,避免按钮越堆越多 -->
<button
v-if="primaryAction && canWorkflow(primaryAction.key)"
v-if="primaryAction"
v-permission="workflowPermission(primaryAction.key)"
type="button"
class="card-button primary"
:title="`${primaryAction.label}后才能发起签署`"
@@ -158,6 +157,7 @@ const removeTitle = computed(() => (props.isMock ? '删除此文书' : '下架
<button
v-for="action in secondaryActions"
:key="action.key"
v-permission="workflowPermission(action.key)"
type="button"
class="card-button"
:title="action.title"
@@ -166,7 +166,8 @@ const removeTitle = computed(() => (props.isMock ? '删除此文书' : '下架
{{ action.label }}
</button>
<button
v-if="showRemoveAction && canRemove"
v-if="showRemoveAction"
v-permission="DOC_TEMPLATE_PERMISSIONS.stop"
type="button"
class="card-button danger"
:title="removeTitle"
@@ -14,6 +14,7 @@ import {
} from '@/utils/signing-fields'
import EditorWangPane from './editors/EditorWangPane.vue'
import { DOC_TEMPLATE_PERMISSIONS } from '../permissions'
import type { DocumentTemplate, TemplateEditorForm } from '../types'
const props = defineProps<{
@@ -30,6 +31,11 @@ const emit = defineEmits<{
save: [form: TemplateEditorForm]
}>()
/** 新增文书用创建权限,编辑已有文书用编辑权限;后端仍会再校验一次 */
const savePermission = computed(() =>
props.template ? DOC_TEMPLATE_PERMISSIONS.edit : DOC_TEMPLATE_PERMISSIONS.create,
)
// Word 自维护方案:科室把文书从 Word/WPS 直接粘贴进来(保留表格与基本格式),
// 或上传 .docx 由 mammoth 转换;签署时由交互管线识别"勾选"表并转为签署域。
// 编辑引擎固定为 WangEditor(MIT)。更换引擎只需新增一个实现 EditorPaneHandle 的面板组件,
@@ -389,7 +395,14 @@ function save() {
<footer class="dialog-footer">
<p>保存后进入文书库;签署时自动填充占位符,"勾选"表转为交互勾选。</p>
<button type="button" class="footer-button secondary" @click="emit('close')">取消</button>
<button type="button" class="footer-button primary" @click="save">保存文书</button>
<button
v-permission.disable="savePermission"
type="button"
class="footer-button primary"
@click="save"
>
保存文书
</button>
</footer>
</section>
</div>
@@ -716,6 +729,14 @@ function save() {
filter: brightness(0.96);
}
.footer-button:disabled {
color: #aebbc0;
cursor: not-allowed;
background: #f1f5f6;
border-color: #dce5e8;
filter: none;
}
@media (max-width: 900px) {
.meta-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));