快速开发三(自定义命令)
为了提高前端开发效率,特意整理了自定义命令用来快速开发
提示
后续考虑通过后端接口以及json文件,实现type和api定义生成和校准,目前暂不支持,需要循序渐进
视频演示
api和type生成
生成type
name (可选参数,如果不填自动根据文件名推导)
type (可选参数,list|tree)
pnpm make:ts 模块名/版本号/路径/文件名 --name=模型名 --type=类型示例:
pnpm make:ts text/v1/bank/bank-type --name=Bank --type=list自动生成如下文件:
src\api\text\v1\bank\bank-type.ts
生成api
name (可选参数,如果不填自动根据文件名推导)
type (可选参数,list|tree,如果不填默认未list)
pnpm make:api 模块名/版本号/路径/文件名 --name=模型名 --type=类型示例:
pnpm make:api text/v1/bank/bank-api --name=Bank --type=list自动生成如下文件:
src\api\text\v1\bank\bank-api.ts
同时生成type和api
name (可选参数,如果不填自动根据文件名推导)
type (可选参数,list|tree,如果不填默认未list)
pnpm call:api 模块名/版本号/路径/文件名 --name=模型名 --type=类型示例:
pnpm call:api text/v1/bank/bank-api --name=Bank --type=list自动生成如下文件:
src\api\text\v1\bank\bank-type.ts
src\api\text\v1\bank\bank-api.ts
页面
提示
如果是复杂页面,请自行删除无用代码
name (可选参数,如果不填自动根据文件名前一个目录名推导)
type (可选参数,list|tree,如果不填默认未list)
pnpm make:page 模块名/版本号/路径/文件名 --name=模型名 --type=类型列表示例:
提示
执行前,先确保有对应的type和api文件
pnpm call:api text/v1/bank/bank-api --name=Bank --type=list之后执行:
pnpm make:page text/v1/bank/index --name=Bank --type=list自动生成如下文件:
src\pages\text\v1\bank\index.vue
src\pages\text\v1\bank\components\CreateBankForm.vue
src\pages\text\v1\bank\components\UpdateBankForm.vue
src\pages\text\v1\bank\components\BankSearch.vue
src\pages\text\v1\bank\components\BankTableList.vue
src\pages\text\v1\bank\components\BankListItemDetails.vue
src\pages\text\v1\bank\components\ListItemDetails\BankOne.vue
src\pages\text\v1\bank\components\ListItemDetails\BankTwo.vue
树形示例:
提示
执行前,先确保有对应的type和api文件
pnpm call:api text/v1/region/region-api --name=Region --type=tree之后执行:
pnpm make:page text/v1/region/index --name=Region --type=tree自动生成如下文件:
src\pages\text\v1\region\index.vue
src\pages\text\v1\region\components\CreateRegionForm.vue
src\pages\text\v1\region\components\UpdateRegionForm.vue
src\pages\text\v1\region\components\RegionRowData.vue
同时生成页面加type和api
name (可选参数,如果不填自动根据文件名前一个目录名推导)
type (可选参数,list|tree,如果不填默认未list)
pnpm call:page 模块名/版本号/路径/文件名 --name=模型名 --type=类型列表示例:
pnpm call:page text/v1/bank/index --name=Bank --type=list自动生成如下文件:
api和type
src\api\text\v1\bank\bank-type.ts
src\api\text\v1\bank\bank-api.ts
页面
src\pages\text\v1\bank\index.vue
src\pages\text\v1\bank\components\CreateBankForm.vue
src\pages\text\v1\bank\components\UpdateBankForm.vue
src\pages\text\v1\bank\components\BankSearch.vue
src\pages\text\v1\bank\components\BankTableList.vue
src\pages\text\v1\bank\components\BankListItemDetails.vue
src\pages\text\v1\bank\components\ListItemDetails\BankOne.vue
src\pages\text\v1\bank\components\ListItemDetails\BankTwo.vue
树形示例:
pnpm call:page text/v1/region/index --name=Region --type=tree自动生成如下文件:
api和type
src\api\text\v1\region\region-type.ts
src\api\text\v1\region\region-api.ts
页面
src\pages\text\v1\region\index.vue
src\pages\text\v1\region\components\CreateRegionForm.vue
src\pages\text\v1\region\components\UpdateRegionForm.vue
src\pages\text\v1\region\components\RegionRowData.vue
示例代码
列表示例代码:
api和type
src\api\text\v1\bank\bank-type.ts
import type {
UidType,
IsDeleteType,
BaseGetDataList,
BaseSearchWhere,
FindType,
} from "@/common/type";
//列表
export interface GetBank extends BaseGetDataList {
type?: number;
}
//添加
export interface AddBank {
bank_name: string;
bank_code: string;
type: number;
note: string;
sort: number;
}
//修改
export interface UpdateBank extends AddBank {
id?: number;
bank_uid?: UidType;
}
//删除
export interface DeleteBank {
id?: number;
bank_uid?: UidType;
is_delete?: IsDeleteType;
}
//批量删除
export interface MultipleDeleteBank {
select_id_array?: number[];
select_uid_array: UidType[];
is_delete?: IsDeleteType;
}
//禁用
export interface DisableBank {
id?: number;
bank_uid?: UidType;
}
//批量禁用
export interface MultipleDisableBank {
select_id_array?: number[];
select_uid_array?: UidType[];
}
//搜索
export interface BankSearchWhere extends BaseSearchWhere {
type?: number;
}
//列表单个
export interface BankListItem {
id?: number;
bank_uid?: UidType;
bank_name: string;
bank_code: string;
type: number;
created_at: string;
sort: number;
}
//默认选项
export interface DefaultBank {
limit?: number;
}
//查找选项
export interface FindBank {
find: FindType;
}src\api\text\v1\bank\bank-api.ts
import request from "@/utils/request";
//如果是分库分表
import type { ApiResult } from "@/common/type";
import type {
GetBank,
AddBank,
UpdateBank,
DeleteBank,
MultipleDeleteBank,
DisableBank,
MultipleDisableBank,
DefaultBank,
FindBank,
} from "@/api/text/v1/bank/bank-type";
const Bank_BASE_URL = "/api/v1/admin/bank";
const BankAPI = {
getBank(data: GetBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/getBank`,
method: "post",
data,
});
},
addBank(data: AddBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/addBank`,
method: "post",
data,
});
},
updateBank(data: UpdateBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/updateBank`,
method: "post",
data,
});
},
deleteBank(data: DeleteBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/deleteBank`,
method: "post",
data,
});
},
multipleDeleteBank(data: MultipleDeleteBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/multipleDeleteBank`,
method: "post",
data,
});
},
disableBank(data: DisableBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/disableBank`,
method: "post",
data,
});
},
multipleDisableBank(data: MultipleDisableBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/multipleDisableBank`,
method: "post",
data,
});
},
defaultBank(data: DefaultBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/defaultBank`,
method: "post",
data,
});
},
findBank(data: FindBank) {
return request<any, ApiResult>({
url: `${Bank_BASE_URL}/findBank`,
method: "post",
data,
});
},
};
export default BankAPI;页面
src\pages\text\v1\bank\index.vue
<template>
<div>
<!-- 页面内容开始 -->
<YhCard>
<template #cardTitle>替换列表</template>
<template #cardNotice>
<el-tag size="small" type="danger" effect="plain">提示消息</el-tag>
</template>
<template #cardButton>
<el-row type="flex" :gutter="20">
<el-col :span="12">
<el-button
v-has-role="['develop', 'super']"
style="float: right"
type="success"
round
@click="handleToDownload()"
>
下载导入模板
</el-button>
</el-col>
<el-col :span="12">
<el-button
v-has-role="['develop', 'super']"
style="float: right"
type="primary"
round
@click="handleAddBank()"
>
添加替换
</el-button>
</el-col>
</el-row>
</template>
<template #cardContent>
<!-- 表格布局开始 -->
<YhTableLayout>
<!-- 搜索区开始 -->
<template #search>
<!-- <el-tag type="success">搜索区</el-tag> -->
<el-divider content-position="left">搜索</el-divider>
<BankSearch @update:handle-change-search="toSearch" />
</template>
<!-- 搜索区结束 -->
<!-- 批量操作区开始 -->
<template #batch>
<el-divider content-position="left">批量操作</el-divider>
<el-row type="flex" justify="space-around">
<el-col :span="6" class="btn-action">
<el-button type="primary" @click="handleReverse()">反选</el-button>
</el-col>
<el-col :span="6" class="btn-action">
<el-button type="danger" @click="handleMultipleDelete()">删除</el-button>
</el-col>
<el-col :span="6" class="btn-action">
<el-button type="warning" @click="handleMultipleDisable()">禁用</el-button>
</el-col>
<el-col :span="6" class="btn-action">
<el-button type="primary" @click="handleExport()">导出</el-button>
</el-col>
<!-- <el-col :span="6" class="btn-action">
<YhUploadExcel
file-name="file"
action-url="uploadExcel"
:upload-data="{type:'bank',use_type:20,save_path:'excel'}"
accept=".xlsx,.xls"
type="excel"
@update:file-path="listenToUplaodFile"
/>
</el-col> -->
</el-row>
</template>
<!-- 批量操作区结束-->
<!-- 表格区开始 -->
<template #table>
<el-divider content-position="left">列表</el-divider>
<BankTableList
v-model:data-list="dataListArray"
v-model:dialog-visible="dialogVisible"
v-model:dialog-title="dialogTitle"
v-model:dialog-type="dialogType"
v-model:before-replace-form="beforeBankForm"
:loading="loading"
:reverse-select="reverseSelect"
:multiple-delete="multipleDelete"
:multiple-disable="multipleDisable"
@update:hanlde-update-data="toUpdateData"
/>
</template>
<!-- 表格区结束 -->
<!-- 分页区开始 -->
<template #paginate>
<div>
<YhPaginate
v-model:current-page="currentPage"
v-model:page-size="pageSize"
v-model:total="total"
:page-sizes="[5, 10, 20, 30, 40, 50, 100]"
/>
</div>
</template>
<!-- 分页区结束 -->
</YhTableLayout>
<!-- 表格布局结束-->
</template>
</YhCard>
<!-- 页面内容结束 -->
<!-- 弹窗内容开始 -->
<YhDialog
v-model="dialogVisible"
:dialog-width="dialogWidth"
:dialog-title="dialogTitle"
:dialog-type="dialogType"
>
<!-- 添加表单 -->
<template #addForm>
<CreateBankForm
ref="createFormRef"
@close-dialog="listenToCloseDialog"
/>
</template>
<!-- 修改表单 -->
<template #updateForm>
<UpdateBankForm
ref="updateFormRef"
:props-update-form="propsUpdateForm"
@close-dialog="listenToCloseDialog"
/>
</template>
</YhDialog>
<!-- 弹窗内容结束 -->
</div>
</template>
<script setup lang="ts">
//系统
import { useRouter } from "vue-router";
//工具
import { encryptData } from "@/utils/crypt";
// 公共级组件
import YhCard from "@/pages/laravel-fast-api/v1/components/element/Card/index.vue";
import YhDialog from "@/pages/laravel-fast-api/v1/components/element/Dialog/index.vue";
import YhTableLayout from "@/pages/laravel-fast-api/v1/components/element/Table/Layout/index.vue";
import YhPaginate from "@/pages/laravel-fast-api/v1/components/element/Paginate/index.vue";
// import YhUploadExcel from "@/pages/laravel-fast-api/v1/components/element/Upload/UploadFile/UploadExcel.vue";
//页面级组件
import BankSearch from "./components/BankSearch.vue";
import BankTableList from "./components/BankTableList.vue";
import CreateBankForm from "./components/CreateBankForm.vue";
import UpdateBankForm from "./components/UpdateBankForm.vue";
//store
import { useUserStore } from "@/store";
import type { UidType } from "@/common/type";
//业务接口
import BankAPI from "@/api/text/v1/bank/bank-api";
import type {
GetBank,
UpdateBank,
BankListItem,
} from "@/api/text/v1/bank/bank-type";
import DownloadAPI from "@/api/laravel-fast-api/v1/download/file/downlaod-file-api";
//初始化路由
const router = useRouter();
//store
const userStore = useUserStore();
let unwatchRole: (() => void) | null = null;
//初始化加载样式
const loading = ref(true);
//是否开启弹窗
const dialogVisible = ref(false);
//弹窗标题
const dialogTitle = ref("");
//弹窗类型 10添加 20修改 30内容
const dialogType = ref(10);
//弹窗宽度
const dialogWidth = ref("65%");
//搜索查询条件
const where = reactive<GetBank>({
//查找内容
find: "",
//查找下标
findSelectIndex: 0,
//时间范围
timeRange: [],
//排序方式
sortType: 1,
// 0 不导出 1导出
isExport: 0,
// 导出类型 1本页 2全部
exportType: 10,
// 分页
currentPage: 1,
//每页条数
pageSize: 10,
//数据总数
total: 0,
});
//思路是加1,在子组件监听变化来触发
//标记反选
const reverseSelect = ref(0);
//标记批量删除
const multipleDelete = ref(0);
//标记批量禁用
const multipleDisable = ref(0);
//数据列表容器
const dataListArray = ref([]);
//修改用户的容器
const beforeBankForm = reactive<BankListItem>({
bank_uid: 0,
bank_name: "",
bank_code: "",
type:10,
sort:100,
created_at: ""
});
//修改表单
const propsUpdateForm = reactive<UpdateBank>({
bank_uid: 0,
bank_name: "",
bank_code: "",
type:10,
sort:100,
note:''
});
//将分页处理定义为计算属性
//当前页
const currentPage = computed({
get() {
return where.currentPage;
},
set(newValue) {
where.currentPage = newValue;
},
});
//页面条数
const pageSize = computed({
get() {
return where.pageSize;
},
set(newValue) {
where.pageSize = newValue;
},
});
//计算总数
const total = computed({
get() {
return where.total;
},
set(newValue) {
where.total = newValue;
},
});
//侦听分页变化
watch([currentPage, pageSize], () => {
loading.value = true;
getBankList();
});
//监听beforeBankForm的变化
watch(
() => beforeBankForm,
(newValue) => {
//console.log('当前表单', newValue)
//console.log('之前表单', oldValue)
if (newValue) {
Object.assign(propsUpdateForm, newValue);
}
},
{ deep: true }
);
//初始化分页条件
const initPageWhere = () => {
where.isExport = 0;
where.exportType = 10;
where.currentPage = 1;
where.pageSize = 10;
};
//初始化导出
const initExport = () => {
where.isExport = 0;
where.exportType = 10;
};
//获取列表
const getBankList = async () => {
const result = await BankAPI.getBank(where);
const { data, meta } = result;
dataListArray.value = data;
where.total = meta.total;
where.currentPage = meta.current_page;
where.pageSize = meta.per_page;
setTimeout(() => {
loading.value = false;
}, 500);
};
//下载导图模版
const handleToDownload = async () => {
const result = await DownloadAPI.downloadBank();
const { download } = result;
window.location.href = download as string;
};
//反选
const handleReverse = () => {
reverseSelect.value = reverseSelect.value + 1;
};
//批量删除
const handleMultipleDelete = () => {
multipleDelete.value = multipleDelete.value + 1;
};
//批量禁用
const handleMultipleDisable = () => {
multipleDisable.value = multipleDisable.value + 1;
};
//导出
const handleExport = () => {
where.isExport = 1;
ElMessageBox.confirm("请选择导出本页数据或是全部导出?", "提示", {
// confirmButtonText: 'Re-Login',
confirmButtonText: "全部",
// cancelButtonText: 'Cancel',
cancelButtonText: "本页",
type: "warning",
})
.then(async () => {
where.exportType = 20;
try {
const result = await BankAPI.getBank(where);
ElMessage.success(result.msg);
window.location.href = result.download;
} catch (error) {
const err = error as Error;
ElMessage.error(`导出失败:${err.message || "未知错误"}`);
}
})
.catch(async (action) => {
where.exportType = 10;
if (action === "cancel") {
try {
const result = await BankAPI.getBank(where);
ElMessage.success(result.msg);
window.location.href = result.download;
} catch (error) {
const err = error as Error;
ElMessage.error(`导出失败:${err.message || "未知错误"}`);
}
}
if (action === "close") {
where.isExport = 0;
}
return;
});
};
//添加
const handleAddBank = () => {
dialogTitle.value = "添加替换";
dialogType.value = 10;
dialogVisible.value = true;
//复杂业务处理
const param = { bank_uid: 0 };
const encryptString = encryptData(param);
router.push({
name: "Bank",
query: { bank: encryptString }, // 注意:query参数建议序列化,避免对象类型问题
});
};
//监听搜索
const toSearch = (param: any) => {
//console.log(param)
initPageWhere();
initExport();
Object.assign(where, param);
loading.value = true;
getBankList();
};
//监听更新数据
const toUpdateData = () => {
loading.value = true;
//重新获取列表
getBankList();
};
//监听关闭弹窗
const listenToCloseDialog = () => {
loading.value = true;
//重新获取列表
getBankList();
//初始化修改表单
initPropsUpdateForm();
//初始化查看详情
//initPropsRowData()
dialogVisible.value = false;
};
//初始化更新表单
const initPropsUpdateForm = () => {
Object.assign(propsUpdateForm, {
bank_uid: 0,
bank_name: "",
bank_code: ""
});
};
//监听上传文件
// const listenToUplaodFile = (filePath: string) => {
// loading.value = true;
// //重新获取用户列表
// getBankList();
// //初始化修改表单
// initPropsUpdateForm();
// //初始化查看详情
// //initPropsRowData()
// dialogVisible.value = false;
// };
//页面加载
onMounted(() => {
//监听用户角色
unwatchRole = userStore.watchRoles();
getBankList();
});
//组件卸载
onUnmounted(() => {
// 组件卸载时停止监听,防止内存泄漏
if (unwatchRole) {
unwatchRole();
}
});
</script>
<style scoped></style>src\pages\text\v1\bank\components\CreateBankForm.vue
<template>
<div>
<el-form
ref="createFormRef"
:model="createForm"
:rules="createRules"
label-width="150px"
:inline="true"
label-position="left"
status-icon
inline-message
style="
width: 80%;
padding: 30px 20px;
margin-right: auto;
margin-left: auto;
text-align: center;
"
>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="名称" prop="bank_name">
<el-input
v-model="createForm.bank_name"
placeholder="请输入名称"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="值" prop="bank_code">
<el-input
v-model="createForm.bank_code"
placeholder="请输入值"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="类型" prop="type">
<el-select v-model="createForm.type" placeholder="请选择" class="select">
<el-option
v-for="item in chooseOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="描述" prop="note">
<el-input
v-model="createForm.note"
placeholder="描述"
type="textarea"
:rows="3"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="排序" prop="sort">
<el-input v-model="createForm.sort" placeholder="排序" clearable class="yh-input" />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center" style="text-align: center">
<el-col :span="12">
<el-form-item>
<el-button type="primary" @click="submitForm(createFormRef)">添加替换</el-button>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button @click="resetForm(createFormRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
//系统
import type { FormInstance, FormRules } from "element-plus";
//api和type
import type { AddBank } from "@/api/text/v1/bank/bank-type";
import BankAPI from "@/api/text/v1/bank/bank-api";
//定义组件名称
defineOptions({
name: "CreateBankForm",
});
const createFormRef = ref<FormInstance>();
const createForm = reactive< AddBank>({
bank_name: "",
bank_code: "",
type: 10,
note: "",
sort: 100,
});
//选项
const chooseOptions = [
{ label: "本地", value: 10 },
{ label: "云端", value: 20 },
];
const createRules = reactive<FormRules<AddBank>>({
bank_name: [{ type: "string", required: true, message: "必须有名称", trigger: "blur" }],
bank_code: [{ type: "string", required: true, message: "必须有名称", trigger: "blur" }],
type: [{ type: "number", required: true, message: "必须有值", trigger: "blur" }],
});
//初始化表单
const initCreateForm = () => {
createForm.bank_name = "";
createForm.bank_code = "";
createForm.type = 10;
createForm.note = "";
createForm.sort = 0;
};
//emit
const emit = defineEmits(["close-dialog"]);
//提交
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
try {
// 1. 表单校验(async/await 语法更简洁)
const valid = await formEl.validate();
// 2. 调用后端新增接口
if (valid) {
//console.log(createForm)
const result = await BankAPI.addBank(createForm);
// 3. 提交成功提示
ElMessage.success(result.msg);
// 4. 重置表单(可选,根据业务需求)
resetForm(formEl);
// 初始化表单
initCreateForm();
// 6. 关闭对话框(可选,根据业务需求)
emit("close-dialog", false);
}
} catch (error) {
// 5. 处理错误(校验失败或接口报错)
const err = error as Error;
ElMessage.error(`提交失败:${err.message || "未知错误"}`);
//console.log('提交错误:', error);
}
};
//重置
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style lang="scss" scoped>
/**表单 */
.content :deep(.el-form-item__label) {
width: 100px;
}
.path-item {
position: relative;
left: -100px;
}
.select,
.input {
width: 300px;
}
.yh-input {
width: 300px;
}
</style>src\pages\text\v1\bank\components\UpdateBankForm.vue
<template>
<div>
<el-form
ref="updateFormRef"
:model="updateForm"
:rules="updateRules"
label-width="150px"
:inline="true"
label-position="left"
status-icon
inline-message
style="
width: 80%;
padding: 30px 20px;
margin-right: auto;
margin-left: auto;
text-align: center;
"
>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="名称" prop="bank_name">
<el-input
v-model="updateForm.bank_name"
placeholder="请输入名称"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="值" prop="bank_code">
<el-input
v-model="updateForm.bank_code"
placeholder="请输入值"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="类型" prop="type">
<el-select v-model="updateForm.type" placeholder="请选择" class="select">
<el-option
v-for="item in chooseOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="描述" prop="note">
<el-input
v-model="updateForm.note"
placeholder="描述"
type="textarea"
:rows="3"
clearable
class="yh-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="排序" prop="sort">
<el-input v-model="updateForm.sort" placeholder="排序" clearable class="yh-input" />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center" style="text-align: center">
<el-col :span="12">
<el-form-item>
<el-button type="primary" @click="submitForm(updateFormRef)">编辑替换</el-button>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button @click="resetForm(updateFormRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
//系统
import type { FormInstance, FormRules } from "element-plus";
//api和type
import type { UpdateBank } from "@/api/text/v1/bank/bank-type";
import BankAPI from "@/api/text/v1/bank/bank-api";
//定义组件名称
defineOptions({
name: "UpdateBankForm",
});
const updateFormRef = ref<FormInstance>();
const updateForm = reactive<UpdateBank>({
bank_uid: 0,
bank_name: "",
bank_code: "",
type: 10,
note: "",
sort: 100,
});
//选项
const chooseOptions = [
{ label: "本地", value: 10 },
{ label: "云端", value: 20 },
];
const updateRules = reactive<FormRules<UpdateBank>>({
bank_name: [{ type: "string", required: true, message: "必须有名称", trigger: "blur" }],
bank_code: [{ type: "string", required: true, message: "必须有名称", trigger: "blur" }],
type: [{ type: "number", required: true, message: "必须有值", trigger: "blur" }]
});
//初始化表单
const initUpdateForm = () => {
updateForm.bank_uid = 0;
updateForm.bank_name = "";
updateForm.bank_code = "";
updateForm.type = 10;
updateForm.note = "";
updateForm.sort = 0;
};
const {
propsUpdateForm = {
id: 0,
bank_uid: 0,
bank_name: "",
bank_code: "",
type: 10,
note: "",
sort: 100,
},
} = defineProps<{
propsUpdateForm?: UpdateBank;
}>();
//监听父级表单值的变化
watch(
() => propsUpdateForm,
(newVal) => {
//console.log('整个对象变化:', newVal, oldVal)
Object.assign(updateForm, newVal);
},
{ deep: true, immediate: true } // 因为是对象,需要开启深度监听
);
//emit
const emit = defineEmits(["close-dialog"]);
//提交
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
try {
// 1. 表单校验(async/await 语法更简洁)
const valid = await formEl.validate();
// 2. 调用后端新增接口
if (valid) {
//console.log(updateForm)
const result = await BankAPI.updateBank(updateForm);
// 3. 提交成功提示
ElMessage.success(result.msg);
// 4. 重置表单(可选,根据业务需求)
resetForm(formEl);
// 初始化表单
initUpdateForm();
// 6. 关闭对话框(可选,根据业务需求)
emit("close-dialog", false);
}
} catch (error) {
// 5. 处理错误(校验失败或接口报错)
const err = error as Error;
ElMessage.error(`提交失败:${err.message || "未知错误"}`);
//console.log('提交错误:', error);
}
};
//重置
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style lang="scss" scoped>
/**表单 */
.content :deep(.el-form-item__label) {
width: 100px;
}
.path-item {
position: relative;
left: -100px;
}
.select,
.input {
width: 300px;
}
.yh-input {
width: 300px;
}
</style>src\pages\text\v1\bank\components\BankSearch.vue
<template>
<div>
<!-- 第一行开始 -->
<el-row :gutter="20">
<!-- 第一列开始 -->
<el-col :span="12">
<div class="inline">
<el-tag style="height: 2rem; line-height: 2rem" type="info">搜索选项</el-tag>
<el-input
v-model="where.find"
clearable
:placeholder="findSelect[where.findSelectIndex as number].label"
>
<template #prepend>
<el-select v-model="where.findSelectIndex" placeholder="请选择">
<el-option label="替换1" :value="0" />
<el-option label="替换2" :value="1" />
</el-select>
</template>
<template #append>
<el-button icon="Search" @click="handleSearchFind()" />
</template>
</el-input>
</div>
</el-col>
<!-- 第一列结束 -->
<!-- 第二列开始 -->
<el-col :span="12">
<div class="inline">
<el-tag style="height: 2rem; line-height: 2rem" type="info">时间</el-tag>
<RangeSelectDateTime v-model:past-time-range="where.timeRange"></RangeSelectDateTime>
</div>
</el-col>
<!-- 第二列结束 -->
</el-row>
<!-- 第一行结束 -->
<!-- 第二行开始 -->
<el-row :gutter="20">
<el-col :span="8">
<div class="inline">
<el-tag style="height: 2rem; line-height: 2rem" type="info">排序</el-tag>
<el-select
v-model="where.sortType"
placeholder="请选择排序方式"
style="margin-right: 0.5rem"
>
<el-option
v-for="item in sortOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</el-col>
<el-col :span="2" :push="14">
<div class="inline-right">
<el-button type="primary" @click="handleToSearch">搜索</el-button>
</div>
</el-col>
</el-row>
<!-- 第二行结束 -->
</div>
</template>
<script setup lang="ts">
import type { OptionItem } from "@/common/type";
import type {
BankSearchWhere,
} from "@/api/text/v1/bank/bank-type";
import RangeSelectDateTime from "@/pages/laravel-fast-api/v1/components/element/Time/RangeSelectDateTime.vue";
import { throttle } from "@/utils";
//定义组件名称
defineOptions({
name: "BankSearch",
});
// 搜索条件
const where = ref<BankSearchWhere>({
find: "",
findSelectIndex: 0,
timeRange: [],
sortType: 2,
});
// 搜索类型选项
const findSelect: OptionItem[] = [
{ label: "替换1", value: 0 },
{ label: "替换2", value: 1 },
];
// 排序选项
const sortOptions: OptionItem[] = [
{ value: 1, label: "按时间升序" },
{ value: 2, label: "按时间降序" },
];
// 定义 emits
const emit = defineEmits(["update:handleChangeSearch"]);
// 执行搜索
const handleToSearch = throttle(() => {
emit("update:handleChangeSearch", where.value);
}, 1000);
// 单独查找
const handleSearchFind = throttle(() => {
emit("update:handleChangeSearch", where.value);
}, 1000);
</script>
<style lang="scss" scoped>
.inline {
display: flex;
flex-direction: row;
justify-content: flex-start;
margin-top: 1rem;
}
.inline-right {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 1rem;
}
</style>src\pages\text\v1\bank\components\BankTableList.vue
<template>
<div>
<el-table
ref="bankTable"
v-loading="loading"
element-loading-text="拼命加载中"
:element-loading-spinner="LOADING_SVG"
element-loading-svg-view-box="-10, -10, 50, 50"
element-loading-background="rgba(38, 38, 38, 0.8)"
:data="dataList"
border
stripe
highlight-current-row
empty-text="暂无数据"
fit
height="70vh"
style="width: 100%"
:row-key="getRowKeys"
:expand-row-keys="expandIds"
@selection-change="handleSelectionChange"
@expand-change="handleExpandChange"
>
<el-table-column type="selection" align="center" width="55" />
<el-table-column label="序号" type="index" width="55" align="center" fixed />
<el-table-column label="详情" type="expand" align="center" fixed>
<template #default="scope">
<BankListItemDetails
v-model:row-data="scope.row"
v-model:show-index="showIndex"
/>
</template>
</el-table-column>
<el-table-column
prop="type"
label="类型"
width="100"
:filters="[
{ text: '本地', value: '10' },
{ text: '云端', value: '20' },
]"
:filter-method="filterType"
filter-placement="bottom-end"
align="center"
>
<template #default="scope">
<el-tag v-show="scope.row.type == 10" type="info">本地</el-tag>
<el-tag v-show="scope.row.type == 20" type="warning">云端</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="bank_name" label="名称" min-width="300" />
<el-table-column align="center" prop="bank_code" label="值" min-width="300" />
<el-table-column align="center" prop="created_at" label="注册时间" min-width="200" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<!-- 编辑 -->
<el-button
size="small"
type="primary"
plain
@click="handleToEditPage(scope.$index, scope.row)"
>
编辑
</el-button>
<el-button
size="small"
type="danger"
el-button
@click="handleToDelete(scope.$index, scope.row)"
>
删除
<!-- 删除(禁用)结束 -->
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
//系统
// import { useRouter } from "vue-router";
import type { TableInstance } from "element-plus";
//store
import { useUserStore } from "@/store";
//工具
//import { encryptData } from "@/utils/crypt";
//加载动画
import { LOADING_SVG } from "@/common/svg/svg-icon";
//组件
import BankListItemDetails from "./BankListItemDetails.vue";
import type { UidType } from "@/common/type";
import type {
DeleteBank,
UpdateBank,
BankListItem,
MultipleDeleteBank
} from "@/api/text/v1/bank/bank-type";
import BankAPI from "@/api/text/v1/bank/bank-api";
//定义组件
defineOptions({
name: "BankTableList",
});
const userStore = useUserStore();
let unwatchRole: (() => void) | null = null;
//defineProps
const {
loading = false,
reverseSelect = 0,
multipleDelete = 0,
} = defineProps<{
//加载动画效果
loading?: boolean;
//反选标记
reverseSelect?: number;
//批量删除标记
multipleDelete?: number;
//批量禁用标记
multipleDisable?: number;
}>();
//弹框是否开启
const dialogVisible = defineModel<boolean>("dialogVisible", {
default: false,
});
//弹框标题
const dialogTitle = defineModel<string>("dialogTitle", {
default: "弹框标题",
});
//弹框类型
const dialogType = defineModel<number>("dialogType", {
default: 10,
});
//
const beforeBankForm = defineModel<UpdateBank>("beforeBankForm", {
default: () => {
return {
bank_uid: 0,
bank_name: "",
bank_code: "",
type: 10,
note: "",
sort: 100,
};
},
});
//选中对象的数组
const multipleSelection = ref<BankListItem[]>([]);
//选中元素的下标
const selectIndex = ref<number[]>([]);
//反选元素的下标
const diffIndex = ref<number[]>([]);
//选中的下标
const select_uid_array = ref<UidType[]>([]);
// 存放展开的id, 每次只展开一行
const getRowKeys = (row: BankListItem) => {
return String(row.bank_uid);
};
const expandIds = ref<string[]>([]);
//emit
const emit = defineEmits(["update:hanldeUpdateData"]);
//定义数据列表
const dataList = defineModel<BankListItem[]>("dataList", {
default: () => [],
});
//初始化路由
// const router = useRouter();
// 获取组件实例(用于调用emit)
// const instance = getCurrentInstance();
// 获取表格实例(需与模板中ref对应)
const bankTable = ref<TableInstance | null>(null);
//头像加载失败
// const errorHandler = () => {
// return false;
// };
//类型
const filterType = (value: string, row: BankListItem) => {
return Number(row.type) == Number(value);
};
//编辑
const handleToEditPage = (index: number, row: BankListItem) => {
//复杂情景跳转页面
//console.log(row)
// let encryptString = encryptData(row);
// router.push({
// name: 'editBank',
// query: { row: encryptString } // 注意:query参数建议序列化,避免对象类型问题
// })
//简单情景弹框修改
dialogVisible.value = true;
dialogTitle.value = "编辑替换";
dialogType.value = 20;
Object.assign(beforeBankForm.value, row);
};
//删除
const handleToDelete = (index: number, row: BankListItem) => {
const param: DeleteBank= { bank_uid: Number(row.bank_uid), is_delete: 1 };
ElMessageBox.confirm("确认删除吗?", "确认删除", {
// confirmButtonText: 'Re-Login',
confirmButtonText: "删除",
// cancelButtonText: 'Cancel',
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
const result = await BankAPI.deleteBank(param);
ElMessage.success(result.msg);
emit("update:hanldeUpdateData", true);
} catch {
//const err = error as Error;
//ElMessage.error(`提交失败:${err.message || '未知错误'}`);
}
})
.catch(() => {
return;
});
};
// 多选处理
const handleSelectionChange = (val: any[]) => {
multipleSelection.value = val;
selectIndex.value = []; // 重置选中索引数组
// 遍历选中项,收集它们在数据列表中的索引
multipleSelection.value.forEach((item) => {
const index = dataList.value.findIndex((dataItem) => dataItem.bank_uid === item.bank_uid);
if (index !== -1) {
selectIndex.value.push(index);
}
});
};
// 展开行处理
const handleExpandChange = (row: any, expandedRows: any[]) => {
// 假设原有逻辑中的show是控制某些显示状态,这里保留类似逻辑
// 可根据实际需求补充show的定义和用途
// const show = ref(0);
// show.value = 1;
// 判断当前行是否在已展开的行数组中(即是否展开状态)
const isExpanded = expandedRows.includes(row);
if (isExpanded) {
// 展开时:只保留当前行的展开状态(关闭其他行)
expandIds.value = [row.bank_uid.toString()];
} else {
// 收起时:清空展开状态
expandIds.value = [];
}
};
//列表详情显示的下标
const showIndex = ref(1);
// 监听反选(对应props.reverseSelect)
watch(
() => reverseSelect,
(newValue) => {
if (!newValue) return; // 非触发状态不执行
diffIndex.value = [];
const allIndexes: number[] = [];
// 生成所有行的索引数组
for (let i = 0; i < dataList.value.length; i++) {
allIndexes.push(i);
}
// 计算反选索引(不在已选索引中的索引)
diffIndex.value = allIndexes.filter((index) => !selectIndex.value.includes(index));
// 执行反选操作
bankTable.value?.clearSelection();
diffIndex.value.forEach((index) => {
const row = dataList.value[index];
if (row) {
bankTable.value?.toggleRowSelection(row, true);
}
});
// 更新选中索引为反选索引
selectIndex.value = [...diffIndex.value];
}
);
// 监听批量删除(对应props.multipleDelete)
watch(
() => multipleDelete,
(newValue) => {
if (!newValue) return;
select_uid_array.value = []; // 重置为id数组
const params: MultipleDeleteBank = {
select_uid_array: [] as UidType[],
is_delete: 1,
};
// 直接收集选中项的id(核心修正)
multipleSelection.value.forEach((item) => {
select_uid_array.value.push(Number(item.bank_uid)); // 存储id
params.select_uid_array.push(Number(item.bank_uid));
});
if (select_uid_array.value.length === 0) {
ElMessage.error("请先选择要删除的选项");
return;
}
// 确认对话框逻辑不变...
ElMessageBox.confirm("确认要批量删除吗?", "确认信息", {
distinguishCancelAndClose: true,
confirmButtonText: "删除",
cancelButtonText: "取消",
})
.then(() => {
BankAPI.multipleDeleteBank(params).then((result) => {
ElMessage.success(result.msg);
emit("update:hanldeUpdateData", true);
});
})
.catch(() => {});
}
);
//组件加载
onMounted(() => {
//监听用户角色
unwatchRole = userStore.watchRoles();
});
//组件卸载
onUnmounted(() => {
// 组件卸载时停止监听,防止内存泄漏
if (unwatchRole) {
unwatchRole();
}
});
</script>
<style lang="scss" scoped></style>src\pages\text\v1\bank\components\BankListItemDetails.vue
<template>
<div class="expanded">
<div class="header">
<el-row type="flex">
<el-button type="primary" size="small" @click="handleClick(1)">
<el-icon style="vertical-align: middle">
<Document />
</el-icon>
详情
</el-button>
<!-- <el-button type="primary" :icon="Avatar" size="small" @click="handleClick(2)">ReplaceTwo</el-button> -->
</el-row>
</div>
<div class="content">
<!-- 复杂处理开始 -->
<!-- <div :class="{show:showIndex==1, hidden:showIndex!=1}">
<BankListItemOne :bank="rowData" :upload-data="{id:0, use_type:30, file_type: '', save_path: 'avatar', bank_uid: rowData.bank_uid }" />
</div>
<div :class="{show:showIndex==2, hidden:showIndex!=2}">
<ReplaceTwo v-if="showComponent === 2" :row-list="rowData" />
</div> -->
<!-- 复杂处理结束 -->
<!-- 简单的处理开始 -->
<div :class="{ show: showIndex == 1, hidden: showIndex != 1 }">
<!-- 真正的一行 -->
<el-row class="row-one" :gutter="10">
<el-tag type="info" style="margin-right: 1rem">配置项类型</el-tag>
<el-tag v-if="Number(rowData.type) === 20" style="margin-right: 1rem" type="primary">
字符串
</el-tag>
<el-tag v-if="Number(rowData.type) === 30" style="margin-right: 1rem" type="success">
数值
</el-tag>
<el-tag v-if="Number(rowData.type) === 40" style="margin-right: 1rem" type="warning">
路径
</el-tag>
</el-row>
<!-- 真正的一行结束 -->
<!-- 真正的一行 -->
<el-row class="row-one">
<el-tag style="margin-right: 1rem" type="info">名称</el-tag>
<el-tag style="margin-right: 1rem" type="primary">{{ rowData.bank_name }}</el-tag>
<el-tag style="margin-right: 1rem" type="info">值</el-tag>
<el-tag style="margin-right: 1rem" type="primary">
{{ rowData.bank_code ? rowData.bank_code : "无" }}
</el-tag>
</el-row>
<el-row class="row-one">
<el-tag style="margin-right: 1rem" type="info">排序</el-tag>
<el-tag style="margin-right: 1rem" type="primary">{{ rowData.sort }}</el-tag>
</el-row>
<el-row class="row-one">
<el-tag style="margin-right: 1rem" type="info">创建时间</el-tag>
<el-tag style="margin-right: 1rem" type="primary">{{ rowData.created_at }}</el-tag>
</el-row>
</div>
<!-- 简单的处理结束 -->
</div>
</div>
</template>
<script setup lang="ts">
//系统
import { Document } from "@element-plus/icons-vue";
//组件(复杂逻辑使用)
// import BankOne from './ListItemDetails/BankOne.vue'
//import BankTwo from './ListItemDetails/BankTwo.vue'
import type { UidType } from "@/common/type";
import type {
BankListItem,
} from "@/api/text/v1/bank/bank-type";
//定义组件名称
defineOptions({
name: "BankListItemDetails",
});
//控制显示的组件
const showComponent = ref(1);
//定义行数据
const rowData = defineModel<BankListItem>("rowData", {
default: () => {},
});
//定义显示的下标
const showIndex = defineModel<number>("showIndex", {
default: () => 1,
});
//点击按钮
const handleClick = (index: number) => {
showIndex.value = index;
showComponent.value = index;
};
</script>
<style lang="scss" scoped>
.expanded {
padding: 15px;
}
.header {
margin-bottom: 1rem;
}
.show {
display: block;
}
.hidden {
display: none;
}
</style>src\pages\text\v1\bank\components\ListItemDetails\BankOne.vue
<template>
<div>BankOne</div>
</template>
<script setup lang="ts">
//定义组件名称
defineOptions({
name: "BankOne",
});
</script>
<style lang="scss" scoped></style>src\pages\text\v1\bank\components\ListItemDetails\BankTwo.vue
<template>
<div>BankTwo</div>
</template>
<script setup lang="ts">
//定义组件名称
defineOptions({
name: "BankTwo",
});
</script>
<style lang="scss" scoped></style>树形示例代码:
api和type
src\api\text\v1\region\region-type.ts
import type { BaseGetDataList,NodeDropType } from "@/common/type";
//树形
export interface GetTreeRegion extends BaseGetDataList {
type?: number;
}
//单个树节点
export interface RegionTreeItem {
id: number;
parent_id: number;
deep: number;
region_name: string;
region_code: string;
sort: number;
children?: RegionTreeItem[];
}
export interface AddRegion {
id: number;
parent_id: number;
deep: number;
region_name: string;
region_code: string;
sort: number;
}
export interface UpdateRegion {
id: number;
region_name: string;
region_code: string;
sort: number;
}
export interface DeleteRegion {
id: number;
}
export interface MoveRegion {
id: number;
parent_id: number;
dropType: NodeDropType;
sort: number;
}src\api\text\v1\region\region-api.ts
import request from "@/utils/request";
//如果是分库分表
import type { ApiResult } from "@/common/type";
import type {
AddRegion,
UpdateRegion,
DeleteRegion,
MoveRegion,
} from "@/api/text/v1/region/region-type";
const Region_BASE_URL = "/api/v1/admin/region";
const RegionAPI = {
getTreeRegion() {
return request<any, ApiResult>({
url: `${Region_BASE_URL}/getRegion`,
method: "post",
});
},
addRegion(data: AddRegion) {
return request<any, ApiResult>({
url: `${Region_BASE_URL}/addRegion`,
method: "post",
data,
});
},
updateRegion(data: UpdateRegion) {
return request<any, ApiResult>({
url: `${Region_BASE_URL}/updateRegion`,
method: "post",
data,
});
},
deleteRegion(data: DeleteRegion) {
return request<any, ApiResult>({
url: `${Region_BASE_URL}/deleteRegion`,
method: "post",
data,
});
},
moveRegion(data: MoveRegion) {
return request<any, ApiResult>({
url: `${Region_BASE_URL}/moveRegion`,
method: "post",
data,
});
}
};
export default RegionAPI;页面
src\pages\text\v1\region\index.vue
<template>
<div>
<!-- 页面内容开始 -->
<YhCard :loading="loading">
<template #cardTitle>替换名称</template>
<template #cardNotice>
<el-tag size="small" type="danger" effect="plain">提示消息</el-tag>
</template>
<template #cardButton>
<el-button
v-has-role="['develop']"
style="float: right"
type="primary"
@click="handleAddTop()"
>
添加顶级替换
</el-button>
</template>
<template #cardContent>
<div class="custom-node">
<el-tree
:data="dataList"
:props="defaultProps"
node-key="id"
draggable
:allow-drag="allowDrag"
@node-drop="handleDrop"
>
<template #default="{ node, data }">
<div class="custom-tree-node">
<div>{{ node.label }}</div>
<div>
<el-button
v-has-role="['develop']"
type="primary"
size="small"
plain
round
@click.stop="() => handleAddChild(node, data)"
>
添加下级
</el-button>
<el-button
v-has-role="['develop']"
type="success"
size="small"
plain
round
@click.stop="() => handleToShow(node, data)"
>
查 看
</el-button>
<el-button
v-has-role="['develop']"
type="warning"
size="small"
plain
round
@click.stop="() => handleToUpdate(node, data)"
>
修改
</el-button>
<el-button
v-has-role="['develop']"
type="danger"
size="small"
plain
round
@click.stop="() => handleToRemove(node, data)"
>
删除
</el-button>
</div>
</div>
</template>
</el-tree>
</div>
</template>
</YhCard>
<!-- 页面内容结束 -->
<!-- 弹窗内容开始 -->
<YhDialog
v-model="dialogVisible"
:dialog-width="dialogWidth"
:dialog-title="dialogTitle"
:dialog-type="dialogType"
>
<!-- 添加表单 -->
<template #addForm>
<CreateRegionForm
ref="createFormRef"
:props-create-form="propsCreateForm"
@close-dialog="listenToCloseDialog"
></CreateRegionForm>
</template>
<!-- 修改表单 -->
<template #updateForm>
<UpdateRegionForm
ref="updateFormRef"
:props-update-form="propsUpdateForm"
@close-dialog="listenToCloseDialog"
></UpdateRegionForm>
</template>
<!-- 显示内容 -->
<template #showContent>
<RegionRowData
:props-row-data="propsRowData"
:props-show-index="propsShowIndex"
@update:show-index="listenToUpdateIndex"
@close-dialog="listenToCloseDialog"
></RegionRowData>
</template>
</YhDialog>
<!-- 弹窗内容结束 -->
</div>
</template>
<script setup lang="ts">
//系统级
import type { NodeDropType, RenderContentContext, TreeNode } from "element-plus";
//store
import { useUserStore } from "@/store";
//重置路由
import { debounce } from "@/utils/index";
// 公共级组件
import YhCard from "@/pages/laravel-fast-api/v1/components/element/Card/index.vue";
import YhDialog from "@/pages/laravel-fast-api/v1/components/element/Dialog/index.vue";
//页面级组件
import CreateRegionForm from "./components/CreateRegionForm.vue";
import UpdateRegionForm from "./components/UpdateRegionForm.vue";
import RegionRowData from "./components/RegionRowData.vue";
//api和type
import RegionAPI from "@/api/text/v1/region/region-api";
import type {
RegionTreeItem,
AddRegion,
UpdateRegion,
} from "@/api/text/v1/region/region-type";
import type { MoveTree } from "@/common/type";
type Node = RenderContentContext["node"];
const userStore = useUserStore();
let unwatchRole: (() => void) | null = null;
//初始化加载样式
const loading = ref(true);
//是否开启弹窗
const dialogVisible = ref(false);
//弹窗标题
const dialogTitle = ref("");
//弹窗类型 10添加 20修改 30内容
const dialogType = ref(10);
//弹窗宽度
const dialogWidth = ref("65%");
//定义拖拽参数
const dragParam = ref<MoveTree>({
id: 0,
parent_id: 0,
sort: 0,
dropType: "none",
});
//源数据
const soureTreeData = ref<RegionTreeItem[]>([]);
//计算数据列表
const dataList = computed({
get() {
return soureTreeData.value;
},
set(newValue) {
//console.log('newValue',newValue)
soureTreeData.value = newValue;
},
});
//创建表单
const propsCreateForm = ref<AddRegion>({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
});
// 更新表单
const propsUpdateForm = ref<UpdateRegion>({
id: 0,
region_name: "",
region_code: "",
sort: 100,
});
//查看单挑数据
const propsRowData = ref<RegionTreeItem>({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
});
//定义查看下标
const propsShowIndex = ref(0);
/**
* 设置对话框状态
*/
const setDialogStatus = (visible: boolean, title: string, type: number) => {
dialogVisible.value = visible;
dialogTitle.value = title;
dialogType.value = type;
};
/**
* 处理添加顶级菜单
*/
const handleAddTop = () => {
initPropsCreateForm();
setDialogStatus(true, "添加顶级", 10);
};
/**
* 处理添加下级
*/
const handleAddChild = (node: TreeNode, data: RegionTreeItem) => {
//console.log('data',data)
propsCreateForm.value.parent_id = data.id;
propsCreateForm.value.deep = Number(data.deep) + 1;
setDialogStatus(true, "添加下级", 10);
};
/**
* 更新
*/
const handleToUpdate = (node: TreeNode, data: RegionTreeItem) => {
//console.log('data',data)
Object.assign(propsUpdateForm.value, data);
setDialogStatus(true, "修改", 20);
};
/**
* 删除
*/
const handleToRemove = (node: TreeNode, data: RegionTreeItem) => {
ElMessageBox.confirm("确认删除吗?", "确认删除", {
// confirmButtonText: 'Re-Login',
confirmButtonText: "删除",
// cancelButtonText: 'Cancel',
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
const result = await RegionAPI.deleteRegion(data);
getTreeRegion();
ElMessage.success(result.msg);
} catch {
ElMessage.error("删除失败");
}
})
.catch(() => {
return;
});
};
/**
* 查看详情
*/
const handleToShow = (node: TreeNode, data: RegionTreeItem) => {
//初始化查看详情
initPropsRowData();
Object.assign(propsRowData.value, data);
setDialogStatus(true, "查看", 30);
};
//拖拽
const handleDrop = debounce(function (draggingNode: Node, dropNode: Node, dropType: NodeDropType) {
//console.log('tree drop:', draggingNode,dropNode.label, dropType)
dragParam.value.id = draggingNode.data.id;
dragParam.value.parent_id = draggingNode.data.parent_id;
dragParam.value.sort = draggingNode.data.sort;
dragParam.value.dropType = dropType;
// 如果是inner,那么parent_id就是 dropNode.data.id,否则就是同级的父id
if (dropType === "inner") {
dragParam.value.parent_id = dropNode.data.id;
} else {
dragParam.value.parent_id = dropNode.data.parent_id;
if (dropType === "after") {
dragParam.value.sort = dropNode.data.sort + 1;
}
if (dropType === "before") {
dragParam.value.sort = dropNode.data.sort + 1;
}
}
RegionAPI.moveRegion(dragParam.value).then(() => {
//console.log(res)
//初始化拖拽参数
initDragParam();
//重新加载
getTreeRegion();
ElMessage.success("移动成功");
});
}, 1000);
//是否可以拖动
const allowDrag = () => {
return true;
};
//树形组件props
const defaultProps = {
children: "children",
label: "region_name",
};
//获取服务器树形路由
const getTreeRegion = async () => {
const result = await RegionAPI.getTreeRegion();
const { data } = result;
soureTreeData.value = data;
};
//监听关闭弹窗
const listenToCloseDialog = () => {
//重新加载
getTreeRegion();
//初始化表单
initPropsCreateForm();
initPropsUpdateForm();
//初始化查看详情
initPropsRowData();
dialogVisible.value = false;
};
//监听切换下标
const listenToUpdateIndex = () => {
//console.log('index',index)
//updateIndex.value = index
};
//初始化创建表单传值
const initPropsCreateForm = () => {
propsCreateForm.value.parent_id = 0;
propsCreateForm.value.deep = 1;
};
//初始化更新表单传值
const initPropsUpdateForm = () => {
propsUpdateForm.value.id = 0;
propsUpdateForm.value. region_name = "";
propsUpdateForm.value.sort = 100;
};
//初始化查看详情
const initPropsRowData = () => {
propsRowData.value.id = 0;
propsRowData.value.parent_id = 0;
propsRowData.value.deep = 1;
propsRowData.value. region_name = "";
propsRowData.value.children = [];
propsRowData.value.sort = 100;
};
//初始化拖拽容器
const initDragParam = () => {
dragParam.value.id = 0;
dragParam.value.parent_id = 0;
dragParam.value.sort = 0;
dragParam.value.dropType = "none";
};
//页面加载完成
onMounted(() => {
//监听用户角色
unwatchRole = userStore.watchRoles();
getTreeRegion();
//加载效果
setTimeout(() => {
loading.value = false;
}, 1000);
});
//组件卸载
onUnmounted(() => {
// 组件卸载时停止监听,防止内存泄漏
if (unwatchRole) {
unwatchRole();
}
});
</script>
<style lang="scss" scoped>
.custom-tree-node {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
padding-right: 1rem;
font-size: 1rem;
}
</style>src\pages\text\v1\region\components\CreateRegionForm.vue
<template>
<div>
<el-form
ref="createFormRef"
:model="createForm"
:rules="createRules"
label-width="220px"
:inline="true"
label-position="left"
status-icon
inline-message
style="
width: 80%;
padding: 30px 20px;
margin-right: auto;
margin-left: auto;
text-align: center;
"
>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="排序" prop="sort">
<el-input v-model="createForm.sort" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="替换内容" prop="region_name">
<el-input v-model="createForm.region_name" placeholder="提示内容" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="替换内容" prop="region_code">
<el-input v-model="createForm.region_code" placeholder="提示内容" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center" style="text-align: center">
<el-col :span="6">
<el-form-item>
<el-button type="primary" @click="submitForm(createFormRef)">添加替换</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button @click="resetForm(createFormRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
//系统
import type { FormInstance, FormRules } from "element-plus";
//api和type
import RegionAPI from "@/api/text/v1/region/region-api";
import type {
AddRegion,
} from "@/api/text/v1/region/region-type";
//定义组件名称
defineOptions({
name: "CreateRegionForm",
});
const createFormRef = ref<FormInstance>();
const createForm = reactive<AddRegion>({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
});
const createRules = reactive<FormRules<AddRegion>>({
region_name: [
{ type: "string", required: true, message: "请输入标题显示名称", trigger: "blur" },
{
pattern: /^([\u4e00-\u9fa5]|[a-zA-Z0-9_-])+$/,
message: "只能是汉字、数字、字母或_-组成",
trigger: "blur",
},
],
});
//定义props
const props = withDefaults(
defineProps<{
propsCreateForm?: AddRegion;
}>(),
{
propsCreateForm: () => ({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
}),
}
);
//监听父级表单值的变化
watch(
() => props.propsCreateForm,
(newVal) => {
createForm.parent_id = newVal.parent_id;
createForm.deep = newVal.deep;
},
{ deep: true, immediate: true } // 因为是对象,需要开启深度监听
);
//初始化表单
const initCreateForm = () => {
createForm.parent_id = 0;
createForm.deep = 1;
};
//emit
const emit = defineEmits(["close-dialog"]);
//提交
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
try {
// 1. 表单校验(async/await 语法更简洁)
const valid = await formEl.validate();
// 2. 调用后端新增接口
if (valid) {
//console.log(createForm)
const result = await RegionAPI.addRegion(createForm);
// 3. 提交成功提示
ElMessage.success(result.msg);
// 4. 重置表单(可选,根据业务需求)
resetForm(formEl);
// 初始化表单
initCreateForm();
// 6. 关闭对话框(可选,根据业务需求)
emit("close-dialog", false);
}
} catch (error) {
// 5. 处理错误(校验失败或接口报错)
const err = error as Error;
ElMessage.error(`提交失败:${err.message || "未知错误"}`);
//console.log('提交错误:', error);
}
};
//重置
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style lang="scss" scoped>
/**表单 */
.content :deep(.el-form-item__label) {
width: 150px;
}
</style>src\pages\text\v1\region\components\UpdateRegionForm.vue
<template>
<div>
<el-form
ref="updateFormRef"
:model="updateForm"
:rules="updateRules"
label-width="220px"
:inline="true"
label-position="left"
status-icon
inline-message
style="
width: 80%;
padding: 30px 20px;
margin-right: auto;
margin-left: auto;
text-align: center;
"
>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="排序" prop="sort">
<el-input v-model="updateForm.sort" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="替换内容" prop="region_name">
<el-input v-model="updateForm.region_name" placeholder="提示内容" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="24">
<el-form-item label="替换内容" prop="region_code">
<el-input v-model="updateForm.region_code" placeholder="提示内容" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row type="flex" justify="center" style="text-align: center">
<el-col :span="6">
<el-form-item>
<el-button type="primary" @click="submitForm(updateFormRef)">修改菜单</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button @click="resetForm(updateFormRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
//系统
import type { FormInstance, FormRules } from "element-plus";
//api和type
import RegionAPI from "@/api/text/v1/region/region-api";
import type {
UpdateRegion,
} from "@/api/text/v1/region/region-type";
//定义组件名称
defineOptions({
name: "UpdateRegionForm",
});
const updateFormRef = ref<FormInstance>();
const updateForm = reactive<UpdateRegion>({
id: 0,
region_name: "",
region_code: "",
sort: 100,
});
const updateRules = reactive<FormRules<UpdateRegion>>({
region_name: [
{ type: "string", required: true, message: "请输入标题显示名称", trigger: "blur" },
{
pattern: /^([\u4e00-\u9fa5]|[a-zA-Z0-9_-])+$/,
message: "只能是汉字、数字、字母或_-组成",
trigger: "blur",
},
],
});
//定义props
const props = withDefaults(
defineProps<{
propsUpdateForm?: UpdateRegion;
}>(),
{
propsUpdateForm: () => ({
id: 0,
region_name: "",
region_code: "",
type: 10,
sort: 100,
}),
}
);
//监听父级表单值的变化
watch(
() => props.propsUpdateForm,
(newVal) => {
//console.log('整个对象变化:', newVal, oldVal)
Object.assign(updateForm, newVal);
},
{ deep: true, immediate: true } // 因为是对象,需要开启深度监听
);
//初始化表单
const initUpdateForm = () => {
updateForm.id = 0;
updateForm.region_name = "";
updateForm.region_code = "";
updateForm.sort = 100;
};
//emit
const emit = defineEmits(["close-dialog"]);
//提交
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
try {
// 1. 表单校验(async/await 语法更简洁)
const valid = await formEl.validate();
// 2. 调用后端新增接口
if (valid) {
//console.log(updateForm)
const result = await RegionAPI.updateRegion(updateForm);
ElMessage.success(result.msg);
resetForm(formEl);
// 初始化表单
initUpdateForm();
emit("close-dialog", false);
}
} catch (error) {
const err = error as Error;
ElMessage.error(`提交失败:${err.message || "未知错误"}`);
}
};
//重置
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
//组件加载完成
onMounted(() => {
Object.assign(updateForm, props.propsUpdateForm);
});
</script>
<style lang="scss" scoped>
/**表单 */
.content :deep(.el-form-item__label) {
width: 150px;
}
</style>src\pages\text\v1\region\components\RegionRowData.vue
<template>
<div v-if="propsShowIndex === 0">
<div class="header">
<el-row type="flex">
<el-button type="primary" icon="FullScreen" size="small" @click="handleClick(0)">
菜单详情
</el-button>
</el-row>
</div>
<div class="content">
<!-- 真正的一行 -->
<el-row class="row-one" :gutter="10">
<el-tag type="info" style="margin-right: 1rem">id</el-tag>
<el-tag style="margin-right: 1rem" type="primary">{{ rowData.id }}</el-tag>
</el-row>
<!-- 真正的一行结束 -->
<!-- 真正的一行 -->
<el-row class="row-one" :gutter="10">
<el-tag type="info" style="margin-right: 1rem">菜单级别</el-tag>
<el-tag style="margin-right: 1rem" type="primary">{{ rowData.deep }}级</el-tag>
</el-row>
<!-- 真正的一行结束 -->
</div>
</div>
</template>
<script lang="ts" setup>
//api和type
import type {
RegionTreeItem,
} from "@/api/text/v1/region/region-type";
//定义组件名称
defineOptions({
name: "RegionRowData",
});
// 定义事件
const emit = defineEmits(["update:ShowIndex"]);
//定义rowData
const rowData = reactive< RegionTreeItem>({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
children: [],
});
//定义props
const props = withDefaults(
defineProps<{
propsRowData?: RegionTreeItem;
propsShowIndex?: number;
}>(),
{
propsRowData: () => ({
id: 0,
parent_id: 0,
deep: 1,
region_name: "",
region_code: "",
sort: 100,
children: [],
}),
propsShowIndex: 0,
}
);
//监听父级表单值的变化
watch(
() => props.propsRowData,
(newVal) => {
//console.log('整个对象变化:', newVal, oldVal)
Object.assign(rowData, newVal);
},
{ deep: true, immediate: true } // 因为是对象,需要开启深度监听
);
//组件加载
onMounted(() => {
Object.assign(rowData, props.propsRowData);
});
const handleClick = (index: number) => {
//console.log(index)
emit("update:ShowIndex", index);
};
</script>
<style lang="scss" scoped>
.row-one {
width: 100rem;
height: auto;
margin-bottom: 1rem;
}
.header {
margin-bottom: 1rem;
}
</style>