feat(app): 基石应用删除解禁——MyConfirm 同步 projman 删除模(输名确认+红色确认键),删除一律危险确认;基石模块/推送仍置灰
build / build (push) Successful in 3m47s

This commit is contained in:
chenxing
2026-09-29 15:26:25 +08:00
parent e034b947e5
commit ec53c34b9c
4 changed files with 57 additions and 16 deletions
+36 -8
View File
@@ -1,26 +1,36 @@
<template>
<Modal v-model="isModal" @on-visible-change="showChange" :title="title">
<!-- 确认框固定最高层:可能从任意 transfer 弹窗/抽屉(含手动抬层的 3000 抽屉)里唤起,
而 Modal 默认层级是全局计数器 1000+N,会被高 z-index 抽屉盖住 -->
<Modal v-model="isModal" @on-visible-change="showChange" :title="title" :z-index="4000" transfer>
<Alert type="warning" class="mb-1" show-icon>
<template #icon>
<Icon type="ios-bulb-outline" :class="{ 'mt-5': !info }" slot="icon" :size="28"></Icon>
<Icon type="ios-bulb-outline" :class="{ 'mt-5': !info }" :size="28"></Icon>
</template>
<div v-if="msg" :class="['message-box', info ? '' : 'mt-10 mb-10 ml-10']">{{ msg }}</div>
<template #desc v-if="info" slot="desc">{{ info }}</template>
<template #desc v-if="info">{{ info }}</template>
</Alert>
<div v-if="confirmName" class="confirm-name-block mb-1">
<div class="mb-5">
此操作<span style="font-weight: bold">无法</span>被回滚。<br />
请输入 <span class="confirm-name-text">{{ confirmName }}</span> 以确认:
</div>
<Input v-model="inputName" placeholder="请输入名称以确认" @on-enter="onOkClick" />
</div>
<template #footer>
<Button type="primary" :loading="okLoading" @click="onOkClick">{{ confirmText }}</Button>
<Button :type="danger ? 'error' : 'primary'" :disabled="!nameMatched" :loading="okLoading" @click="onOkClick">{{ confirmText }}</Button>
<Button :loading="cancleLoading" @click="onCancleClick">{{ closeText }}</Button>
</template>
</Modal>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { Modal, Button, Alert, Icon } from "view-ui-plus";
import { ref, computed, onMounted } from "vue";
import { Modal, Button, Alert, Icon, Input } from "view-ui-plus";
const isModal = ref(false),
okLoading = ref(false),
cancleLoading = ref(false);
cancleLoading = ref(false),
inputName = ref("");
const props = withDefaults(
defineProps<{
@@ -30,6 +40,8 @@ const props = withDefaults(
isLoading?: boolean;
confirmText?: string;
closeText?: string;
confirmName?: string;
danger?: boolean;
confirmFn: any;
closeFn: any;
destroy: Function;
@@ -39,9 +51,13 @@ const props = withDefaults(
isLoading: true,
confirmText: "确认",
closeText: "取消",
confirmName: "",
danger: false,
}
);
const nameMatched = computed(() => !props.confirmName || inputName.value.trim() === props.confirmName);
onMounted(function () {
setTimeout(() => {
isModal.value = true;
@@ -49,6 +65,7 @@ onMounted(function () {
});
const onOkClick = async () => {
if (!nameMatched.value) return;
if (!!props.isLoading) okLoading.value = true;
const { onOk, reslove } = props.confirmFn;
await Promise.resolve(onOk());
@@ -73,4 +90,15 @@ const showChange = (state: boolean) => {
};
</script>
<style></style>
<style lang="scss" scoped>
.confirm-name-block {
padding: 0 2px;
line-height: 1.6;
.confirm-name-text {
color: #ed4014;
font-weight: bold;
user-select: all;
}
}
</style>
+4 -2
View File
@@ -2,8 +2,8 @@ import { MyConfirm } from "@/types/Basics";
import confirm from "./Confirm.vue";
import { createApp } from "vue";
export default ({ title, msg, info, confirmText, closeText, isLoading, onOk, onCancel }: MyConfirm) => {
return new Promise<boolean>((reslove, reject) => {
export default ({ title, msg, info, confirmText, closeText, isLoading, confirmName, danger, onOk, onCancel }: MyConfirm) => {
return new Promise<boolean>((reslove, _reject) => {
const instance = createApp(confirm, {
title,
msg,
@@ -11,6 +11,8 @@ export default ({ title, msg, info, confirmText, closeText, isLoading, onOk, onC
confirmText,
closeText,
isLoading,
confirmName,
danger,
confirmFn: {
onOk,
reslove,
+4
View File
@@ -105,6 +105,10 @@ export interface MyConfirm {
isLoading?: boolean;
confirmText?: string;
closeText?: string;
/** 输名确认:值与 msg 对象名一致才允许点确认键(危险操作删除模,对齐 projman) */
confirmName?: string;
/** 红色确认键(危险操作) */
danger?: boolean;
onOk?: Function;
onCancel?: Function;
}
+13 -6
View File
@@ -181,13 +181,14 @@ const columns = [
width: 280,
render: (h: any, { row }: any) => {
// SCHOOL 模式:仅编辑(名称/描述/图标,写学校覆盖表);模块管理/推送设置/删除全部隐藏。
// 基石应用(app_nature=BASE)只做身份注册:模块/推送/删除置灰,「编辑」保持可用(密钥轮换入口)
// 基石应用(app_nature=BASE)只做身份注册:模块/推送置灰;删除允许——基石也可下线,
// 但走「输名确认」删除模(危险确认,与 projman 应用删除同款)。
const base = row.appNature === "BASE";
const actions = [$utils.operatingTextHandle(h, '编辑', '', () => handleEdit(row))];
if (!isSchoolMode.value) {
actions.push($utils.operatingTextHandle(h, '模块管理', '', () => goModuleManage(row), base));
actions.push($utils.operatingTextHandle(h, '推送设置', '', () => openPushSetting(row), base));
actions.push($utils.operatingTextHandle(h, '删除', 'text-error', () => handleDelete(row), base));
actions.push($utils.operatingTextHandle(h, '删除', 'text-error', () => handleDelete(row)));
}
return actions;
}
@@ -239,18 +240,24 @@ function openSortModal() {
sortModalVisible.value = true;
}
/* ---------------- 删除应用 ---------------- */
/* ---------------- 删除应用(删除模:输名确认 + 红色确认键,对齐 projman;基石应用同样可删) ---------------- */
async function handleDelete(row: any) {
const name = row.name || String(row.id);
await MyConfirm({
msg: `确定删除应用「${row.name || row.id}」吗?`,
info: "删除后不可恢复!",
title: "删除应用",
msg: `确定删除应用「${name}」吗?`,
info: "删除后不可恢复,关联就绪资源需手动清理!"
+ (row.appNature === "BASE" ? "(基石应用:请先确认无平台组件仍依赖其 appKey 签名身份)" : ""),
confirmName: name,
danger: true,
confirmText: "删除",
async onOk() {
try {
await Apis.AppSettings.deleteApp({ pathParams: { id: row.id } });
Message.success('删除成功!');
tableRef.value?.reloadData(OperatorEnum.del);
} catch (e: any) {
console.warn(e?.message || '删除失败');
Message.error(e?.message || '删除失败');
}
},
});