feat(device): 新增应用图标管理功能
新增应用图标库页面,支持图标分页查询、锁定/解锁、手动上传和删除操作。
This commit is contained in:
73
src/api/device/appIcon.ts
Normal file
73
src/api/device/appIcon.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
import type { AppIconQuery, AppIconVO } from "./types";
|
||||||
|
|
||||||
|
const APP_ICON_BASE_URL = "/api/v1/app-icons";
|
||||||
|
|
||||||
|
const AppIconAPI = {
|
||||||
|
/** 应用图标库分页列表(不传 packageName 返回全部) */
|
||||||
|
getPage(queryParams: AppIconQuery) {
|
||||||
|
return request<unknown, PageResult<AppIconVO>>({
|
||||||
|
url: `${APP_ICON_BASE_URL}/page`,
|
||||||
|
method: "get",
|
||||||
|
params: queryParams,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 获取应用当前生效图标信息 */
|
||||||
|
getCurrentIcon(packageName: string) {
|
||||||
|
return request<unknown, AppIconVO>({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 获取应用图标历史版本列表(含当前,按版本倒序) */
|
||||||
|
getHistory(packageName: string) {
|
||||||
|
return request<unknown, AppIconVO[]>({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}/history`,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 选取历史版本并锁定 */
|
||||||
|
lock(packageName: string, iconId: number) {
|
||||||
|
return request({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}/lock`,
|
||||||
|
method: "post",
|
||||||
|
params: { iconId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 解锁当前图标 */
|
||||||
|
unlock(packageName: string) {
|
||||||
|
return request({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}/unlock`,
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 上传应用图标 */
|
||||||
|
upload(packageName: string, file: File, appName?: string) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
if (appName) {
|
||||||
|
formData.append("appName", appName);
|
||||||
|
}
|
||||||
|
return request<unknown, string>({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}/upload`,
|
||||||
|
method: "post",
|
||||||
|
data: formData,
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 删除应用图标版本 */
|
||||||
|
delete(packageName: string, iconId: number) {
|
||||||
|
return request({
|
||||||
|
url: `${APP_ICON_BASE_URL}/${packageName}/${iconId}`,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppIconAPI;
|
||||||
33
src/api/device/types.ts
Normal file
33
src/api/device/types.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { BaseQueryParams } from "@/api/common";
|
||||||
|
|
||||||
|
/** 应用图标分页查询参数 */
|
||||||
|
export interface AppIconQuery extends BaseQueryParams {
|
||||||
|
/** 应用包名(可选,不传则返回全部) */
|
||||||
|
packageName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 应用图标VO */
|
||||||
|
export interface AppIconVO {
|
||||||
|
/** 主键ID */
|
||||||
|
id?: number;
|
||||||
|
/** 应用包名 */
|
||||||
|
packageName?: string;
|
||||||
|
/** 应用名称 */
|
||||||
|
appName?: string;
|
||||||
|
/** 图标访问地址 */
|
||||||
|
iconUrl?: string;
|
||||||
|
/** 图标文件MD5 */
|
||||||
|
iconMd5?: string;
|
||||||
|
/** 版本号(同包名内自增) */
|
||||||
|
version?: number;
|
||||||
|
/** 状态(1当前生效 0历史版本) */
|
||||||
|
status?: number;
|
||||||
|
/** 是否锁定(0未锁定 1已锁定) */
|
||||||
|
locked?: number;
|
||||||
|
/** 图标来源(device/admin) */
|
||||||
|
source?: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
createTime?: string;
|
||||||
|
/** 更新时间 */
|
||||||
|
updateTime?: string;
|
||||||
|
}
|
||||||
435
src/views/devices/app-icon/index.vue
Normal file
435
src/views/devices/app-icon/index.vue
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-card shadow="never" class="mb-4">
|
||||||
|
<el-form ref="queryFormRef" :model="params" :inline="true" @submit.prevent>
|
||||||
|
<el-form-item label="应用包名" prop="packageName">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="params.packageName"
|
||||||
|
placeholder="按包名筛选(留空显示全部)"
|
||||||
|
clearable
|
||||||
|
style="width: 320px"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button :icon="RefreshLeft" @click="handleResetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<div class="flex items-center justify-between mb-3 flex-wrap gap-2">
|
||||||
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
<span v-if="selectedRow" class="text-sm">
|
||||||
|
已选中:v{{ selectedRow.version }} ·
|
||||||
|
<span class="text-info">{{ selectedRow.packageName }}</span>
|
||||||
|
<el-tag
|
||||||
|
v-if="selectedRow.status === 1"
|
||||||
|
type="success"
|
||||||
|
effect="light"
|
||||||
|
size="small"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
当前生效
|
||||||
|
</el-tag>
|
||||||
|
<el-tag
|
||||||
|
v-if="selectedRow.locked === 1"
|
||||||
|
type="warning"
|
||||||
|
effect="light"
|
||||||
|
size="small"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
已锁定
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-info text-xs">请先在下方列表选择一行,再进行锁定 / 解锁</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="Lock"
|
||||||
|
:disabled="!selectedRow || selectedRow.status === 1"
|
||||||
|
:loading="lockLoading"
|
||||||
|
@click="handleToolbarLock"
|
||||||
|
>
|
||||||
|
锁定
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:icon="Unlock"
|
||||||
|
:disabled="!selectedRow || selectedRow.locked !== 1"
|
||||||
|
:loading="unlockLoading"
|
||||||
|
@click="handleToolbarUnlock"
|
||||||
|
>
|
||||||
|
解锁
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" :icon="Upload" @click="openUploadDialog">手动上传</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
<el-table-column label="图标" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-image
|
||||||
|
:src="resolveIconUrl(scope.row.iconUrl)"
|
||||||
|
fit="contain"
|
||||||
|
style="width: 48px; height: 48px; border-radius: 6px"
|
||||||
|
:preview-src-list="previewList"
|
||||||
|
:initial-index="previewIndex(scope.row)"
|
||||||
|
preview-teleported
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="应用包名"
|
||||||
|
prop="packageName"
|
||||||
|
min-width="180"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column label="应用名称" prop="appName" min-width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column label="版本" prop="version" width="80" align="center" />
|
||||||
|
<el-table-column label="状态" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" effect="light">
|
||||||
|
{{ scope.row.status === 1 ? "当前生效" : "历史版本" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="锁定" width="90" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.locked === 1" type="warning" effect="light">已锁定</el-tag>
|
||||||
|
<span v-else class="text-info">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="来源" prop="source" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.source === 'admin'" effect="plain">管理端</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.source === 'device'" effect="plain" type="info">
|
||||||
|
设备端
|
||||||
|
</el-tag>
|
||||||
|
<span v-else>{{ scope.row.source }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="MD5" prop="iconMd5" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column label="更新时间" prop="updateTime" width="180" />
|
||||||
|
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.status === 0"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
:icon="Lock"
|
||||||
|
:loading="lockLoadingId === scope.row.id"
|
||||||
|
@click="handleLock(scope.row)"
|
||||||
|
>
|
||||||
|
锁定此版本
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else-if="scope.row.locked === 1"
|
||||||
|
type="warning"
|
||||||
|
link
|
||||||
|
:icon="Unlock"
|
||||||
|
:loading="lockLoadingId === scope.row.id"
|
||||||
|
@click="handleUnlock(scope.row)"
|
||||||
|
>
|
||||||
|
解锁
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
:icon="Delete"
|
||||||
|
:loading="deleteLoadingId === scope.row.id"
|
||||||
|
@click="handleDelete(scope.row)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-if="total > 0"
|
||||||
|
v-model:total="total"
|
||||||
|
v-model:page="params.pageNum"
|
||||||
|
v-model:limit="params.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
@pagination="fetchData"
|
||||||
|
/>
|
||||||
|
<el-empty v-if="!loading && total === 0" description="暂无图标" />
|
||||||
|
|
||||||
|
<!-- 手动上传对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="uploadDialogVisible"
|
||||||
|
title="手动上传图标"
|
||||||
|
width="480px"
|
||||||
|
append-to-body
|
||||||
|
@closed="resetUploadDialog"
|
||||||
|
>
|
||||||
|
<el-form :model="uploadForm" label-width="96px">
|
||||||
|
<el-form-item label="应用包名" required>
|
||||||
|
<el-input
|
||||||
|
v-model.trim="uploadForm.packageName"
|
||||||
|
placeholder="如 com.example.app"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="应用名称">
|
||||||
|
<el-input
|
||||||
|
v-model.trim="uploadForm.appName"
|
||||||
|
placeholder="可选,不填沿用已有"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图标文件" required>
|
||||||
|
<el-upload
|
||||||
|
v-model:file-list="uploadFileList"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
:show-file-list="true"
|
||||||
|
accept=".jpg,.jpeg,.png,.webp,image/*"
|
||||||
|
:before-upload="validateUploadFile"
|
||||||
|
drag
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<div class="el-upload__text">
|
||||||
|
将图标拖到此处,或
|
||||||
|
<em>点击选择</em>
|
||||||
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="text-info text-xs">仅支持 jpg/jpeg/png/webp,大小 ≤ 2MB</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="uploadDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :icon="Upload" :loading="uploadLoading" @click="submitUpload">
|
||||||
|
确认上传
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive, ref } from "vue";
|
||||||
|
import { Delete, Lock, RefreshLeft, Search, Unlock, Upload } from "@element-plus/icons-vue";
|
||||||
|
import {
|
||||||
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
|
type FormInstance,
|
||||||
|
type UploadRawFile,
|
||||||
|
type UploadUserFile,
|
||||||
|
} from "element-plus";
|
||||||
|
|
||||||
|
import { usePageTable } from "@/composables";
|
||||||
|
import AppIconAPI from "@/api/device/appIcon";
|
||||||
|
import type { AppIconQuery, AppIconVO } from "@/api/device/types";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "AppIcon",
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
// 分页表格(打开即加载全部图标)
|
||||||
|
const { loading, list, total, params, fetchData, handleQuery, handleResetQuery } = usePageTable<
|
||||||
|
AppIconVO,
|
||||||
|
AppIconQuery
|
||||||
|
>({
|
||||||
|
initialParams: { pageNum: 1, pageSize: 10, packageName: "" },
|
||||||
|
request: AppIconAPI.getPage,
|
||||||
|
onBeforeReset: () => queryFormRef.value?.resetFields(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前选中行
|
||||||
|
const selectedRow = ref<AppIconVO | null>(null);
|
||||||
|
|
||||||
|
// 上传(手动上传对话框)
|
||||||
|
const uploadDialogVisible = ref(false);
|
||||||
|
const uploadLoading = ref(false);
|
||||||
|
const uploadForm = reactive({
|
||||||
|
packageName: "",
|
||||||
|
appName: "",
|
||||||
|
});
|
||||||
|
const uploadFileList = ref<UploadUserFile[]>([]);
|
||||||
|
|
||||||
|
// loading 状态
|
||||||
|
const lockLoading = ref(false);
|
||||||
|
const unlockLoading = ref(false);
|
||||||
|
const lockLoadingId = ref<number | null>(null);
|
||||||
|
const deleteLoadingId = ref<number | null>(null);
|
||||||
|
|
||||||
|
function handleCurrentChange(row: AppIconVO | null) {
|
||||||
|
selectedRow.value = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewList = computed(() => list.value.map((item) => resolveIconUrl(item.iconUrl)));
|
||||||
|
const previewIndex = (row: AppIconVO) => {
|
||||||
|
const idx = list.value.findIndex((item) => item.id === row.id);
|
||||||
|
return idx < 0 ? 0 : idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拼接图标完整访问地址
|
||||||
|
* 后端返回的是相对路径(如 /static/app_icon/{md5}.png)。
|
||||||
|
* el-image 的 src 是浏览器原生请求,不经过 axios,拿不到 baseURL,
|
||||||
|
* 因此必须手动补上代理前缀(VITE_APP_BASE_API),否则请求会打到前端 dev server 上导致 404。
|
||||||
|
*/
|
||||||
|
function resolveIconUrl(url?: string | null): string {
|
||||||
|
if (!url) return "";
|
||||||
|
if (/^(https?:)?\/\//.test(url) || url.startsWith("data:")) return url;
|
||||||
|
const baseApi = import.meta.env.VITE_APP_BASE_API ?? "";
|
||||||
|
return `${baseApi.replace(/\/$/, "")}${url.startsWith("/") ? "" : "/"}${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUploadFile(file: UploadRawFile) {
|
||||||
|
const allowExt = ["jpg", "jpeg", "png", "webp"];
|
||||||
|
const ext = file.name.split(".").pop()?.toLowerCase() || "";
|
||||||
|
const isImage = file.type.startsWith("image/") || allowExt.includes(ext);
|
||||||
|
if (!isImage) {
|
||||||
|
ElMessage.warning("仅支持 jpg/jpeg/png/webp 格式图标");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.size > 2 * 1024 * 1024) {
|
||||||
|
ElMessage.warning("图标大小不能超过 2MB");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openUploadDialog() {
|
||||||
|
uploadDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUploadDialog() {
|
||||||
|
uploadForm.packageName = "";
|
||||||
|
uploadForm.appName = "";
|
||||||
|
uploadFileList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitUpload() {
|
||||||
|
const pkg = uploadForm.packageName.trim();
|
||||||
|
if (!pkg) {
|
||||||
|
ElMessage.warning("请填写应用包名");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^[A-Za-z0-9._]+$/.test(pkg)) {
|
||||||
|
ElMessage.warning("应用包名仅支持字母、数字、. 和 _");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = uploadFileList.value[0]?.raw as File | undefined;
|
||||||
|
if (!file) {
|
||||||
|
ElMessage.warning("请选择图标文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadLoading.value = true;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.upload(pkg, file, uploadForm.appName.trim() || undefined);
|
||||||
|
ElMessage.success("上传成功");
|
||||||
|
uploadDialogVisible.value = false;
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
uploadLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLock(row: AppIconVO) {
|
||||||
|
if (!row.id || !row.packageName) return;
|
||||||
|
lockLoadingId.value = row.id;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.lock(row.packageName, row.id);
|
||||||
|
ElMessage.success("已锁定该版本");
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
lockLoadingId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUnlock(row: AppIconVO) {
|
||||||
|
if (!row.packageName) return;
|
||||||
|
lockLoadingId.value = row.id ?? null;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.unlock(row.packageName);
|
||||||
|
ElMessage.success("已解锁");
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
lockLoadingId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具栏:锁定当前选中行
|
||||||
|
async function handleToolbarLock() {
|
||||||
|
const row = selectedRow.value;
|
||||||
|
if (!row || !row.id || !row.packageName) return;
|
||||||
|
lockLoading.value = true;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.lock(row.packageName, row.id);
|
||||||
|
ElMessage.success("已锁定该版本");
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
lockLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具栏:解锁当前选中行(需为已锁定)
|
||||||
|
async function handleToolbarUnlock() {
|
||||||
|
const row = selectedRow.value;
|
||||||
|
if (!row || !row.packageName) return;
|
||||||
|
unlockLoading.value = true;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.unlock(row.packageName);
|
||||||
|
ElMessage.success("已解锁");
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
unlockLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(row: AppIconVO) {
|
||||||
|
if (!row.id || !row.packageName) return;
|
||||||
|
const isActive = row.status === 1;
|
||||||
|
const tip = isActive
|
||||||
|
? "该图标为当前生效版本,删除后该应用将无生效图标,确认删除?"
|
||||||
|
: "确认删除该历史图标版本?";
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(tip, "删除图标", {
|
||||||
|
type: "warning",
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLoadingId.value = row.id;
|
||||||
|
try {
|
||||||
|
await AppIconAPI.delete(row.packageName, row.id);
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
await fetchData();
|
||||||
|
} finally {
|
||||||
|
deleteLoadingId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.text-info {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user