ow
This commit is contained in:
1 parent
23d14ba54c
commit
dcc112c076
17 files changed
+636
-31
No files matched your search
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 帮助文档预览的渲染视图模型:help-doc-preview 组件与调用它的页面共用。
|
||||
*
|
||||
* 单独放一个 .ts 是因为 <script setup> 里不能写 ES module export,
|
||||
* 类型只能外提到这里,组件和页面各自 import。
|
||||
*/
|
||||
|
||||
/** 一条操作步骤 */
|
||||
export interface HelpStepVM {
|
||||
title: string;
|
||||
desc: string;
|
||||
tips?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/** 一个功能点 / 图标说明 */
|
||||
export interface HelpFeatureVM {
|
||||
name: string;
|
||||
desc: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
}
|
||||
@@ -57,6 +57,11 @@ function actionVisible(action: AdminFormAction): boolean {
|
||||
|
||||
const visibleActions = computed(() => props.actions.filter(actionVisible));
|
||||
|
||||
/** 导航栏返回:u-navbar 没有 left-click 事件,只能通过 custom-back 回调接接;抛给页面 @back(内部走带兜底的 smartBack) */
|
||||
function onNavBack(): void {
|
||||
emit("back");
|
||||
}
|
||||
|
||||
function rawValue(field: AdminFormField): unknown {
|
||||
return props.model?.[field.prop];
|
||||
}
|
||||
@@ -118,7 +123,7 @@ function onTapAction(action: AdminFormAction): void {
|
||||
<template>
|
||||
<view class="admin-form">
|
||||
<u-navbar v-if="navTitle" :title="navTitle" :is-back="showNavBack" :is-fixed="false" :immersive="false"
|
||||
back-icon-name="arrow-leftward" @left-click="emit('back')" />
|
||||
back-icon-name="arrow-leftward" :custom-back="onNavBack" />
|
||||
|
||||
<scroll-view class="form-body" scroll-y :show-scrollbar="false">
|
||||
<view class="hero">
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
restColumns,
|
||||
rowKeyOf,
|
||||
} from "@/admin/admin-column";
|
||||
import { smartBack } from "@/router/routes";
|
||||
|
||||
/** 列表取数:只给页码与每页条数,条件由上层闭包注入(见 useAdminList.query) */
|
||||
type AdminListLoader = (page: number, pageSize: number) => Promise<AdminLoadResult>;
|
||||
@@ -200,6 +201,11 @@ function refresh(): void {
|
||||
paging.value?.reload();
|
||||
}
|
||||
|
||||
/** 导航栏返回:走带兜底的 smartBack,无栈时回「我的」页(security 域统一落地),避免 H5 刷新后点了没反应 */
|
||||
function onNavBack(): void {
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
defineExpose({ refresh, clearSelection, selectedRows });
|
||||
</script>
|
||||
|
||||
@@ -209,7 +215,7 @@ defineExpose({ refresh, clearSelection, selectedRows });
|
||||
:loading-more-enabled="true" :empty-view-text="t('AdminPage.emptyText')" @query="onQuery">
|
||||
<template #top>
|
||||
<u-navbar v-if="navTitle" :title="navTitle" :is-back="showNavBack" :is-fixed="false" :immersive="false"
|
||||
back-icon-name="arrow-leftward" />
|
||||
back-icon-name="arrow-leftward" :custom-back="onNavBack" />
|
||||
<view class="toolbar">
|
||||
<view class="toolbar-search">
|
||||
<u-search v-model="keywordModel" :placeholder="t('AdminPage.searchPlaceholder')"
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 帮助文档预览弹窗:旧 WebVue components/basic/HelpTimelineModal.vue 的移动端等价物。
|
||||
*
|
||||
* 用途:在「帮助功能详情」里点「预览」,把当前正在编辑/查看的这份帮助数据
|
||||
* 按最终用户看到的样子渲染出来——上方时间轴讲「操作流程」,下方网格讲「功能点与图标说明」。
|
||||
*
|
||||
* 与旧弹窗一致的结构:
|
||||
* - 头部:圆形图标徽标 + 标题(+ u-popup 自带右上角关闭);
|
||||
* - 功能简介 intro;
|
||||
* - 操作流程:竖排时间轴,每步一个圆形徽标(有图标显图标、否则显序号)+ 序号胶囊 + 标题 + 说明 + 可选「提示」黄条;
|
||||
* - 功能点与图标说明:图标徽标 + 名称 + 作用说明。
|
||||
*
|
||||
* 主题色沿用本项目青绿主色(旧弹窗的紫色是它所属分组的主题色,由调用方传入;
|
||||
* 这里是通用预览,统一用 $u-type-primary 与全站一致)。
|
||||
*
|
||||
* 图标:帮助数据里存的是旧项目的图标名(Ant Design 组件名如 DeleteOutlined,或 iconfont 名 icon-xxx),
|
||||
* 移动端渲染不了这些,逐个翻译到 uview-pro 内置图标名,认不出的兜底成 question-circle
|
||||
* (与旧弹窗取不到图标时退化成问号的行为一致)。
|
||||
*/
|
||||
import type { PropType } from "vue";
|
||||
import type { HelpFeatureVM, HelpStepVM } from "@/admin/help-preview-types";
|
||||
import { useLocale } from "uview-pro";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 弹窗标题:一般是功能的本地化名称,缺省显示「使用帮助」 */
|
||||
title: { type: String, default: "" },
|
||||
intro: { type: String, default: "" },
|
||||
steps: { type: Array as PropType<HelpStepVM[]>, default: () => [] },
|
||||
features: { type: Array as PropType<HelpFeatureVM[]>, default: () => [] },
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "close"): void;
|
||||
}>();
|
||||
|
||||
const { t } = useLocale();
|
||||
|
||||
/** 旧项目图标名 -> uview-pro 内置图标名(只认这张表里的名字,否则渲染空白) */
|
||||
const ICON_MAP: Record<string, string> = {
|
||||
FileSearchOutlined: "search",
|
||||
SearchOutlined: "search",
|
||||
FilterOutlined: "search",
|
||||
ClearOutlined: "close-circle",
|
||||
FileAddOutlined: "plus-circle",
|
||||
PlusOutlined: "plus",
|
||||
PlusCircleOutlined: "plus-circle",
|
||||
EditOutlined: "edit-pen",
|
||||
FormOutlined: "edit-pen",
|
||||
SaveOutlined: "checkmark-circle",
|
||||
CheckOutlined: "checkmark",
|
||||
CheckCircleOutlined: "checkmark-circle",
|
||||
UndoOutlined: "reload",
|
||||
ReloadOutlined: "reload",
|
||||
SyncOutlined: "reload",
|
||||
DeleteOutlined: "trash",
|
||||
UpSquareTwoTone: "arrow-up",
|
||||
ArrowUpOutlined: "arrow-up",
|
||||
DownSquareTwoTone: "arrow-down",
|
||||
ArrowDownOutlined: "arrow-down",
|
||||
LeftCircleOutlined: "arrow-left",
|
||||
ArrowLeftOutlined: "arrow-left",
|
||||
RightCircleOutlined: "arrow-right",
|
||||
ArrowRightOutlined: "arrow-right",
|
||||
HomeOutlined: "home",
|
||||
AppstoreOutlined: "grid",
|
||||
LaptopOutlined: "grid",
|
||||
FolderOutlined: "grid",
|
||||
PictureOutlined: "photo",
|
||||
FileImageOutlined: "photo",
|
||||
FileTextOutlined: "file-text",
|
||||
ProfileOutlined: "file-text",
|
||||
EyeOutlined: "eye",
|
||||
DownloadOutlined: "download",
|
||||
CloudDownloadOutlined: "download",
|
||||
ShareAltOutlined: "share",
|
||||
StarOutlined: "star",
|
||||
HeartOutlined: "heart",
|
||||
UserOutlined: "account",
|
||||
TeamOutlined: "account",
|
||||
SettingOutlined: "setting",
|
||||
ClockCircleOutlined: "clock",
|
||||
InfoCircleOutlined: "info-circle",
|
||||
CloseCircleOutlined: "close-circle",
|
||||
QuestionCircleOutlined: "question-circle",
|
||||
CameraOutlined: "camera",
|
||||
PlayCircleOutlined: "play-circle",
|
||||
VideoCameraOutlined: "camera",
|
||||
LockOutlined: "lock",
|
||||
MailOutlined: "email",
|
||||
PhoneOutlined: "phone",
|
||||
CalendarOutlined: "calendar",
|
||||
MapOutlined: "map",
|
||||
};
|
||||
|
||||
/** 兜底图标:与旧弹窗取不到图标时退化成一问号的行为一致 */
|
||||
const FALLBACK_ICON = "question-circle";
|
||||
|
||||
/** 把存储的图标名解析成 u-icon 认得的内置名;icon-* 是旧 iconfont,移动端没有,一律兜底 */
|
||||
function resolveIcon(name?: string): string {
|
||||
const key = String(name ?? "").trim();
|
||||
if (!key || key.startsWith("icon-")) {
|
||||
return FALLBACK_ICON;
|
||||
}
|
||||
return ICON_MAP[key] ?? FALLBACK_ICON;
|
||||
}
|
||||
|
||||
/** 步骤徽标:有图标才显图标,否则显序号(对齐旧 htl-dot 的 icon-else-number) */
|
||||
function stepHasIcon(step: HelpStepVM): boolean {
|
||||
return String(step.icon ?? "").trim().length > 0;
|
||||
}
|
||||
|
||||
/** 功能点图标色:数据自带 color 优先,否则用主色 */
|
||||
function featureColor(color?: string): string {
|
||||
return String(color ?? "").trim() || "#059669";
|
||||
}
|
||||
|
||||
const hasContent = computed(
|
||||
() =>
|
||||
Boolean(String(props.intro ?? "").trim())
|
||||
|| props.steps.length > 0
|
||||
|| props.features.length > 0,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<u-popup :model-value="visible" mode="bottom" round="24" :closeable="true" close-icon-pos="top-right"
|
||||
:safe-area-inset-bottom="true" @close="emit('close')">
|
||||
<view class="help-preview">
|
||||
<scroll-view class="hp-body" scroll-y :show-scrollbar="false">
|
||||
<!-- 头部:圆形图标徽标 + 标题 -->
|
||||
<view class="hp-head">
|
||||
<view class="hp-head-icon">
|
||||
<u-icon name="question-circle" size="30" color="#ffffff" />
|
||||
</view>
|
||||
<text class="hp-head-title">
|
||||
{{ title || t("HelpPreview.title") }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 空态:这份帮助还没内容 -->
|
||||
<view v-if="!hasContent" class="hp-empty">
|
||||
<text>
|
||||
{{ t("HelpPreview.empty") }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-else>
|
||||
<!-- 功能简介 -->
|
||||
<text v-if="intro" class="hp-intro">
|
||||
{{ intro }}
|
||||
</text>
|
||||
|
||||
<!-- 操作流程 -->
|
||||
<template v-if="steps.length">
|
||||
<view class="hp-section">
|
||||
<text class="hp-section-title">
|
||||
{{ t("HelpPreview.flow") }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="hp-steps">
|
||||
<view v-for="(step, i) in steps" :key="`step-${i}`" class="hp-step">
|
||||
<view class="hp-rail">
|
||||
<view class="hp-dot">
|
||||
<u-icon v-if="stepHasIcon(step)" :name="resolveIcon(step.icon)" size="22"
|
||||
color="#ffffff" />
|
||||
<text v-else class="hp-dot-no">
|
||||
{{ i + 1 }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="i !== steps.length - 1" class="hp-line" />
|
||||
</view>
|
||||
<view class="hp-step-main">
|
||||
<view class="hp-step-head">
|
||||
<text class="hp-step-no">
|
||||
{{ i + 1 }}
|
||||
</text>
|
||||
<text class="hp-step-title">
|
||||
{{ step.title }}
|
||||
</text>
|
||||
</view>
|
||||
<text v-if="step.desc" class="hp-step-desc">
|
||||
{{ step.desc }}
|
||||
</text>
|
||||
<view v-if="step.tips" class="hp-step-tips">
|
||||
<text class="hp-tips-label">
|
||||
{{ t("HelpPreview.tip") }}
|
||||
</text>
|
||||
<text class="hp-tips-text">
|
||||
{{ step.tips }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 功能点与图标说明 -->
|
||||
<template v-if="features.length">
|
||||
<view class="hp-section">
|
||||
<text class="hp-section-title">
|
||||
{{ t("HelpPreview.features") }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="hp-features">
|
||||
<view v-for="(feat, i) in features" :key="`feat-${i}`" class="hp-feature">
|
||||
<view class="hp-feature-icon">
|
||||
<u-icon :name="resolveIcon(feat.icon)" size="28"
|
||||
:color="featureColor(feat.color)" />
|
||||
</view>
|
||||
<view class="hp-feature-text">
|
||||
<text class="hp-feature-name">
|
||||
{{ feat.name }}
|
||||
</text>
|
||||
<text v-if="feat.desc" class="hp-feature-desc">
|
||||
{{ feat.desc }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 82vh;
|
||||
padding: 8rpx 32rpx 24rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.hp-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: calc(82vh - 32rpx);
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.hp-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 24rpx 4rpx 8rpx;
|
||||
}
|
||||
|
||||
.hp-head-icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
margin-right: 16rpx;
|
||||
background: linear-gradient(135deg, $u-type-primary-dark, $u-type-primary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.hp-head-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
/* 空态 */
|
||||
.hp-empty {
|
||||
padding: 80rpx 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: $u-tips-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 简介 */
|
||||
.hp-intro {
|
||||
display: block;
|
||||
padding: 8rpx 4rpx 4rpx;
|
||||
font-size: 27rpx;
|
||||
line-height: 1.8;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
/* 分节标题:左侧竖条 */
|
||||
.hp-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 32rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.hp-section-title {
|
||||
position: relative;
|
||||
padding-left: 18rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.hp-section-title::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 6rpx;
|
||||
height: 26rpx;
|
||||
content: "";
|
||||
background-color: $u-type-primary;
|
||||
border-radius: 3rpx;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* 时间轴步骤 */
|
||||
.hp-steps {
|
||||
padding-left: 4rpx;
|
||||
}
|
||||
|
||||
.hp-step {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hp-rail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 48rpx;
|
||||
}
|
||||
|
||||
.hp-dot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
background: linear-gradient(135deg, $u-type-primary-dark, $u-type-primary);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4rpx rgba(5, 150, 105, 0.12);
|
||||
}
|
||||
|
||||
.hp-dot-no {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hp-line {
|
||||
flex: 1;
|
||||
min-height: 20rpx;
|
||||
width: 2rpx;
|
||||
margin: 6rpx 0;
|
||||
background-color: $u-border-color;
|
||||
}
|
||||
|
||||
.hp-step-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 0 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.hp-step-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hp-step-no {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 34rpx;
|
||||
height: 34rpx;
|
||||
padding: 0 10rpx;
|
||||
margin-right: 12rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
color: $u-tips-color;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 17rpx;
|
||||
}
|
||||
|
||||
.hp-step-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 29rpx;
|
||||
font-weight: 700;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.hp-step-desc {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.8;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
/* 提示黄条 */
|
||||
.hp-step-tips {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 14rpx 18rpx;
|
||||
margin-top: 14rpx;
|
||||
background-color: #fffbe6;
|
||||
border-left: 6rpx solid #faad14;
|
||||
border-radius: 0 10rpx 10rpx 0;
|
||||
}
|
||||
|
||||
.hp-tips-label {
|
||||
flex-shrink: 0;
|
||||
margin-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #ad6800;
|
||||
}
|
||||
|
||||
.hp-tips-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: #8a6116;
|
||||
}
|
||||
|
||||
/* 功能点网格 */
|
||||
.hp-features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.hp-feature {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding: 20rpx 22rpx;
|
||||
background-color: #fafbfc;
|
||||
border: 1rpx solid #f0f1f3;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.hp-feature-icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
margin-right: 18rpx;
|
||||
background-color: rgba(5, 150, 105, 0.1);
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.hp-feature-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hp-feature-name {
|
||||
display: block;
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.hp-feature-desc {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.7;
|
||||
color: #6b7280;
|
||||
}
|
||||
</style>
|
||||
@@ -208,7 +208,15 @@
|
||||
"stepIconPlaceholder": "Icon name on the page",
|
||||
"featureTitle": "Referenced Feature Groups",
|
||||
"addFeatureBut": "Reference Feature Group",
|
||||
"featureEmpty": "No feature group referenced"
|
||||
"featureEmpty": "No feature group referenced",
|
||||
"previewBut": "Preview"
|
||||
},
|
||||
"HelpPreview": {
|
||||
"title": "Help",
|
||||
"flow": "Operation Steps",
|
||||
"features": "Features & Icons",
|
||||
"tip": "Tip",
|
||||
"empty": "Nothing to preview yet. Fill in the introduction, steps, or reference a feature group first."
|
||||
},
|
||||
"HelpFeaturePage": {
|
||||
"title": "Help Features",
|
||||
|
||||
@@ -208,7 +208,15 @@
|
||||
"stepIconPlaceholder": "页面上的图标名",
|
||||
"featureTitle": "引用的功能点组",
|
||||
"addFeatureBut": "引用功能点组",
|
||||
"featureEmpty": "暂未引用功能点组"
|
||||
"featureEmpty": "暂未引用功能点组",
|
||||
"previewBut": "预览"
|
||||
},
|
||||
"HelpPreview": {
|
||||
"title": "使用帮助",
|
||||
"flow": "操作流程",
|
||||
"features": "功能点与图标说明",
|
||||
"tip": "提示",
|
||||
"empty": "还没有可预览的帮助内容,请先填写功能说明、操作步骤或引用功能点组。"
|
||||
},
|
||||
"HelpFeaturePage": {
|
||||
"title": "帮助功能点查询",
|
||||
|
||||
@@ -21,6 +21,7 @@ import { SecurityActorData } from "@/data/auto/securityActorData";
|
||||
import { SecurityActorGroupViewEntity } from "@/data/auto/securityActorGroupViewEntity";
|
||||
import { SecurityUserActorViewEntity } from "@/data/auto/securityUserActorViewEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityActorService from "@/service/security/securityActorService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -135,7 +136,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityActorService.Delete(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -151,7 +152,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -163,7 +164,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
// 组别选择:跳 group-enquiry(多选),回传后按 GroupName 去重合并
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useAdminPick, useAuthGuard } from "@/composables";
|
||||
import { SecurityFunctionData } from "@/data/auto/securityFunctionData";
|
||||
import { SecurityFunctionMapViewEntity } from "@/data/auto/securityFunctionMapViewEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityFunctionService from "@/service/security/securityFunctionService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -148,7 +149,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityFunctionService.Delete(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -164,7 +165,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -176,7 +177,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
// 权限名称:单选 permission-enquiry,回传写进 FunctionName 之外不影响
|
||||
|
||||
@@ -16,6 +16,7 @@ import { computed, ref } from "vue";
|
||||
import { useAuthGuard } from "@/composables";
|
||||
import { SecurityGeneratorNumberEntity } from "@/data/auto/securityGeneratorNumberEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityGeneratorNumberService from "@/service/security/securityGeneratorNumberService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -120,7 +121,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
|
||||
@@ -22,6 +22,7 @@ import { SecurityGroupData } from "@/data/auto/securityGroupData";
|
||||
import { SecurityGroupPermissionDependentViewDisplayData } from "@/data/auto/securityGroupPermissionDependentViewDisplayData";
|
||||
import { SecurityGroupPermissionViewDisplayData } from "@/data/auto/securityGroupPermissionViewDisplayData";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityGroupService from "@/service/security/securityGroupService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -130,7 +131,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityGroupService.Delete(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -146,7 +147,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -158,7 +159,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
// 权限勾选:四个 boolean 直接落在 DisplayData 上(与旧 GroupPermissionActions 一致)
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useAuthGuard } from "@/composables";
|
||||
import { SecurityHelpFeatureData } from "@/data/auto/securityHelpFeatureData";
|
||||
import { SecurityHelpFeatureEntity } from "@/data/auto/securityHelpFeatureEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityHelpFunctionService from "@/service/security/securityHelpFunctionService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -123,7 +124,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityHelpFunctionService.DeleteByGroupCode(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -139,7 +140,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -151,7 +152,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
function addFeature(): void {
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
* 删除走 DeleteByFunctionName;新建默认 EnabledIndc='Y'。
|
||||
*/
|
||||
import type { AdminFormAction, AdminFormField, AdminFormSection } from "@/admin/admin-types";
|
||||
import type { HelpFeatureVM, HelpStepVM } from "@/admin/help-preview-types";
|
||||
import type { SecurityHelpFeatureEntity } from "@/data/auto/securityHelpFeatureEntity";
|
||||
import type { SecurityHelpFeatureGroupEntity } from "@/data/auto/securityHelpFeatureGroupEntity";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useLocale } from "uview-pro";
|
||||
@@ -22,6 +24,7 @@ import { SecurityHelpFunctionData } from "@/data/auto/securityHelpFunctionData";
|
||||
import { SecurityHelpFunctionFeatureEntity } from "@/data/auto/securityHelpFunctionFeatureEntity";
|
||||
import { SecurityHelpStepEntity } from "@/data/auto/securityHelpStepEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityHelpFunctionService from "@/service/security/securityHelpFunctionService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -37,6 +40,14 @@ const isNew = computed(() => !String(helpFunction.value.CreatedBy ?? "").trim())
|
||||
/** 组表:把功能点上的「组主键」翻译成「组编码」 */
|
||||
const groups = ref<SecurityHelpFeatureGroupEntity[]>([]);
|
||||
|
||||
/** 全部功能点明细(FindAll 带回),预览时按组主键展开成「功能点与图标说明」 */
|
||||
const allFeatures = ref<SecurityHelpFeatureEntity[]>([]);
|
||||
|
||||
/** 预览弹窗状态:openPreview 时按当前界面数据现拼,不额外发请求 */
|
||||
const previewVisible = ref(false);
|
||||
const previewSteps = ref<HelpStepVM[]>([]);
|
||||
const previewFeatures = ref<HelpFeatureVM[]>([]);
|
||||
|
||||
const heroTitle = computed(() => String(helpFunction.value.FunctionName || ""));
|
||||
const heroChips = computed(() => {
|
||||
const chips: string[] = [];
|
||||
@@ -69,7 +80,8 @@ const sections = computed<AdminFormSection[]>(() => {
|
||||
});
|
||||
|
||||
const actions = computed<AdminFormAction[]>(() => {
|
||||
const list: AdminFormAction[] = [];
|
||||
// 预览:不分查看/编辑态都可点,让管理员随时看这份帮助最终长什么样
|
||||
const list: AdminFormAction[] = [{ key: "preview", label: t("HelpFunctionPage.previewBut") }];
|
||||
if (perm.canAmend.value) {
|
||||
list.push({ key: "amend", label: t("AdminPage.amendBut"), when: "view" });
|
||||
}
|
||||
@@ -100,6 +112,7 @@ async function loadGroups(): Promise<void> {
|
||||
try {
|
||||
const all = await securityHelpFunctionService.FindAll();
|
||||
groups.value = all?.SecurityHelpFeatureGroups ?? [];
|
||||
allFeatures.value = all?.SecurityHelpFeatures ?? [];
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(error);
|
||||
@@ -166,11 +179,14 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityHelpFunctionService.DeleteByFunctionName(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
switch (action.key) {
|
||||
case "preview":
|
||||
openPreview();
|
||||
break;
|
||||
case "amend":
|
||||
readonly.value = false;
|
||||
break;
|
||||
@@ -182,7 +198,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -194,7 +210,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
// 功能名称:新建时单选 function-enquiry 回填(旧页用 FindFunctionNameOptions 下拉,移动端退化成选择器)
|
||||
@@ -278,6 +294,37 @@ function downStep(index: number): void {
|
||||
swap(data.value.SecurityHelpSteps, index, 1);
|
||||
}
|
||||
|
||||
/** 预览:把当前界面的步骤与引用组功能点拼成最终用户看到的帮助文档,弹窗展示 */
|
||||
function openPreview(): void {
|
||||
ensureLists();
|
||||
// 步骤按界面顺序(数组序即显示序,StepNo 要到保存时才重排)
|
||||
previewSteps.value = data.value.SecurityHelpSteps.map(step => ({
|
||||
title: String(step.Title ?? ""),
|
||||
desc: String(step.Description ?? ""),
|
||||
tips: String(step.Tips ?? ""),
|
||||
icon: String(step.Icon ?? ""),
|
||||
}));
|
||||
// 功能点:每个引用组按组主键展开成组内明细,组内按 SortNo 升序
|
||||
const items: HelpFeatureVM[] = [];
|
||||
for (const link of data.value.SecurityHelpFunctionFeatures) {
|
||||
const groupId = groups.value.find(g => g.GroupCode === link.GroupCode)?.HelpFeatureGroupId;
|
||||
if (!groupId) {
|
||||
continue;
|
||||
}
|
||||
allFeatures.value
|
||||
.filter(feature => feature.HelpFeatureGroupId === groupId)
|
||||
.sort((a, b) => (a.SortNo ?? 0) - (b.SortNo ?? 0))
|
||||
.forEach(feature => items.push({
|
||||
name: String(feature.Name ?? ""),
|
||||
desc: String(feature.Description ?? ""),
|
||||
icon: String(feature.Icon ?? ""),
|
||||
color: String(feature.Color ?? ""),
|
||||
}));
|
||||
}
|
||||
previewFeatures.value = items;
|
||||
previewVisible.value = true;
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
routeId.value = String(options?.id ?? "");
|
||||
const mode = String(options?.mode ?? "view");
|
||||
@@ -436,6 +483,9 @@ useAuthGuard(() => {
|
||||
</view>
|
||||
</template>
|
||||
</admin-form>
|
||||
|
||||
<help-doc-preview :visible="previewVisible" :title="heroTitle" :intro="helpFunction.Intro" :steps="previewSteps"
|
||||
:features="previewFeatures" @close="previewVisible = false" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { computed, ref } from "vue";
|
||||
import { useAuthGuard } from "@/composables";
|
||||
import { SecurityMasterEntity } from "@/data/auto/securityMasterEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityMasterService from "@/service/security/securityMasterService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -146,7 +147,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityMasterService.Delete(dataKey.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -162,7 +163,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !isValidKey()) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -174,7 +175,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useAuthGuard } from "@/composables";
|
||||
import { SecurityPermissionData } from "@/data/auto/securityPermissionData";
|
||||
import { SecurityPermissionDependentEntity } from "@/data/auto/securityPermissionDependentEntity";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityPermissionService from "@/service/security/securityPermissionService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -140,7 +141,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityPermissionService.Delete(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -156,7 +157,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -168,7 +169,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
function addDependent(): void {
|
||||
|
||||
@@ -179,7 +179,7 @@ useAuthGuard(() => {
|
||||
|
||||
<template>
|
||||
<view class="page">
|
||||
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.title')" :show-nav-back="isPick"
|
||||
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.title')" :show-nav-back="!isPick"
|
||||
:columns="columns" :loader="state.query" :row-key="SecurityUserEntity.USER_NAME" title-key="UserLocalName"
|
||||
:selectable="isPick ? pickTarget.multiple.value : Boolean(perm.canDelete.value)" :filter-count="filterCount"
|
||||
:filter-visible="filterVisible" :filter-items="draft" :search-token="searchToken"
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useAdminPick, useAuthGuard } from "@/composables";
|
||||
import { SecurityUserActorViewEntity } from "@/data/auto/securityUserActorViewEntity";
|
||||
import { SecurityUserData } from "@/data/auto/securityUserData";
|
||||
import { usePagePermission } from "@/permission/permission-context";
|
||||
import { smartBack } from "@/router/routes";
|
||||
import securityUserService from "@/service/security/securityUserService";
|
||||
|
||||
const { t } = useLocale();
|
||||
@@ -142,7 +143,7 @@ async function onSave(): Promise<void> {
|
||||
async function onDelete(): Promise<void> {
|
||||
await securityUserService.Delete(routeId.value);
|
||||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||||
setTimeout(() => uni.navigateBack(), 600);
|
||||
setTimeout(() => smartBack("mineTab"), 600);
|
||||
}
|
||||
|
||||
function onAction(action: AdminFormAction): void {
|
||||
@@ -158,7 +159,7 @@ function onAction(action: AdminFormAction): void {
|
||||
break;
|
||||
case "cancel":
|
||||
if (isNew.value || !routeId.value) {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
return;
|
||||
}
|
||||
readonly.value = true;
|
||||
@@ -170,7 +171,7 @@ function onAction(action: AdminFormAction): void {
|
||||
}
|
||||
|
||||
function onBack(): void {
|
||||
uni.navigateBack();
|
||||
smartBack("mineTab");
|
||||
}
|
||||
|
||||
// 角色选择:跳 actor-enquiry(可多选),回传后按 ActorName 去重合并
|
||||
|
||||
@@ -250,3 +250,16 @@ export function decodeQueryPath(value: unknown): string {
|
||||
export function isAuthFree(name: PageName): boolean {
|
||||
return AUTH_FREE_PAGES.includes(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带兜底的返回:优先 navigateBack;H5 整页刷新(F5)或深链直达会把页面栈清空,
|
||||
* 此时无处可退,退回到落地页(默认首页 dashboard),避免“返回”箭头点了没反应。
|
||||
* 与 pages/tab/file.vue、transport.vue、trash.vue 既有的“无栈就回首页”兜底策略一致。
|
||||
*/
|
||||
export function smartBack(fallback: PageName = "dashboard"): void {
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
uni.reLaunch({ url: pageUrl(fallback) });
|
||||
}
|
||||
Reference in new issue
Block a user