diff --git a/uniapp/youjiala/src/admin/help-preview-types.ts b/uniapp/youjiala/src/admin/help-preview-types.ts
new file mode 100644
index 0000000..b90a524
--- /dev/null
+++ b/uniapp/youjiala/src/admin/help-preview-types.ts
@@ -0,0 +1,22 @@
+/**
+ * 帮助文档预览的渲染视图模型:help-doc-preview 组件与调用它的页面共用。
+ *
+ * 单独放一个 .ts 是因为
@@ -209,7 +215,7 @@ defineExpose({ refresh, clearSelection, selectedRows });
:loading-more-enabled="true" :empty-view-text="t('AdminPage.emptyText')" @query="onQuery">
+ back-icon-name="arrow-leftward" :custom-back="onNavBack" />
+/**
+ * 帮助文档预览弹窗:旧 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, default: () => [] },
+ features: { type: Array as PropType, default: () => [] },
+});
+
+const emit = defineEmits<{
+ (e: "close"): void;
+}>();
+
+const { t } = useLocale();
+
+/** 旧项目图标名 -> uview-pro 内置图标名(只认这张表里的名字,否则渲染空白) */
+const ICON_MAP: Record = {
+ 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,
+);
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ title || t("HelpPreview.title") }}
+
+
+
+
+
+
+ {{ t("HelpPreview.empty") }}
+
+
+
+
+
+
+ {{ intro }}
+
+
+
+
+
+
+ {{ t("HelpPreview.flow") }}
+
+
+
+
+
+
+
+
+ {{ i + 1 }}
+
+
+
+
+
+
+
+ {{ i + 1 }}
+
+
+ {{ step.title }}
+
+
+
+ {{ step.desc }}
+
+
+
+ {{ t("HelpPreview.tip") }}
+
+
+ {{ step.tips }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("HelpPreview.features") }}
+
+
+
+
+
+
+
+
+
+ {{ feat.name }}
+
+
+ {{ feat.desc }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/locale/lang/en-US.json b/uniapp/youjiala/src/locale/lang/en-US.json
index a02c63f..25767d6 100644
--- a/uniapp/youjiala/src/locale/lang/en-US.json
+++ b/uniapp/youjiala/src/locale/lang/en-US.json
@@ -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",
diff --git a/uniapp/youjiala/src/locale/lang/zh-CN.json b/uniapp/youjiala/src/locale/lang/zh-CN.json
index 936da73..14ab6f7 100644
--- a/uniapp/youjiala/src/locale/lang/zh-CN.json
+++ b/uniapp/youjiala/src/locale/lang/zh-CN.json
@@ -208,7 +208,15 @@
"stepIconPlaceholder": "页面上的图标名",
"featureTitle": "引用的功能点组",
"addFeatureBut": "引用功能点组",
- "featureEmpty": "暂未引用功能点组"
+ "featureEmpty": "暂未引用功能点组",
+ "previewBut": "预览"
+ },
+ "HelpPreview": {
+ "title": "使用帮助",
+ "flow": "操作流程",
+ "features": "功能点与图标说明",
+ "tip": "提示",
+ "empty": "还没有可预览的帮助内容,请先填写功能说明、操作步骤或引用功能点组。"
},
"HelpFeaturePage": {
"title": "帮助功能点查询",
diff --git a/uniapp/youjiala/src/pages/security/actor-operation.vue b/uniapp/youjiala/src/pages/security/actor-operation.vue
index 538f210..79bde61 100644
--- a/uniapp/youjiala/src/pages/security/actor-operation.vue
+++ b/uniapp/youjiala/src/pages/security/actor-operation.vue
@@ -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 {
async function onDelete(): Promise {
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 去重合并
diff --git a/uniapp/youjiala/src/pages/security/function-operation.vue b/uniapp/youjiala/src/pages/security/function-operation.vue
index 787b3ae..3ab29df 100644
--- a/uniapp/youjiala/src/pages/security/function-operation.vue
+++ b/uniapp/youjiala/src/pages/security/function-operation.vue
@@ -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 {
async function onDelete(): Promise {
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 之外不影响
diff --git a/uniapp/youjiala/src/pages/security/generator-operation.vue b/uniapp/youjiala/src/pages/security/generator-operation.vue
index c805a7d..34acf53 100644
--- a/uniapp/youjiala/src/pages/security/generator-operation.vue
+++ b/uniapp/youjiala/src/pages/security/generator-operation.vue
@@ -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) => {
diff --git a/uniapp/youjiala/src/pages/security/group-operation.vue b/uniapp/youjiala/src/pages/security/group-operation.vue
index cb54cc2..481c683 100644
--- a/uniapp/youjiala/src/pages/security/group-operation.vue
+++ b/uniapp/youjiala/src/pages/security/group-operation.vue
@@ -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 {
async function onDelete(): Promise {
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 一致)
diff --git a/uniapp/youjiala/src/pages/security/help-feature-operation.vue b/uniapp/youjiala/src/pages/security/help-feature-operation.vue
index bf134d9..41a03da 100644
--- a/uniapp/youjiala/src/pages/security/help-feature-operation.vue
+++ b/uniapp/youjiala/src/pages/security/help-feature-operation.vue
@@ -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 {
async function onDelete(): Promise {
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 {
diff --git a/uniapp/youjiala/src/pages/security/help-function-operation.vue b/uniapp/youjiala/src/pages/security/help-function-operation.vue
index b9c0c1a..3e32f63 100644
--- a/uniapp/youjiala/src/pages/security/help-function-operation.vue
+++ b/uniapp/youjiala/src/pages/security/help-function-operation.vue
@@ -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([]);
+/** 全部功能点明细(FindAll 带回),预览时按组主键展开成「功能点与图标说明」 */
+const allFeatures = ref([]);
+
+/** 预览弹窗状态:openPreview 时按当前界面数据现拼,不额外发请求 */
+const previewVisible = ref(false);
+const previewSteps = ref([]);
+const previewFeatures = ref([]);
+
const heroTitle = computed(() => String(helpFunction.value.FunctionName || ""));
const heroChips = computed(() => {
const chips: string[] = [];
@@ -69,7 +80,8 @@ const sections = computed(() => {
});
const actions = computed(() => {
- 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 {
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 {
async function onDelete(): Promise {
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(() => {
+
+