feat: 添加编码管理页面

This commit is contained in:
皓月归尘 2025-02-14 21:06:24 +08:00
parent f5424660a0
commit acd364fdf7
9 changed files with 897 additions and 25 deletions

View File

@ -26,6 +26,11 @@ buttons:Add: Add
buttons:Update: Update buttons:Update: Update
buttons:Delete: Delete buttons:Delete: Delete
buttons:Export: Export buttons:Export: Export
buttons:Import: Import
buttons:DownLoded: DownLoded
buttons:DownLodedTemplate: DownLoded Template
buttons:Hide: Hide
buttons:ConfirmUpload: Confirm Upload
buttons:Save: Save buttons:Save: Save
buttons:Permission: Permission buttons:Permission: Permission
buttons:ExpandOrCollapse: Expand Or Collapse buttons:ExpandOrCollapse: Expand Or Collapse

View File

@ -26,6 +26,11 @@ buttons:Add: 添加
buttons:Update: 修改 buttons:Update: 修改
buttons:Delete: 删除 buttons:Delete: 删除
buttons:Export: 导出 buttons:Export: 导出
buttons:Import: 导入
buttons:DownLoded: 下载
buttons:DownLodedTemplate: 下载模版
buttons:Hide: 隐藏
buttons:ConfirmUpload: 确认上传
buttons:Save: 保存 buttons:Save: 保存
buttons:Permission: 权限 buttons:Permission: 权限
buttons:ExpandOrCollapse: 展开或折叠 buttons:ExpandOrCollapse: 展开或折叠

90
src/api/code.ts Normal file
View File

@ -0,0 +1,90 @@
import { http } from "@/utils/http";
import type { CodeInfo, QueryCodeResult } from "types/code";
/**
*
*/
export const getCodeTemplateAPI = () => {
return http.request<Blob>("get", "/api/code/template", {
responseType: "blob" // 设置响应类型为 blob
});
};
/**
*
*/
export const getAddCodeAPI = (id: string) => {
return http.request<null>("get", `/api/code/addCode/${id}`);
};
/**导入编码 */
export const postAddCodeAPI = (data: AddCodeParams) => {
return http.request<null>("post", `/api/code/add`, {
data
});
};
/**删除编码 */
export const deleteCodeAPI = (id: string) => {
return http.request<null>("delete", `/api/code/delete/${id}`);
};
/**
*
*/
export const deleteCodeListAPI = (data: { ids: string[] }) => {
return http.request<null>("post", `/api/code/deleteList`, { data });
};
/**
*
*/
interface AddCodeParams {
/**编码 */
code: string;
/** 编码描述 */
description: string;
}
/**更新编码 */
export const putUpdateCodeAPI = (data: AddCodeParams, id: string) => {
return http.request<null>("put", `/api/code/update/${id}`, {
data
});
};
/**获取编码详情 */
export const getCodeInfoAPI = (id: string) => {
return http.request<CodeInfo>("get", `/api/code/info/${id}`);
};
/**
*
*/
interface GetCodeListParams {
/** 当前页码 */
page: number;
/** 每页条数 */
pageSize: number;
/** 编码 */
code: string;
/** 编码描述 */
description: string;
}
/**
*
* @param params
* @returns
*/
export const getCodeListAPI = (params: GetCodeListParams) => {
return http.request<QueryListResult<CodeInfo>>("get", "/api/code/list", {
params
});
};
/**查询编码 */
export const postCodeInfoAPI = (data: { query_text: string }) => {
return http.request<QueryCodeResult>("post", `/api/code/query`, {
data
});
};

17
src/api/file.ts Normal file
View File

@ -0,0 +1,17 @@
import { http } from "@/utils/http";
import type { FileInfo } from "types/file";
/**上传文件 */
export const postUploadFileAPI = (data: { file: Blob }) => {
return http.request<FileInfo>("post", "/api/file/upload", {
data,
headers: {
"Content-Type": "multipart/form-data"
}
});
};
/**删除文件 */
export const deleteFileAPI = (id: string) => {
return http.request<null>("delete", `/api/file/delete/${id}`);
};

View File

@ -59,18 +59,14 @@ type GetLoacleListParams = {
code?: string; code?: string;
}; };
type GetLocaleListResult = {
/**语言列表 */
result: LanguageInfo[];
/**总条数 */
total: number;
/**页码 */
page: number;
};
export const getLocaleListAPI = (params: GetLoacleListParams) => { export const getLocaleListAPI = (params: GetLoacleListParams) => {
return http.request<GetLocaleListResult>("get", "/api/i18n/locale/list", { return http.request<QueryListResult<LanguageInfo>>(
"get",
"/api/i18n/locale/list",
{
params params
}); }
);
}; };
/** /**
@ -111,17 +107,6 @@ type GetI18nListParams = {
/**翻译内容 */ /**翻译内容 */
translation?: string; translation?: string;
}; };
/**
*
*/
type GetI18nListResult = {
/**翻译列表 */
result: TranslationInfo[];
/**总条数 */
total: number;
/**页码 */
page: number;
};
/** /**
* *
@ -129,9 +114,13 @@ type GetI18nListResult = {
* @returns * @returns
*/ */
export const getI18nListAPI = (params: GetI18nListParams) => { export const getI18nListAPI = (params: GetI18nListParams) => {
return http.request<GetI18nListResult>("get", "/api/i18n/list", { return http.request<QueryListResult<TranslationInfo>>(
"get",
"/api/i18n/list",
{
params params
}); }
);
}; };
/** /**

View File

@ -0,0 +1,61 @@
<template>
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-row :gutter="30">
<re-col :value="24" :xm="24" :sm="24">
<el-form-item label="编码" prop="code">
<el-input
v-model="newFormInline.code"
placeholder="请输入编码~"
clearable
class="w-full"
/>
</el-form-item>
</re-col>
<re-col :value="24" :xm="24" :sm="24">
<el-form-item label="描述" prop="description">
<el-input
v-model="newFormInline.description"
placeholder="请输入编码描述~"
clearable
class="w-full"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import ReCol from "@/components/ReCol";
import type { FormRules } from "element-plus";
const ruleFormRef = ref();
interface PropsInfo {
code: string;
description: string;
}
type ProsData = {
formInline: PropsInfo;
};
/** 自定义表单规则校验 */
const formRules = reactive<FormRules>({
code: [{ required: true, message: "编码为必填项", trigger: "blur" }],
description: [
{ required: true, message: "编码描述为必填项", trigger: "blur" }
]
});
const props = withDefaults(defineProps<ProsData>(), {
formInline: () => ({
code: "",
description: ""
})
});
const newFormInline = ref<PropsInfo>(props.formInline);
defineExpose({ newFormInline });
</script>

View File

@ -0,0 +1,244 @@
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
<el-form-item label="编码" prop="code">
<el-input
v-model="form.code"
placeholder="请输入编码"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item label="编码描述" prop="description">
<el-input
v-model="form.description"
clearable
placeholder="请输入编码描述~"
class="!w-[180px]"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
{{ t("buttons:Search") }}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
{{ t("buttons:Reset") }}
</el-button>
</el-form-item>
</el-form>
<PureTableBar title="编码管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增')"
>
{{ t("buttons:Add") }}
</el-button>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="showUploadArea = !showUploadArea"
>
{{ showUploadArea ? t("buttons:Hide") : t("buttons:Import") }}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<!-- 上传文件区域 -->
<el-card v-if="showUploadArea" shadow="never">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
drag
action="#"
class="w-full"
:auto-upload="false"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
>
<div class="el-upload__text">
<IconifyIconOffline
:icon="UploadIcon"
width="26"
:limit="1"
class="m-auto mb-2"
/>
可点击或拖拽上传
</div>
</el-upload>
<template #footer>
<div class="flex items-center justify-center">
<el-button class="w-full my-2" @click="onDownloadTemplate">{{
t("buttons:DownLodedTemplate")
}}</el-button>
<el-popconfirm title="是否确认上传?" @confirm="handleUpload">
<template #reference>
<el-button
class="w-full my-2"
type="primary"
:disabled="uploadStatus"
>{{ t("buttons:ConfirmUpload") }}</el-button
>
</template>
</el-popconfirm>
</div>
</template>
</el-card>
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button type="danger" text class="mr-1">
{{ t("buttons:DeleteInBatches") }}
</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
adaptive
border
stripe
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
{{ t("buttons:Update") }}
</el-button>
<el-popconfirm
:title="`是否确认删除编码名为 ${row.code} 的这条数据`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "CodeAdmin"
});
import { ref } from "vue";
import { useCode } from "./utils/hook";
import { useI18n } from "vue-i18n";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
import UploadIcon from "@iconify-icons/ri/upload-2-line";
const { t } = useI18n();
/**
* 表格Ref
*/
const tableRef = ref();
const formRef = ref();
const {
form,
dataList,
loading,
pagination,
columns,
selectedNum,
showUploadArea,
fileIds,
fileList,
uploadStatus,
beforeUpload,
handleUpload,
beforeRemove,
openDialog,
onSearch,
resetForm,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel,
onDownloadTemplate
} = useCode(tableRef);
</script>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
:deep(.el-button:focus-visible) {
outline: none;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,405 @@
import dayjs from "dayjs";
import editForm from "../components/form.vue";
import { message } from "@/utils/message";
import { type Ref, ref, reactive, onMounted, h } from "vue";
import { addDialog } from "@/components/ReDialog";
import type { PaginationProps } from "@pureadmin/table";
import type { CodeInfo } from "types/code";
import {
deleteCodeAPI,
deleteCodeListAPI,
getAddCodeAPI,
getCodeListAPI,
getCodeTemplateAPI,
postAddCodeAPI,
putUpdateCodeAPI
} from "@/api/code";
import { getKeyList } from "@pureadmin/utils";
import {
ElMessageBox,
type UploadProps,
type UploadUserFile
} from "element-plus";
import { deleteFileAPI, postUploadFileAPI } from "@/api/file";
export const useCode = (tableRef: Ref) => {
/**
*
*/
const form = reactive({
code: "",
description: ""
});
/**
* Ref
*/
const formRef = ref(null);
/**
*
*/
const dataList = ref<CodeInfo[]>([]);
/**
*
*/
const loading = ref(true);
/**
*
*/
const selectedNum = ref<number>(0);
/**
*
*/
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true,
pageSizes: [10, 20, 30, 40, 50]
});
// 上传文件区域显示状态
const showUploadArea = ref(false);
const fileList = ref<UploadUserFile[]>([]);
/**上传成功后文件列表 */
const fileIds = ref([]);
const fileId = ref<string>("");
/**上传按钮状态 */
const uploadStatus = ref<boolean>(false);
/**
*
*/
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "编码",
prop: "code"
},
{
label: "编码描述",
prop: "description"
},
{
label: "创建时间",
prop: "create_time",
formatter: ({ create_time }) =>
dayjs(create_time).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 250,
slot: "operation"
}
];
/**
*
*/
const onSearch = async () => {
loading.value = true;
const res = await getCodeListAPI({
page: pagination.currentPage,
pageSize: pagination.pageSize,
description: form.description,
code: form.code
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
loading.value = false;
};
/**
*
* @param formEl ref
* @returns
*/
const resetForm = (formEl: any) => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
/**
*
* @param row
*/
const handleDelete = async (row: CodeInfo) => {
const res = await deleteCodeAPI(row.id);
if (res.success) {
onSearch();
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
*/
const handleSizeChange = async (val: number) => {
const res = await getCodeListAPI({
page: pagination.currentPage,
pageSize: val,
description: form.description,
code: form.code
});
if (res.success) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/**
*
* @param val
*/
const handleCurrentChange = async (val: number) => {
const res = await getCodeListAPI({
page: val,
pageSize: pagination.pageSize,
description: form.description,
code: form.code
});
if (res.code === 200) {
dataList.value = res.data.result;
pagination.total = res.data.total;
pagination.currentPage = res.data.page;
pagination.pageSize = res.data.pageSize;
}
message(res.msg, {
type: res.success ? "success" : "error"
});
};
/** 当CheckBox选择项发生变化时会触发该事件 */
const handleSelectionChange = async (val: any) => {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
};
/** 取消选择 */
const onSelectionCancel = async () => {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
};
/**
*
*/
const onbatchDel = async () => {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
const res = await deleteCodeListAPI({
ids: getKeyList(curSelected, "id")
});
if (res.success) {
message(res.msg, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
} else {
message(res.msg, { type: "error", duration: 5000 });
}
};
const openDialog = async (title = "新增", row?: CodeInfo) => {
addDialog({
title: `${title}编码项`,
props: {
formInline: {
title: title,
description: row?.description ?? "",
code: row?.code ?? ""
}
},
width: "45%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () =>
h(editForm, {
formInline: {
description: row?.description ?? "",
code: row?.code ?? ""
},
ref: formRef
}),
beforeSure: async (done, {}) => {
const FormData = formRef.value.newFormInline;
let form = {
description: FormData.name ?? "",
code: FormData.code ?? ""
};
if (title === "新增") {
const res = await postAddCodeAPI(form);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
} else {
const res = await putUpdateCodeAPI(form, row.id);
if (res.success) {
done();
await onSearch();
}
message(res.msg, { type: res.success ? "success" : "error" });
}
}
});
};
/**下载模版文件 */
const onDownloadTemplate = async () => {
try {
const blob = await getCodeTemplateAPI();
// 生成下载链接
// @ts-ignore
const url = URL.createObjectURL(blob);
// 创建 <a> 元素并触发下载
const link = document.createElement("a");
link.href = url;
link.download = "上传模版.xlsx"; // 设置下载文件名,确保后缀名正确
document.body.appendChild(link); // 将 <a> 元素添加到 DOM 中
link.click(); // 模拟点击下载
// 清理 URL 对象
URL.revokeObjectURL(url);
document.body.removeChild(link); // 移除 <a> 元素
} catch (error) {
console.error("下载模板失败:", error);
}
};
/** 上传文件前 */
const beforeUpload = async file => {
const isExcel =
file.type ===
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || // xlsx
file.type === "application/vnd.ms-excel" || // xls
file.name.endsWith(".xlsx") || // 兼容部分浏览器
file.name.endsWith(".xls"); // 兼容部分浏览器
// const maxSize = 20 * 1024 * 1024; // 20MB 限制
if (!isExcel) {
message("只能上传 xlsx 或 xls 文件!", { type: "error" });
return false;
}
/*
if (file.size > maxSize) {
message("文件大小应在 20MB 以内!", { type: "error" });
return false;
}
*/
return true;
};
/**处理文件上传 */
const handleUpload = async () => {
if (fileList.value.length === 0) {
message("请先上传文件!", { type: "error", duration: 5000 });
return;
}
uploadStatus.value = true;
for (const file of fileList.value) {
if (file.status === "success") {
const data = await getAddCodeAPI(fileId.value);
if (data.success) {
message("导入成功!", { type: "success" });
await onSearch();
}
continue;
}
try {
const res = await postUploadFileAPI({
file: file.raw
});
if (res.success) {
file.status = "success";
fileId.value = res.data.id;
message(`${res.data.name}上传成功!`, { type: "success" });
fileIds.value.push(res.data);
const data = await getAddCodeAPI(fileId.value);
if (data.success) {
message(`导入成功!`, { type: "success" });
await onSearch();
}
} else {
file.status = "fail";
}
} catch (error) {
console.error(error);
}
}
uploadStatus.value = false;
};
/**移除文件 */
const beforeRemove: UploadProps["beforeRemove"] = async uploadFile => {
return ElMessageBox.confirm(`是否移除 ${uploadFile.name} ?`).then(
async () => {
if (uploadFile.status === "success") {
const fileId = fileIds.value.filter(
item => item.filename === uploadFile.name
)[0]["fileId"];
const res = await deleteFileAPI(fileId);
if (res.code === 200) {
message(res.msg, { type: "success" });
return true;
} else {
message(res.msg, { type: "error" });
return false;
}
} else {
return true;
}
},
() => false
);
};
/**
*
*/
onMounted(async () => {
await onSearch();
});
return {
form,
dataList,
loading,
pagination,
columns,
selectedNum,
showUploadArea,
fileIds,
fileList,
uploadStatus,
beforeUpload,
handleUpload,
beforeRemove,
openDialog,
onSearch,
resetForm,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel,
onDownloadTemplate
};
};

56
types/code.d.ts vendored Normal file
View File

@ -0,0 +1,56 @@
/**编码详情 */
export interface CodeInfo {
/** 编码ID */
id: string;
/** 编码 */
code: string;
/** 编码描述 */
description: string;
/** 创建时间 */
create_time: string;
/** 更新时间 */
update_time: string;
/** 创建人 */
create_by: string;
/** 更新人 */
update_by: string;
}
/**查询结果项 */
export interface QueryResultItem {
/** 编码ID */
id: string;
/** 编码 */
code: string;
/** 编码描述 */
description: string;
/**匹配度 */
match_rate: number;
}
/**查询结果 */
export interface QueryResult {
/**查询文本 */
query_text: string;
/**查询状态 */
status: number;
/**会话ID */
id: string;
/**结果 */
result: QueryResultItem[];
}
/**查询编码结果 */
export interface QueryCodeResult {
/**查询文本 */
query: string;
/**查询统计 */
query_count: number;
/**查询耗时 */
cost_time: number;
/**结果统计 */
result_count: number;
/**查询结果 */
response_result: QueryResult[];
/**查询状态 */
status: number;
}