| | |
| | | |
| | | import { http } from "@/utils/http"; |
| | | import { baseUrlApi } from "../util"; |
| | | type Result = { |
| | | success: boolean; |
| | | data: Array<any>; |
| | | }; |
| | | import type { Result } from "../types"; |
| | | |
| | | // 获取行政区域列表 |
| | | export const getRegionList = () => { |
| | |
| | | |
| | | // 首页查询非政府订单处理 |
| | | export const shouyeOrder = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/shouyeOrder"), { |
| | | data |
| | | }); |
| | | return http.request<Result>( |
| | | "post", |
| | | baseUrlApi("/api/tenderOrder/shouyeOrder"), |
| | | { |
| | | data |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | // 招标代理分页查询非政府订单处理 |
| | | export const zhaobiaoPageOrder = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/page"), { data }); |
| | | return http.request<Result>("post", baseUrlApi("/api/tenderOrder/page"), { |
| | | data |
| | | }); |
| | | }; |
| | | |
| | | // 采购代理人增加非政府订单处理 |
| | | export const caigourenAdd = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/add"), { data }); |
| | | return http.request<Result>("post", baseUrlApi("/api/tenderOrder/add"), { |
| | | data |
| | | }); |
| | | }; |
| | | |
| | | // 采购代理人更新非政府订单处理 |
| | | export const caigourenUpdate = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/update"), { data }); |
| | | return http.request<Result>("post", baseUrlApi("/api/tenderOrder/update"), { |
| | | data |
| | | }); |
| | | }; |
| | | |
| | | // 采购代理人更新非政府订单质疑 |
| | | export const caigourenUpdateZhiyi = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/updateZhiyi"), { |
| | | data |
| | | }); |
| | | return http.request<Result>( |
| | | "post", |
| | | baseUrlApi("/api/tenderOrder/updateZhiyi"), |
| | | { |
| | | data |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | // 采购代理人更新非政府订单投诉 |
| | | export const caigourenUpdateTousu = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/updateTousu"), { |
| | | data |
| | | }); |
| | | return http.request<Result>( |
| | | "post", |
| | | baseUrlApi("/api/tenderOrder/updateTousu"), |
| | | { |
| | | data |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | // 采购代理人删除非政府订单处理 |
| | | export const caigourenDelete = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/delete"), { data }); |
| | | }; |
| | | |
| | | // 采购代理人批量删除非政府订单处理 |
| | | export const caigourenBatchDelete = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/batchDelete"), { |
| | | return http.request<Result>("post", baseUrlApi("/api/tenderOrder/delete"), { |
| | | data |
| | | }); |
| | | }; |
| | | |
| | | // 采购代理人批量删除非政府订单处理 |
| | | export const caigourenBatchDelete = (data?: object) => { |
| | | return http.request<Result>( |
| | | "post", |
| | | baseUrlApi("/api/tenderOrder/batchDelete"), |
| | | { |
| | | data |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | // 导出非政府订单处理记录 |
| | | export const exportFZF = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/tenderOrder/export"), { data }); |
| | | return http.request<Result>("post", baseUrlApi("/api/tenderOrder/export"), { |
| | | data |
| | | }); |
| | | }; |
| | |
| | | <span class="el-dropdown-link navbar-bg-hover select-none"> |
| | | <!-- <img :src="userAvatar" :style="avatarsStyle" /> --> |
| | | <p class="dark:text-white"> |
| | | {{ username || "请完善资料" }} |
| | | 你好,{{ username || "请完善资料" }} |
| | | <el-tag effect="plain">{{ userRoles.name }}</el-tag> |
| | | </p> |
| | | <el-icon><CaretBottom /></el-icon> |
| | |
| | | export default { |
| | | path: "/item", |
| | | component: () => import("@/views/item/index.vue"), |
| | | component: () => import("@/views/system/dept/index.vue"), |
| | | name: "item", |
| | | // redirect: "/error/403", |
| | | meta: { |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { onMounted, ref, reactive } from "vue"; |
| | | import ReCol from "@/components/ReCol"; |
| | | import { formRules } from "./utils/rule"; |
| | | import { FormProps } from "./utils/types"; |
| | | import { usePublicHooks } from "../hooks"; |
| | | import { useDept } from "./utils/hook"; |
| | | import { Operation } from "@element-plus/icons-vue"; |
| | | import { getCaigoufangshiList } from "@/api/item/index"; |
| | | |
| | | const { state } = useDept(); |
| | | const props = withDefaults(defineProps<FormProps>(), { |
| | | formInline: () => ({ |
| | | projectCode: "", // 项目编号(必填) |
| | | projectName: "", // 项目名称(必填) |
| | | hangyepinmu: null, // 行业品目(可选) |
| | | caigoufangshi: null, // 采购方式(可选) |
| | | caigouyusuan: null, // 采购预算(可选) |
| | | dingbiaoguize: null, // 定标规则(可选) |
| | | baomingfei: null, // 报名费(可选) |
| | | toubiaobaozhengjin: null, // 投标保证金(可选) |
| | | lianhetitoubiao: null, // 联合体投标(可选) |
| | | kaibiaofangshi: null, // 开标方式(可选) |
| | | shifoufenbao: "false", // 是否分包(可选) |
| | | shifoutuisongxuanchuan: "true", // 是否推送宣传(可选) |
| | | caigourenmingcheng: null, // 采购人名称(可选) |
| | | xingzhengquyu: null, // 行政区域(可选) |
| | | xingzhengquyuName: null, // 行政区域名称(可选) |
| | | jigoudaima: null, // 机构代码(可选) |
| | | daimaleixing: null, // 代码类型(可选) |
| | | lianxiren: null, // 联系人(可选) |
| | | lianxidianhua: null, // 联系电话(可选) |
| | | tongxindizhi: null, // 通信地址(可选) |
| | | dianziyoujian: null, // 电子邮件(可选) |
| | | xiangmujingbanren: null, // 项目经办人(可选) |
| | | zhiwu: null, // 职务(可选) |
| | | jingbanrendianhua: null, // 经办人电话(可选) |
| | | dailijigoumingcheng: null, // 代理机构名称(可选) |
| | | dailiLianxiren: null, // 代理机构联系人(可选) |
| | | dailiLianxidianhua: null, // 代理机构联系电话(可选) |
| | | dailiDianziyoujian: null, // 代理机构电子邮件(可选) |
| | | dailiTongxindizhi: null, // 代理机构通信地址(可选) |
| | | dailiXiangmujingli: null, // 代理机构项目经理(可选 |
| | | dailijingliLianxidianhua: null // 代理机构项目经理联系电话(可选) |
| | | }) |
| | | }); |
| | | |
| | | const ruleFormRef = ref(); |
| | | const { switchStyle } = usePublicHooks(); |
| | | const newFormInline = ref(props.formInline); |
| | | |
| | | function getRef() { |
| | | return ruleFormRef.value; |
| | | } |
| | | |
| | | defineExpose({ getRef }); |
| | | onMounted(async () => {}); |
| | | </script> |
| | | |
| | | <template> |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | :model="newFormInline" |
| | | :rules="formRules" |
| | | label-width="110px" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <re-col> |
| | | <p class="flex items-center"> |
| | | <el-icon color="#409EFF" class="m-2" size="large"> |
| | | <Operation /> |
| | | </el-icon> |
| | | <el-text class="mx-1" size="large" type="primary" tag="b"> |
| | | 项目信息 |
| | | </el-text> |
| | | </p> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="项目名称" prop="projectName"> |
| | | <el-input |
| | | v-model="newFormInline.projectName" |
| | | clearable |
| | | placeholder="请输入项目名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="项目编号" prop="projectCode"> |
| | | <el-input |
| | | v-model="newFormInline.projectCode" |
| | | clearable |
| | | placeholder="请输入项目编号" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="行业品目" prop="hangyepinmu"> |
| | | <el-select |
| | | v-model="newFormInline.hangyepinmu" |
| | | placeholder="请选择行业品目" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-option |
| | | v-for="item in state.hangyepingmuList" |
| | | :key="item.id" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="采购方式"> |
| | | <el-select |
| | | v-model="newFormInline.caigoufangshi" |
| | | placeholder="请选择采购方式" |
| | | style="width: 240px" |
| | | > |
| | | <el-option |
| | | v-for="item in state.caigoufangshiList" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="采购预算" prop="caigouyusuan"> |
| | | <el-input |
| | | v-model="newFormInline.caigouyusuan" |
| | | clearable |
| | | placeholder="请输入采购预算" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="定制规划" prop="dingbiaoguize"> |
| | | <el-checkbox-group |
| | | v-model="newFormInline.dingbiaoguize" |
| | | placeholder="请选择状态" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-checkbox label="最低价" :value="1" /> |
| | | <el-checkbox label="综合评分" :value="0" /> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="报名费" prop="baomingfei"> |
| | | <el-input |
| | | v-model="newFormInline.baomingfei" |
| | | clearable |
| | | placeholder="请输入报名费" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="投标保证金" prop="toubiaobaozhengjin"> |
| | | <el-input |
| | | v-model="newFormInline.toubiaobaozhengjin" |
| | | clearable |
| | | placeholder="请输入投标保证金" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联合体投标" prop="lianhetitoubiao"> |
| | | <el-radio-group v-model="newFormInline.lianhetitoubiao"> |
| | | <el-radio value="支持">支持</el-radio> |
| | | <el-radio value="不支持">不支持</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="开标方式" prop="kaibiaofangshi"> |
| | | <el-radio-group v-model="newFormInline.kaibiaofangshi"> |
| | | <el-radio value="纸质标">纸质标</el-radio> |
| | | <el-radio value="电子标">电子标</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="是否分包" prop="shifoufenbao"> |
| | | <el-radio-group v-model="newFormInline.shifoufenbao"> |
| | | <el-radio value="true">是</el-radio> |
| | | <el-radio value="false">否</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="是否推送宣传" prop="shifoutuisongxuanchuan"> |
| | | <el-radio-group v-model="newFormInline.shifoutuisongxuanchuan"> |
| | | <el-radio value="true">是</el-radio> |
| | | <el-radio value="false">否</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col> |
| | | <p class="flex items-center"> |
| | | <el-icon color="#409EFF" class="m-2" size="large"> |
| | | <Operation /> |
| | | </el-icon> |
| | | <el-text class="mx-1" size="large" type="primary" tag="b"> |
| | | 采购人信息 |
| | | </el-text> |
| | | </p> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="采购人名称" prop="caigourenmingcheng"> |
| | | <el-input |
| | | v-model="newFormInline.caigourenmingcheng" |
| | | clearable |
| | | placeholder="请输入采购人名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="行政区域" prop="xingzhengquyu"> |
| | | <el-cascader |
| | | v-model="newFormInline.xingzhengquyu" |
| | | class="w-full" |
| | | :options="state.regionList" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'name', |
| | | emitPath: false, |
| | | children: 'regions' |
| | | }" |
| | | clearable |
| | | filterable |
| | | placeholder="请选择区域" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="机构代码" prop="jigoudaima"> |
| | | <el-input |
| | | v-model="newFormInline.jigoudaima" |
| | | clearable |
| | | placeholder="请输入机构代码" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="代码类型" prop="daimaleixing"> |
| | | <el-select |
| | | v-model="newFormInline.daimaleixing" |
| | | placeholder="请选择代码类型" |
| | | style="width: 240px" |
| | | > |
| | | <el-option |
| | | v-for="item in state.daimaleixingList" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联系人" prop="lianxiren"> |
| | | <el-input |
| | | v-model="newFormInline.lianxiren" |
| | | clearable |
| | | placeholder="请输入联系人" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联系人电话" prop="lianxidianhua"> |
| | | <el-input |
| | | v-model="newFormInline.lianxidianhua" |
| | | clearable |
| | | placeholder="请输入联系人电话" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="通信地址" prop="tongxindizhi"> |
| | | <el-input |
| | | v-model="newFormInline.tongxindizhi" |
| | | clearable |
| | | placeholder="请输入通信地址" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="电子邮箱" prop="dianziyoujian"> |
| | | <el-input |
| | | v-model="newFormInline.dianziyoujian" |
| | | clearable |
| | | placeholder="请输入电子邮箱" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="项目经办人" prop="xiangmujingbanren"> |
| | | <el-input |
| | | v-model="newFormInline.xiangmujingbanren" |
| | | clearable |
| | | placeholder="请输入项目经办人" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="职务" prop="zhiwu"> |
| | | <el-input |
| | | v-model="newFormInline.zhiwu" |
| | | clearable |
| | | placeholder="请输入职务" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="经办人电话" prop="jingbanrendianhua"> |
| | | <el-input |
| | | v-model="newFormInline.jingbanrendianhua" |
| | | clearable |
| | | placeholder="请输入经办人电话" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col> |
| | | <p class="flex items-center"> |
| | | <el-icon color="#409EFF" class="m-2" size="large"> |
| | | <Operation /> |
| | | </el-icon> |
| | | <el-text class="mx-1" size="large" type="primary" tag="b"> |
| | | 代理机构信息 |
| | | </el-text> |
| | | </p> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="代理机构名称" prop="dailijigoumingcheng"> |
| | | <el-input |
| | | v-model="newFormInline.dailijigoumingcheng" |
| | | clearable |
| | | placeholder="请输入代理机构名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联系人人" prop="dailiLianxiren"> |
| | | <el-input |
| | | v-model="newFormInline.dailiLianxiren" |
| | | clearable |
| | | placeholder="请输入联系人" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联系电话" prop="dailiLianxidianhua"> |
| | | <el-input |
| | | v-model="newFormInline.dailiLianxidianhua" |
| | | clearable |
| | | placeholder="请输入联系电话" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="电子邮箱" prop="dailiDianziyoujian"> |
| | | <el-input |
| | | v-model="newFormInline.dailiDianziyoujian" |
| | | clearable |
| | | placeholder="请输入电子邮箱" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="通信地址" prop="dailiTongxindizhi"> |
| | | <el-input |
| | | v-model="newFormInline.dailiTongxindizhi" |
| | | clearable |
| | | placeholder="请输入通信地址" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="项目经理" prop="dailiXiangmujingli"> |
| | | <el-input |
| | | v-model="newFormInline.dailiXiangmujingli" |
| | | clearable |
| | | placeholder="请输入项目经理" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="6" :xs="24" :sm="24"> |
| | | <el-form-item label="联系电话" prop="dailijingliLianxidianhua"> |
| | | <el-input |
| | | v-model="newFormInline.dailijingliLianxidianhua" |
| | | clearable |
| | | placeholder="请输入联系电话" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | </el-row> |
| | | </el-form> |
| | | </template> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { useDept } from "./utils/hook"; |
| | | import { PureTableBar } from "@/components/RePureTableBar"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | |
| | | import Delete from "~icons/ep/delete"; |
| | | import EditPen from "~icons/ep/edit-pen"; |
| | | import Refresh from "~icons/ep/refresh"; |
| | | import AddFill from "~icons/ri/add-circle-line"; |
| | | |
| | | defineOptions({ |
| | | name: "SystemDept" |
| | | }); |
| | | |
| | | const formRef = ref(); |
| | | const tableRef = ref(); |
| | | const { |
| | | form, |
| | | state, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | onSearch, |
| | | resetForm, |
| | | openDialog, |
| | | handleDelete, |
| | | handleSelectionChange |
| | | } = useDept(); |
| | | |
| | | function onFullscreen() { |
| | | // 重置表格高度 |
| | | tableRef.value.setAdaptive(); |
| | | } |
| | | |
| | | onMounted(() => {}); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="main"> |
| | | <!-- class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" --> |
| | | <el-card shadow="hover" :body-style="{ paddingBottom: '0' }"> |
| | | <el-form ref="formRef" :model="form" labelWidth="100"> |
| | | <el-row> |
| | | <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6"> |
| | | <el-form-item label="时间:"> |
| | | <el-date-picker |
| | | v-model="form.createDateRange" |
| | | type="daterange" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
| | | class="w-[100%]!" |
| | | /> |
| | | <!-- start-placeholder="开始日期" |
| | | end-placeholder="结束日期" --> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6"> |
| | | <el-form-item label="区域:"> |
| | | <el-cascader |
| | | v-model="form.xingzhengquyu" |
| | | class="w-full" |
| | | :options="state.regionList" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'name', |
| | | emitPath: false, |
| | | children: 'regions' |
| | | }" |
| | | clearable |
| | | filterable |
| | | placeholder="请选择区域" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="行业品目:"> |
| | | <el-select |
| | | v-model="form.hangyepinmu" |
| | | placeholder="请选择行业品目" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-option |
| | | v-for="item in state.hangyepingmuList" |
| | | :key="item.id" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="项目进度:"> |
| | | <el-select |
| | | v-model="form.orderStatus" |
| | | placeholder="请选择项目进度" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-option |
| | | v-for="item in state.orderStatusList" |
| | | :key="item.id" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> </el-col |
| | | ><el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="质疑:"> |
| | | <el-checkbox-group |
| | | v-model="form.zhiyi" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-checkbox label="有" :value="1" /> |
| | | <el-checkbox label="无" :value="0" /> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="投诉:"> |
| | | <el-checkbox-group |
| | | v-model="form.tousu" |
| | | clearable |
| | | class="w-[100%]!" |
| | | > |
| | | <el-checkbox label="有" :value="1" /> |
| | | <el-checkbox label="无" :value="0" /> |
| | | </el-checkbox-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="项目名称:"> |
| | | <el-input |
| | | v-model="form.projectName" |
| | | placeholder="请输入项目名称" |
| | | clearable |
| | | class="w-[100%]!" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="代理机构:"> |
| | | <el-input |
| | | v-model="form.dailijigoumingcheng" |
| | | placeholder="请输入代理机构" |
| | | clearable |
| | | class="w-[100%]!" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="中标供应商:"> |
| | | <el-input |
| | | v-model="form.zhongbiaoName" |
| | | placeholder="请输入中标供应商" |
| | | clearable |
| | | class="w-[100%]!" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label="评审专家:"> |
| | | <el-input |
| | | v-model="form.zhuanjiaName" |
| | | placeholder="请输入评审专家" |
| | | clearable |
| | | class="w-[100%]!" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> |
| | | <el-form-item label-width="40"> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon('ri/search-line')" |
| | | :loading="loading" |
| | | @click="onSearch" |
| | | > |
| | | 搜索 |
| | | </el-button> |
| | | <!-- <el-button |
| | | :icon="useRenderIcon(Refresh)" |
| | | @click="resetForm(formRef)" |
| | | > |
| | | 重置 |
| | | </el-button> --> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog()" |
| | | > |
| | | 新增 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </el-card> |
| | | <PureTableBar |
| | | title="" |
| | | :columns="columns" |
| | | :tableRef="tableRef?.getTableRef()" |
| | | @refresh="onSearch" |
| | | @fullscreen="onFullscreen" |
| | | > |
| | | <!-- <template #buttons> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog()" |
| | | > |
| | | 新增项目 |
| | | </el-button> |
| | | </template> --> |
| | | <template v-slot="{ size, dynamicColumns }"> |
| | | <pure-table |
| | | ref="tableRef" |
| | | adaptive |
| | | :adaptiveConfig="{ offsetBottom: 45 }" |
| | | align-whole="center" |
| | | row-key="id" |
| | | showOverflowTooltip |
| | | table-layout="auto" |
| | | default-expand-all |
| | | :loading="loading" |
| | | :data="dataList" |
| | | :columns="dynamicColumns" |
| | | :header-cell-style="{ |
| | | background: 'var(--el-fill-color-light)', |
| | | color: 'var(--el-text-color-primary)' |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(EditPen)" |
| | | @click="openDialog('修改', row)" |
| | | > |
| | | 修改 |
| | | </el-button> |
| | | <!-- <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog('新增', { parentId: row.id } as any)" |
| | | > |
| | | 新增 |
| | | </el-button> --> |
| | | <el-popconfirm |
| | | :title="`是否确认删除部门名称为${row.projectName}的这条数据`" |
| | | @confirm="handleDelete(row)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Delete)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-popconfirm> |
| | | </template> |
| | | </pure-table> |
| | | </template> |
| | | </PureTableBar> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | :deep(.el-table__inner-wrapper::before) { |
| | | height: 0; |
| | | } |
| | | |
| | | .main-content { |
| | | margin: 24px 24px 0 !important; |
| | | } |
| | | |
| | | .search-form { |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 12px; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import dayjs from "dayjs"; |
| | | import editForm from "../form.vue"; |
| | | import { handleTree } from "@/utils/tree"; |
| | | import { message } from "@/utils/message"; |
| | | import { zhaobiaoPageOrder } from "@/api/item/index"; |
| | | |
| | | import { |
| | | getCaigoufangshiList, |
| | | getHangyepingmuList, |
| | | getOrderStatusList, |
| | | getRegionList, |
| | | getDaimaleixingList, |
| | | caigourenAdd, |
| | | caigourenDelete |
| | | } from "@/api/item/index"; |
| | | import { usePublicHooks } from "../../hooks"; |
| | | import { addDialog } from "@/components/ReDialog"; |
| | | import { reactive, ref, onMounted, h } from "vue"; |
| | | import type { FormItemProps } from "../utils/types"; |
| | | import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils"; |
| | | |
| | | export function useDept() { |
| | | const form = reactive({ |
| | | // 新增日期范围,可为 null,类型为数组 |
| | | createDateRange: null, |
| | | // 行政区域,可为 null,类型为字符串 |
| | | xingzhengquyu: "", |
| | | // 行业品目,可为 null,类型为字符串 |
| | | hangyepinmu: "", |
| | | // 订单状态,可为 null,类型为 32 位整数 |
| | | orderStatus: "", |
| | | // 质疑有无,可为 null,类型为布尔值 |
| | | zhiyi: null, |
| | | // 投诉有无,可为 null,类型为布尔值 |
| | | tousu: null, |
| | | // 项目名称,可为 null,类型为字符串 |
| | | projectName: null, |
| | | // 代理机构名称,可为 null,类型为字符串 |
| | | dailijigoumingcheng: null, |
| | | // 中标供应商姓名,可为 null,类型为字符串 |
| | | zhongbiaoName: null, |
| | | // 专家姓名,可为 null,类型为字符串 |
| | | zhuanjiaName: null |
| | | }); |
| | | const state = reactive({ |
| | | caigoufangshiList: [], |
| | | hangyepingmuList: [], |
| | | orderStatusList: [], |
| | | regionList: [], |
| | | daimaleixingList: [] |
| | | }); |
| | | //获取采购方式 |
| | | const getCaigoufangshiListFun = async () => { |
| | | const res = await getCaigoufangshiList(); |
| | | state.caigoufangshiList = res.result; |
| | | }; |
| | | |
| | | //获取行业品目 |
| | | const getHangyepingmuListFun = async () => { |
| | | const res = await getHangyepingmuList(); |
| | | state.hangyepingmuList = res.result; |
| | | }; |
| | | |
| | | //获取项目进度 |
| | | const getOrderStatusListFun = async () => { |
| | | const res = await getOrderStatusList(); |
| | | state.orderStatusList = res.result; |
| | | }; |
| | | |
| | | // 获取区域 |
| | | const getRegionListFun = async () => { |
| | | const res = await getRegionList(); |
| | | state.regionList = res.result; |
| | | }; |
| | | |
| | | // 获取代码类型 |
| | | const getDaimaleixingListFun = async () => { |
| | | const res = await getDaimaleixingList(); |
| | | state.daimaleixingList = res.result; |
| | | }; |
| | | const formRef = ref(); |
| | | const dataList = ref([]); |
| | | const loading = ref(true); |
| | | const { tagStyle } = usePublicHooks(); |
| | | const getOrderStatus = row => { |
| | | const res = state.orderStatusList.find(item => { |
| | | return row.orderStatus == item.status; |
| | | }); |
| | | return res.label; |
| | | }; |
| | | const columns: TableColumnList = [ |
| | | { |
| | | label: "项目名称", |
| | | prop: "projectName", |
| | | width: 180, |
| | | align: "left" |
| | | }, |
| | | { |
| | | label: "代理机构", |
| | | prop: "dailijigoumingcheng", |
| | | minWidth: 70 |
| | | }, |
| | | { |
| | | label: "项目进度", |
| | | prop: "orderStatus", |
| | | minWidth: 70, |
| | | cellRenderer: ({ row, props }) => getOrderStatus(row) |
| | | }, |
| | | { |
| | | label: "报名费", |
| | | prop: "baomingfei", |
| | | minWidth: 70 |
| | | }, |
| | | { |
| | | label: "投标保证金", |
| | | prop: "toubiaobaozhengjin", |
| | | minWidth: 70 |
| | | }, |
| | | { |
| | | label: "中标供应商", |
| | | prop: "zhongbiaoName", |
| | | minWidth: 70 |
| | | }, |
| | | { |
| | | label: "评审专家", |
| | | prop: "zhuanjiaName", |
| | | minWidth: 70 |
| | | }, |
| | | { |
| | | label: "质疑", |
| | | prop: "zhiyi", |
| | | minWidth: 100, |
| | | cellRenderer: ({ row, props }) => ( |
| | | <span>{row.status === 1 ? "有" : "无"}</span> |
| | | ) |
| | | }, |
| | | { |
| | | label: "投诉", |
| | | prop: "tousu", |
| | | minWidth: 100, |
| | | cellRenderer: ({ row, props }) => ( |
| | | // <el-tag size={props.size} style={tagStyle.value(row.status)}> |
| | | <span>{row.status === 1 ? "有" : "无"}</span> |
| | | // </el-tag> |
| | | ) |
| | | }, |
| | | { |
| | | label: "操作", |
| | | fixed: "right", |
| | | width: 210, |
| | | slot: "operation" |
| | | } |
| | | ]; |
| | | |
| | | function handleSelectionChange(val) { |
| | | console.log("handleSelectionChange", val); |
| | | } |
| | | |
| | | function resetForm(formEl) { |
| | | if (!formEl) return; |
| | | formEl.resetFields(); |
| | | onSearch(); |
| | | } |
| | | |
| | | async function onSearch() { |
| | | loading.value = true; |
| | | const { result } = await zhaobiaoPageOrder(form); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id |
| | | const newData = result.items; |
| | | loading.value = false; |
| | | // if (!isAllEmpty(form.name)) { |
| | | // // 前端搜索部门名称 |
| | | // newData = newData.filter(item => item.name.includes(form.name)); |
| | | // } |
| | | // if (!isAllEmpty(form.status)) { |
| | | // // 前端搜索状态 |
| | | // newData = newData.filter(item => item.status === form.status); |
| | | // } |
| | | dataList.value = handleTree(newData); // 处理成树结构 |
| | | } |
| | | |
| | | function formatHigherDeptOptions(treeList) { |
| | | // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理) |
| | | if (!treeList || !treeList.length) return; |
| | | const newTreeList = []; |
| | | for (let i = 0; i < treeList.length; i++) { |
| | | treeList[i].disabled = treeList[i].status === 0 ? true : false; |
| | | formatHigherDeptOptions(treeList[i].children); |
| | | newTreeList.push(treeList[i]); |
| | | } |
| | | return newTreeList; |
| | | } |
| | | |
| | | function openDialog(title = "新增", row?: FormItemProps) { |
| | | addDialog({ |
| | | title: `${title}项目`, |
| | | props: { |
| | | formInline: { |
| | | higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)), |
| | | projectCode: row?.projectCode ?? "", // 项目编号(必填) |
| | | projectName: row?.projectName ?? "", // 项目名称(必填) |
| | | hangyepinmu: row?.hangyepinmu ?? null, // 行业品目(可选) |
| | | caigoufangshi: row?.caigoufangshi ?? null, // 采购方式(可选) |
| | | caigouyusuan: row?.caigouyusuan ?? null, // 采购预算(可选) |
| | | dingbiaoguize: row?.dingbiaoguize ?? null, // 定标规则(可选) |
| | | baomingfei: row?.baomingfei ?? null, // 报名费(可选) |
| | | toubiaobaozhengjin: row?.toubiaobaozhengjin ?? null, // 投标保证金(可选) |
| | | lianhetitoubiao: row?.lianhetitoubiao ?? null, // 联合体投标(可选) |
| | | kaibiaofangshi: row?.kaibiaofangshi ?? null, // 开标方式(可选) |
| | | shifoufenbao: row?.shifoufenbao ?? false, // 是否分包(可选) |
| | | shifoutuisongxuanchuan: row?.shifoutuisongxuanchuan ?? true, // 是否推送宣传(可选) |
| | | caigourenmingcheng: row?.caigourenmingcheng ?? null, // 采购人名称(可选) |
| | | xingzhengquyu: row?.xingzhengquyu ?? null, // 行政区域(可选) |
| | | xingzhengquyuName: row?.xingzhengquyuName ?? null, // 行政区域名称(可选) |
| | | jigoudaima: row?.jigoudaima ?? null, // 机构代码(可选) |
| | | daimaleixing: row?.daimaleixing ?? null, // 代码类型(可选) |
| | | lianxiren: row?.lianxiren ?? null, // 联系人(可选) |
| | | lianxidianhua: row?.lianxidianhua ?? null, // 联系电话(可选) |
| | | tongxindizhi: row?.tongxindizhi ?? null, // 通信地址(可选) |
| | | dianziyoujian: row?.dianziyoujian ?? null, // 电子邮件(可选) |
| | | xiangmujingbanren: row?.xiangmujingbanren ?? null, // 项目经办人(可选) |
| | | zhiwu: row?.zhiwu ?? null, // 职务(可选) |
| | | jingbanrendianhua: row?.jingbanrendianhua ?? null, // 经办人电话(可选) |
| | | dailijigoumingcheng: row?.dailijigoumingcheng ?? null, // 代理机构名称(可选) |
| | | dailiLianxiren: row?.dailiLianxiren ?? null, // 代理机构联系人(可选) |
| | | dailiLianxidianhua: row?.dailiLianxidianhua ?? null, // 代理机构联系电话(可选) |
| | | dailiDianziyoujian: row?.dailiDianziyoujian ?? null, // 代理机构电子邮件(可选) |
| | | dailiTongxindizhi: row?.dailiTongxindizhi ?? null, // 代理机构通信地址(可选) |
| | | dailiXiangmujingli: row?.dailiXiangmujingli ?? null, // 代理机构项目经理(可选 |
| | | dailijingliLianxidianhua: row?.dailijingliLianxidianhua ?? null // 代理机构项目经理联系电话(可选) |
| | | } |
| | | }, |
| | | width: "80%", |
| | | draggable: true, |
| | | fullscreen: deviceDetection(), |
| | | fullscreenIcon: true, |
| | | closeOnClickModal: false, |
| | | contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), |
| | | beforeSure: (done, { options }) => { |
| | | const FormRef = formRef.value.getRef(); |
| | | const curData = options.props.formInline as FormItemProps; |
| | | async function chores() { |
| | | message(`您${title}了项目名称为${curData.projectName}的这条数据`, { |
| | | type: "success" |
| | | }); |
| | | const res = await caigourenAdd(curData); |
| | | if (res.code == "200") { |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | } else { |
| | | message(res.message, { |
| | | type: "error" |
| | | }); |
| | | } |
| | | } |
| | | FormRef.validate(valid => { |
| | | if (valid) { |
| | | console.log("curData", curData); |
| | | // 表单规则校验通过 |
| | | if (title === "新增") { |
| | | // 实际开发先调用新增接口,再进行下面操作 |
| | | chores(); |
| | | } else { |
| | | // 实际开发先调用修改接口,再进行下面操作 |
| | | chores(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | async function handleDelete(row) { |
| | | const res = await caigourenDelete({ id: row.id }); |
| | | if (res.code == "200") { |
| | | message(`您删除了项目名称为${row.projectName}的这条数据`, { |
| | | type: "success" |
| | | }); |
| | | onSearch(); |
| | | } else { |
| | | message(res.message, { |
| | | type: "error" |
| | | }); |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | onSearch(); |
| | | getCaigoufangshiListFun(); |
| | | getHangyepingmuListFun(); |
| | | getOrderStatusListFun(); |
| | | getRegionListFun(); |
| | | getDaimaleixingListFun(); |
| | | }); |
| | | |
| | | return { |
| | | form, |
| | | state, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | /** 搜索 */ |
| | | onSearch, |
| | | /** 重置 */ |
| | | resetForm, |
| | | /** 新增、修改部门 */ |
| | | openDialog, |
| | | /** 删除部门 */ |
| | | handleDelete, |
| | | handleSelectionChange |
| | | }; |
| | | } |
New file |
| | |
| | | import { reactive } from "vue"; |
| | | import type { FormRules } from "element-plus"; |
| | | import { isPhone, isEmail } from "@pureadmin/utils"; |
| | | |
| | | /** 自定义表单规则校验 */ |
| | | export const formRules = reactive(<FormRules>{ |
| | | name: [{ required: true, message: "部门名称为必填项", trigger: "blur" }], |
| | | phone: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === "") { |
| | | callback(); |
| | | } else if (!isPhone(value)) { |
| | | callback(new Error("请输入正确的手机号码格式")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | // trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可 |
| | | } |
| | | ], |
| | | email: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === "") { |
| | | callback(); |
| | | } else if (!isEmail(value)) { |
| | | callback(new Error("请输入正确的邮箱格式")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | } |
| | | ] |
| | | }); |
New file |
| | |
| | | interface FormItemProps { |
| | | projectCode: string; // 项目编号(必填) |
| | | projectName: string; // 项目名称(必填) |
| | | hangyepinmu: any | null; // 行业品目(可选) |
| | | caigoufangshi: any | null; // 采购方式(可选) |
| | | caigouyusuan: any | null; // 采购预算(可选) |
| | | dingbiaoguize: any | null; // 定标规则(可选) |
| | | baomingfei: any | null; // 报名费(可选) |
| | | toubiaobaozhengjin: any | null; // 投标保证金(可选) |
| | | lianhetitoubiao: any | null; // 联合体投标(可选) |
| | | kaibiaofangshi: any | null; // 开标方式(可选) |
| | | shifoufenbao: any | null; // 是否分包(可选) |
| | | shifoutuisongxuanchuan: any | null; // 是否推送宣传(可选) |
| | | caigourenmingcheng: string | null; // 采购人名称(可选) |
| | | xingzhengquyu: any | null; // 行政区域(可选) |
| | | xingzhengquyuName: string | null; // 行政区域名称(可选) |
| | | jigoudaima: string | null; // 机构代码(可选) |
| | | daimaleixing: any | null; // 代码类型(可选) |
| | | lianxiren: string | null; // 联系人(可选) |
| | | lianxidianhua: string | null; // 联系电话(可选) |
| | | tongxindizhi: string | null; // 通信地址(可选) |
| | | dianziyoujian: string | null; // 电子邮件(可选) |
| | | xiangmujingbanren: string | null; // 项目经办人(可选) |
| | | zhiwu: string | null; // 职务(可选) |
| | | jingbanrendianhua: string | null; // 经办人电话(可选) |
| | | dailijigoumingcheng: string | null; // 代理机构名称(可选) |
| | | dailiLianxiren: string | null; // 代理机构联系人(可选) |
| | | dailiLianxidianhua: string | null; // 代理机构联系电话(可选) |
| | | dailiDianziyoujian: string | null; // 代理机构电子邮件(可选) |
| | | dailiTongxindizhi: string | null; // 代理机构通信地址(可选) |
| | | dailiXiangmujingli: string | null; // 代理机构项目经理(可选) |
| | | dailijingliLianxidianhua: string | null; // 代理机构项目经理联系电话(可选) |
| | | } |
| | | interface FormProps { |
| | | formInline: FormItemProps; |
| | | } |
| | | |
| | | export type { FormItemProps, FormProps }; |
New file |
| | |
| | | // 抽离可公用的工具函数等用于系统管理页面逻辑 |
| | | import { computed } from "vue"; |
| | | import { useDark } from "@pureadmin/utils"; |
| | | |
| | | export function usePublicHooks() { |
| | | const { isDark } = useDark(); |
| | | |
| | | const switchStyle = computed(() => { |
| | | return { |
| | | "--el-switch-on-color": "#6abe39", |
| | | "--el-switch-off-color": "#e84749" |
| | | }; |
| | | }); |
| | | |
| | | const tagStyle = computed(() => { |
| | | return (status: number) => { |
| | | return status === 1 |
| | | ? { |
| | | "--el-tag-text-color": isDark.value ? "#6abe39" : "#389e0d", |
| | | "--el-tag-bg-color": isDark.value ? "#172412" : "#f6ffed", |
| | | "--el-tag-border-color": isDark.value ? "#274a17" : "#b7eb8f" |
| | | } |
| | | : { |
| | | "--el-tag-text-color": isDark.value ? "#e84749" : "#cf1322", |
| | | "--el-tag-bg-color": isDark.value ? "#2b1316" : "#fff1f0", |
| | | "--el-tag-border-color": isDark.value ? "#58191c" : "#ffa39e" |
| | | }; |
| | | }; |
| | | }); |
| | | |
| | | return { |
| | | /** 当前网页是否为`dark`模式 */ |
| | | isDark, |
| | | /** 表现更鲜明的`el-switch`组件 */ |
| | | switchStyle, |
| | | /** 表现更鲜明的`el-tag`组件 */ |
| | | tagStyle |
| | | }; |
| | | } |
New file |
| | |
| | | ## 字段含义 |
| | | |
| | | | 字段 | 说明 | |
| | | | :---------------- | :----------------------------------------------------------- | |
| | | | `menuType` | 菜单类型(`0`代表菜单、`1`代表`iframe`、`2`代表外链、`3`代表按钮) | |
| | | | `parentId` | | |
| | | | `title` | 菜单名称(兼容国际化、非国际化,如果用国际化的写法就必须在根目录的`locales`文件夹下对应添加) | |
| | | | `name` | 路由名称(必须唯一并且和当前路由`component`字段对应的页面里用`defineOptions`包起来的`name`保持一致) | |
| | | | `path` | 路由路径 | |
| | | | `component` | 组件路径(传`component`组件路径,那么`path`可以随便写,如果不传,`component`组件路径会跟`path`保持一致) | |
| | | | `rank` | 菜单排序(平台规定只有`home`路由的`rank`才能为`0`,所以后端在返回`rank`的时候需要从非`0`开始 [点击查看更多](https://pure-admin.cn/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)) | |
| | | | `redirect` | 路由重定向 | |
| | | | `icon` | 菜单图标 | |
| | | | `extraIcon` | 右侧图标 | |
| | | | `enterTransition` | 进场动画(页面加载动画) | |
| | | | `leaveTransition` | 离场动画(页面加载动画) | |
| | | | `activePath` | 菜单激活(将某个菜单激活,主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`) | |
| | | | `auths` | 权限标识(按钮级别权限设置) | |
| | | | `frameSrc` | 链接地址(需要内嵌的`iframe`链接地址) | |
| | | | `frameLoading` | 加载动画(内嵌的`iframe`页面是否开启首次加载动画) | |
| | | | `keepAlive` | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) | |
| | | | `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) | |
| | | | `fixedTag` | 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) | |
| | | | `showLink` | 菜单(是否显示该菜单) | |
| | | | `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://pure-admin.cn/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) | |
| | | |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import ReCol from "@/components/ReCol"; |
| | | import { formRules } from "./utils/rule"; |
| | | import { FormProps } from "./utils/types"; |
| | | import { transformI18n } from "@/plugins/i18n"; |
| | | import { IconSelect } from "@/components/ReIcon"; |
| | | import Segmented from "@/components/ReSegmented"; |
| | | import ReAnimateSelector from "@/components/ReAnimateSelector"; |
| | | import { |
| | | menuTypeOptions, |
| | | showLinkOptions, |
| | | fixedTagOptions, |
| | | keepAliveOptions, |
| | | hiddenTagOptions, |
| | | showParentOptions, |
| | | frameLoadingOptions |
| | | } from "./utils/enums"; |
| | | |
| | | const props = withDefaults(defineProps<FormProps>(), { |
| | | formInline: () => ({ |
| | | menuType: 0, |
| | | higherMenuOptions: [], |
| | | parentId: 0, |
| | | title: "", |
| | | name: "", |
| | | path: "", |
| | | component: "", |
| | | rank: 99, |
| | | redirect: "", |
| | | icon: "", |
| | | extraIcon: "", |
| | | enterTransition: "", |
| | | leaveTransition: "", |
| | | activePath: "", |
| | | auths: "", |
| | | frameSrc: "", |
| | | frameLoading: true, |
| | | keepAlive: false, |
| | | hiddenTag: false, |
| | | fixedTag: false, |
| | | showLink: true, |
| | | showParent: false |
| | | }) |
| | | }); |
| | | |
| | | const ruleFormRef = ref(); |
| | | const newFormInline = ref(props.formInline); |
| | | |
| | | function getRef() { |
| | | return ruleFormRef.value; |
| | | } |
| | | |
| | | defineExpose({ getRef }); |
| | | </script> |
| | | |
| | | <template> |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | :model="newFormInline" |
| | | :rules="formRules" |
| | | label-width="82px" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <re-col> |
| | | <el-form-item label="菜单类型"> |
| | | <Segmented |
| | | v-model="newFormInline.menuType" |
| | | :options="menuTypeOptions" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col> |
| | | <el-form-item label="上级菜单"> |
| | | <el-cascader |
| | | v-model="newFormInline.parentId" |
| | | class="w-full" |
| | | :options="newFormInline.higherMenuOptions" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'title', |
| | | emitPath: false, |
| | | checkStrictly: true |
| | | }" |
| | | clearable |
| | | filterable |
| | | placeholder="请选择上级菜单" |
| | | > |
| | | <template #default="{ node, data }"> |
| | | <span>{{ transformI18n(data.title) }}</span> |
| | | <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span> |
| | | </template> |
| | | </el-cascader> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="菜单名称" prop="title"> |
| | | <el-input |
| | | v-model="newFormInline.title" |
| | | clearable |
| | | placeholder="请输入菜单名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="路由名称" prop="name"> |
| | | <el-input |
| | | v-model="newFormInline.name" |
| | | clearable |
| | | placeholder="请输入路由名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="路由路径" prop="path"> |
| | | <el-input |
| | | v-model="newFormInline.path" |
| | | clearable |
| | | placeholder="请输入路由路径" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col |
| | | v-show="newFormInline.menuType === 0" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="组件路径"> |
| | | <el-input |
| | | v-model="newFormInline.component" |
| | | clearable |
| | | placeholder="请输入组件路径" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="菜单排序"> |
| | | <el-input-number |
| | | v-model="newFormInline.rank" |
| | | class="w-full!" |
| | | :min="1" |
| | | :max="9999" |
| | | controls-position="right" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col |
| | | v-show="newFormInline.menuType === 0" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="路由重定向"> |
| | | <el-input |
| | | v-model="newFormInline.redirect" |
| | | clearable |
| | | placeholder="请输入默认跳转地址" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col |
| | | v-show="newFormInline.menuType !== 3" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="菜单图标"> |
| | | <IconSelect v-model="newFormInline.icon" class="w-full" /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col |
| | | v-show="newFormInline.menuType !== 3" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="右侧图标"> |
| | | <el-input |
| | | v-model="newFormInline.extraIcon" |
| | | clearable |
| | | placeholder="菜单名称右侧的额外图标" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="进场动画"> |
| | | <ReAnimateSelector |
| | | v-model="newFormInline.enterTransition" |
| | | placeholder="请选择页面进场加载动画" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="离场动画"> |
| | | <ReAnimateSelector |
| | | v-model="newFormInline.leaveTransition" |
| | | placeholder="请选择页面离场加载动画" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col |
| | | v-show="newFormInline.menuType === 0" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="菜单激活"> |
| | | <el-input |
| | | v-model="newFormInline.activePath" |
| | | clearable |
| | | placeholder="请输入需要激活的菜单" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col v-if="newFormInline.menuType === 3" :value="12" :xs="24" :sm="24"> |
| | | <!-- 按钮级别权限设置 --> |
| | | <el-form-item label="权限标识" prop="auths"> |
| | | <el-input |
| | | v-model="newFormInline.auths" |
| | | clearable |
| | | placeholder="请输入权限标识" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col |
| | | v-show="newFormInline.menuType === 1" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <!-- iframe --> |
| | | <el-form-item label="链接地址"> |
| | | <el-input |
| | | v-model="newFormInline.frameSrc" |
| | | clearable |
| | | placeholder="请输入 iframe 链接地址" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col v-if="newFormInline.menuType === 1" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="加载动画"> |
| | | <Segmented |
| | | :modelValue="newFormInline.frameLoading ? 0 : 1" |
| | | :options="frameLoadingOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.frameLoading = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col |
| | | v-show="newFormInline.menuType !== 3" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="菜单"> |
| | | <Segmented |
| | | :modelValue="newFormInline.showLink ? 0 : 1" |
| | | :options="showLinkOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.showLink = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col |
| | | v-show="newFormInline.menuType !== 3" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="父级菜单"> |
| | | <Segmented |
| | | :modelValue="newFormInline.showParent ? 0 : 1" |
| | | :options="showParentOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.showParent = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="缓存页面"> |
| | | <Segmented |
| | | :modelValue="newFormInline.keepAlive ? 0 : 1" |
| | | :options="keepAliveOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.keepAlive = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="标签页"> |
| | | <Segmented |
| | | :modelValue="newFormInline.hiddenTag ? 1 : 0" |
| | | :options="hiddenTagOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.hiddenTag = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="固定标签页"> |
| | | <Segmented |
| | | :modelValue="newFormInline.fixedTag ? 0 : 1" |
| | | :options="fixedTagOptions" |
| | | @change=" |
| | | ({ option: { value } }) => { |
| | | newFormInline.fixedTag = value; |
| | | } |
| | | " |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | </el-row> |
| | | </el-form> |
| | | </template> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import { useMenu } from "./utils/hook"; |
| | | import { transformI18n } from "@/plugins/i18n"; |
| | | import { PureTableBar } from "@/components/RePureTableBar"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | |
| | | import Delete from "~icons/ep/delete"; |
| | | import EditPen from "~icons/ep/edit-pen"; |
| | | import Refresh from "~icons/ep/refresh"; |
| | | import AddFill from "~icons/ri/add-circle-line"; |
| | | |
| | | defineOptions({ |
| | | name: "SystemMenu" |
| | | }); |
| | | |
| | | const formRef = ref(); |
| | | const tableRef = ref(); |
| | | const { |
| | | form, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | onSearch, |
| | | resetForm, |
| | | openDialog, |
| | | handleDelete, |
| | | handleSelectionChange |
| | | } = useMenu(); |
| | | |
| | | function onFullscreen() { |
| | | // 重置表格高度 |
| | | tableRef.value.setAdaptive(); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="main"> |
| | | <el-form |
| | | ref="formRef" |
| | | :inline="true" |
| | | :model="form" |
| | | class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" |
| | | > |
| | | <el-form-item label="菜单名称:" prop="title"> |
| | | <el-input |
| | | v-model="form.title" |
| | | placeholder="请输入菜单名称" |
| | | clearable |
| | | class="w-[180px]!" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon('ri/search-line')" |
| | | :loading="loading" |
| | | @click="onSearch" |
| | | > |
| | | 搜索 |
| | | </el-button> |
| | | <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <PureTableBar |
| | | title="菜单管理(仅演示,操作后不生效)" |
| | | :columns="columns" |
| | | :isExpandAll="false" |
| | | :tableRef="tableRef?.getTableRef()" |
| | | @refresh="onSearch" |
| | | @fullscreen="onFullscreen" |
| | | > |
| | | <template #buttons> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog()" |
| | | > |
| | | 新增菜单 |
| | | </el-button> |
| | | </template> |
| | | <template v-slot="{ size, dynamicColumns }"> |
| | | <pure-table |
| | | ref="tableRef" |
| | | adaptive |
| | | :adaptiveConfig="{ offsetBottom: 45 }" |
| | | align-whole="center" |
| | | row-key="id" |
| | | showOverflowTooltip |
| | | table-layout="auto" |
| | | :loading="loading" |
| | | :size="size" |
| | | :data="dataList" |
| | | :columns="dynamicColumns" |
| | | :header-cell-style="{ |
| | | background: 'var(--el-fill-color-light)', |
| | | color: 'var(--el-text-color-primary)' |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | > |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(EditPen)" |
| | | @click="openDialog('修改', row)" |
| | | > |
| | | 修改 |
| | | </el-button> |
| | | <el-button |
| | | v-show="row.menuType !== 3" |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog('新增', { parentId: row.id } as any)" |
| | | > |
| | | 新增 |
| | | </el-button> |
| | | <el-popconfirm |
| | | :title="`是否确认删除菜单名称为${transformI18n(row.title)}的这条数据${row?.children?.length > 0 ? '。注意下级菜单也会一并删除,请谨慎操作' : ''}`" |
| | | @confirm="handleDelete(row)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Delete)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-popconfirm> |
| | | </template> |
| | | </pure-table> |
| | | </template> |
| | | </PureTableBar> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | :deep(.el-table__inner-wrapper::before) { |
| | | height: 0; |
| | | } |
| | | |
| | | .main-content { |
| | | margin: 24px 24px 0 !important; |
| | | } |
| | | |
| | | .search-form { |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 12px; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import type { OptionsType } from "@/components/ReSegmented"; |
| | | |
| | | const menuTypeOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "菜单", |
| | | value: 0 |
| | | }, |
| | | { |
| | | label: "iframe", |
| | | value: 1 |
| | | }, |
| | | { |
| | | label: "外链", |
| | | value: 2 |
| | | }, |
| | | { |
| | | label: "按钮", |
| | | value: 3 |
| | | } |
| | | ]; |
| | | |
| | | const showLinkOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "显示", |
| | | tip: "会在菜单中显示", |
| | | value: true |
| | | }, |
| | | { |
| | | label: "隐藏", |
| | | tip: "不会在菜单中显示", |
| | | value: false |
| | | } |
| | | ]; |
| | | |
| | | const fixedTagOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "固定", |
| | | tip: "当前菜单名称固定显示在标签页且不可关闭", |
| | | value: true |
| | | }, |
| | | { |
| | | label: "不固定", |
| | | tip: "当前菜单名称不固定显示在标签页且可关闭", |
| | | value: false |
| | | } |
| | | ]; |
| | | |
| | | const keepAliveOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "缓存", |
| | | tip: "会保存该页面的整体状态,刷新后会清空状态", |
| | | value: true |
| | | }, |
| | | { |
| | | label: "不缓存", |
| | | tip: "不会保存该页面的整体状态", |
| | | value: false |
| | | } |
| | | ]; |
| | | |
| | | const hiddenTagOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "允许", |
| | | tip: "当前菜单名称或自定义信息允许添加到标签页", |
| | | value: false |
| | | }, |
| | | { |
| | | label: "禁止", |
| | | tip: "当前菜单名称或自定义信息禁止添加到标签页", |
| | | value: true |
| | | } |
| | | ]; |
| | | |
| | | const showParentOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "显示", |
| | | tip: "会显示父级菜单", |
| | | value: true |
| | | }, |
| | | { |
| | | label: "隐藏", |
| | | tip: "不会显示父级菜单", |
| | | value: false |
| | | } |
| | | ]; |
| | | |
| | | const frameLoadingOptions: Array<OptionsType> = [ |
| | | { |
| | | label: "开启", |
| | | tip: "有首次加载动画", |
| | | value: true |
| | | }, |
| | | { |
| | | label: "关闭", |
| | | tip: "无首次加载动画", |
| | | value: false |
| | | } |
| | | ]; |
| | | |
| | | export { |
| | | menuTypeOptions, |
| | | showLinkOptions, |
| | | fixedTagOptions, |
| | | keepAliveOptions, |
| | | hiddenTagOptions, |
| | | showParentOptions, |
| | | frameLoadingOptions |
| | | }; |
New file |
| | |
| | | import editForm from "../form.vue"; |
| | | import { handleTree } from "@/utils/tree"; |
| | | import { message } from "@/utils/message"; |
| | | import { getMenuList } from "@/api/system"; |
| | | import { transformI18n } from "@/plugins/i18n"; |
| | | import { addDialog } from "@/components/ReDialog"; |
| | | import { reactive, ref, onMounted, h } from "vue"; |
| | | import type { FormItemProps } from "../utils/types"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils"; |
| | | |
| | | export function useMenu() { |
| | | const form = reactive({ |
| | | title: "" |
| | | }); |
| | | |
| | | const formRef = ref(); |
| | | const dataList = ref([]); |
| | | const loading = ref(true); |
| | | |
| | | const getMenuType = (type, text = false) => { |
| | | switch (type) { |
| | | case 0: |
| | | return text ? "菜单" : "primary"; |
| | | case 1: |
| | | return text ? "iframe" : "warning"; |
| | | case 2: |
| | | return text ? "外链" : "danger"; |
| | | case 3: |
| | | return text ? "按钮" : "info"; |
| | | } |
| | | }; |
| | | |
| | | const columns: TableColumnList = [ |
| | | { |
| | | label: "菜单名称", |
| | | prop: "title", |
| | | align: "left", |
| | | cellRenderer: ({ row }) => ( |
| | | <> |
| | | <span class="inline-block mr-1"> |
| | | {h(useRenderIcon(row.icon), { |
| | | style: { paddingTop: "1px" } |
| | | })} |
| | | </span> |
| | | <span>{transformI18n(row.title)}</span> |
| | | </> |
| | | ) |
| | | }, |
| | | { |
| | | label: "菜单类型", |
| | | prop: "menuType", |
| | | width: 100, |
| | | cellRenderer: ({ row, props }) => ( |
| | | <el-tag |
| | | size={props.size} |
| | | type={getMenuType(row.menuType)} |
| | | effect="plain" |
| | | > |
| | | {getMenuType(row.menuType, true)} |
| | | </el-tag> |
| | | ) |
| | | }, |
| | | { |
| | | label: "路由路径", |
| | | prop: "path" |
| | | }, |
| | | { |
| | | label: "组件路径", |
| | | prop: "component", |
| | | formatter: ({ path, component }) => |
| | | isAllEmpty(component) ? path : component |
| | | }, |
| | | { |
| | | label: "权限标识", |
| | | prop: "auths" |
| | | }, |
| | | { |
| | | label: "排序", |
| | | prop: "rank", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "隐藏", |
| | | prop: "showLink", |
| | | formatter: ({ showLink }) => (showLink ? "否" : "是"), |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "操作", |
| | | fixed: "right", |
| | | width: 210, |
| | | slot: "operation" |
| | | } |
| | | ]; |
| | | |
| | | function handleSelectionChange(val) { |
| | | console.log("handleSelectionChange", val); |
| | | } |
| | | |
| | | function resetForm(formEl) { |
| | | if (!formEl) return; |
| | | formEl.resetFields(); |
| | | onSearch(); |
| | | } |
| | | |
| | | async function onSearch() { |
| | | loading.value = true; |
| | | const { data } = await getMenuList(); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id |
| | | let newData = data; |
| | | if (!isAllEmpty(form.title)) { |
| | | // 前端搜索菜单名称 |
| | | newData = newData.filter(item => |
| | | transformI18n(item.title).includes(form.title) |
| | | ); |
| | | } |
| | | dataList.value = handleTree(newData); // 处理成树结构 |
| | | setTimeout(() => { |
| | | loading.value = false; |
| | | }, 500); |
| | | } |
| | | |
| | | function formatHigherMenuOptions(treeList) { |
| | | if (!treeList || !treeList.length) return; |
| | | const newTreeList = []; |
| | | for (let i = 0; i < treeList.length; i++) { |
| | | treeList[i].title = transformI18n(treeList[i].title); |
| | | formatHigherMenuOptions(treeList[i].children); |
| | | newTreeList.push(treeList[i]); |
| | | } |
| | | return newTreeList; |
| | | } |
| | | |
| | | function openDialog(title = "新增", row?: FormItemProps) { |
| | | addDialog({ |
| | | title: `${title}菜单`, |
| | | props: { |
| | | formInline: { |
| | | menuType: row?.menuType ?? 0, |
| | | higherMenuOptions: formatHigherMenuOptions(cloneDeep(dataList.value)), |
| | | parentId: row?.parentId ?? 0, |
| | | title: row?.title ?? "", |
| | | name: row?.name ?? "", |
| | | path: row?.path ?? "", |
| | | component: row?.component ?? "", |
| | | rank: row?.rank ?? 99, |
| | | redirect: row?.redirect ?? "", |
| | | icon: row?.icon ?? "", |
| | | extraIcon: row?.extraIcon ?? "", |
| | | enterTransition: row?.enterTransition ?? "", |
| | | leaveTransition: row?.leaveTransition ?? "", |
| | | activePath: row?.activePath ?? "", |
| | | auths: row?.auths ?? "", |
| | | frameSrc: row?.frameSrc ?? "", |
| | | frameLoading: row?.frameLoading ?? true, |
| | | keepAlive: row?.keepAlive ?? false, |
| | | hiddenTag: row?.hiddenTag ?? false, |
| | | fixedTag: row?.fixedTag ?? false, |
| | | showLink: row?.showLink ?? true, |
| | | showParent: row?.showParent ?? false |
| | | } |
| | | }, |
| | | width: "45%", |
| | | draggable: true, |
| | | fullscreen: deviceDetection(), |
| | | fullscreenIcon: true, |
| | | closeOnClickModal: false, |
| | | contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), |
| | | beforeSure: (done, { options }) => { |
| | | const FormRef = formRef.value.getRef(); |
| | | const curData = options.props.formInline as FormItemProps; |
| | | function chores() { |
| | | message( |
| | | `您${title}了菜单名称为${transformI18n(curData.title)}的这条数据`, |
| | | { |
| | | type: "success" |
| | | } |
| | | ); |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | } |
| | | FormRef.validate(valid => { |
| | | if (valid) { |
| | | console.log("curData", curData); |
| | | // 表单规则校验通过 |
| | | if (title === "新增") { |
| | | // 实际开发先调用新增接口,再进行下面操作 |
| | | chores(); |
| | | } else { |
| | | // 实际开发先调用修改接口,再进行下面操作 |
| | | chores(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function handleDelete(row) { |
| | | message(`您删除了菜单名称为${transformI18n(row.title)}的这条数据`, { |
| | | type: "success" |
| | | }); |
| | | onSearch(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | onSearch(); |
| | | }); |
| | | |
| | | return { |
| | | form, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | /** 搜索 */ |
| | | onSearch, |
| | | /** 重置 */ |
| | | resetForm, |
| | | /** 新增、修改菜单 */ |
| | | openDialog, |
| | | /** 删除菜单 */ |
| | | handleDelete, |
| | | handleSelectionChange |
| | | }; |
| | | } |
New file |
| | |
| | | import { reactive } from "vue"; |
| | | import type { FormRules } from "element-plus"; |
| | | |
| | | /** 自定义表单规则校验 */ |
| | | export const formRules = reactive(<FormRules>{ |
| | | title: [{ required: true, message: "菜单名称为必填项", trigger: "blur" }], |
| | | name: [{ required: true, message: "路由名称为必填项", trigger: "blur" }], |
| | | path: [{ required: true, message: "路由路径为必填项", trigger: "blur" }], |
| | | auths: [{ required: true, message: "权限标识为必填项", trigger: "blur" }] |
| | | }); |
New file |
| | |
| | | interface FormItemProps { |
| | | /** 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮)*/ |
| | | menuType: number; |
| | | higherMenuOptions: Record<string, unknown>[]; |
| | | parentId: number; |
| | | title: string; |
| | | name: string; |
| | | path: string; |
| | | component: string; |
| | | rank: number; |
| | | redirect: string; |
| | | icon: string; |
| | | extraIcon: string; |
| | | enterTransition: string; |
| | | leaveTransition: string; |
| | | activePath: string; |
| | | auths: string; |
| | | frameSrc: string; |
| | | frameLoading: boolean; |
| | | keepAlive: boolean; |
| | | hiddenTag: boolean; |
| | | fixedTag: boolean; |
| | | showLink: boolean; |
| | | showParent: boolean; |
| | | } |
| | | interface FormProps { |
| | | formInline: FormItemProps; |
| | | } |
| | | |
| | | export type { FormItemProps, FormProps }; |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import { formRules } from "./utils/rule"; |
| | | import { FormProps } from "./utils/types"; |
| | | |
| | | const props = withDefaults(defineProps<FormProps>(), { |
| | | formInline: () => ({ |
| | | name: "", |
| | | code: "", |
| | | remark: "" |
| | | }) |
| | | }); |
| | | |
| | | const ruleFormRef = ref(); |
| | | const newFormInline = ref(props.formInline); |
| | | |
| | | function getRef() { |
| | | return ruleFormRef.value; |
| | | } |
| | | |
| | | defineExpose({ getRef }); |
| | | </script> |
| | | |
| | | <template> |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | :model="newFormInline" |
| | | :rules="formRules" |
| | | label-width="82px" |
| | | > |
| | | <el-form-item label="角色名称" prop="name"> |
| | | <el-input |
| | | v-model="newFormInline.name" |
| | | clearable |
| | | placeholder="请输入角色名称" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="角色标识" prop="code"> |
| | | <el-input |
| | | v-model="newFormInline.code" |
| | | clearable |
| | | placeholder="请输入角色标识" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="备注"> |
| | | <el-input |
| | | v-model="newFormInline.remark" |
| | | placeholder="请输入备注信息" |
| | | type="textarea" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | </template> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { useRole } from "./utils/hook"; |
| | | import { ref, computed, nextTick, onMounted } from "vue"; |
| | | import { PureTableBar } from "@/components/RePureTableBar"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | import { |
| | | delay, |
| | | subBefore, |
| | | deviceDetection, |
| | | useResizeObserver |
| | | } from "@pureadmin/utils"; |
| | | |
| | | // import Database from "~icons/ri/database-2-line"; |
| | | // import More from "~icons/ep/more-filled"; |
| | | import Delete from "~icons/ep/delete"; |
| | | import EditPen from "~icons/ep/edit-pen"; |
| | | import Refresh from "~icons/ep/refresh"; |
| | | import Menu from "~icons/ep/menu"; |
| | | import AddFill from "~icons/ri/add-circle-line"; |
| | | import Close from "~icons/ep/close"; |
| | | import Check from "~icons/ep/check"; |
| | | |
| | | defineOptions({ |
| | | name: "SystemRole" |
| | | }); |
| | | |
| | | const iconClass = computed(() => { |
| | | return [ |
| | | "w-[22px]", |
| | | "h-[22px]", |
| | | "flex", |
| | | "justify-center", |
| | | "items-center", |
| | | "outline-hidden", |
| | | "rounded-[4px]", |
| | | "cursor-pointer", |
| | | "transition-colors", |
| | | "hover:bg-[#0000000f]", |
| | | "dark:hover:bg-[#ffffff1f]", |
| | | "dark:hover:text-[#ffffffd9]" |
| | | ]; |
| | | }); |
| | | |
| | | const treeRef = ref(); |
| | | const formRef = ref(); |
| | | const tableRef = ref(); |
| | | const contentRef = ref(); |
| | | const treeHeight = ref(); |
| | | |
| | | const { |
| | | form, |
| | | isShow, |
| | | curRow, |
| | | loading, |
| | | columns, |
| | | rowStyle, |
| | | dataList, |
| | | treeData, |
| | | treeProps, |
| | | isLinkage, |
| | | pagination, |
| | | isExpandAll, |
| | | isSelectAll, |
| | | treeSearchValue, |
| | | // buttonClass, |
| | | onSearch, |
| | | resetForm, |
| | | openDialog, |
| | | handleMenu, |
| | | handleSave, |
| | | handleDelete, |
| | | filterMethod, |
| | | transformI18n, |
| | | onQueryChanged, |
| | | // handleDatabase, |
| | | handleSizeChange, |
| | | handleCurrentChange, |
| | | handleSelectionChange |
| | | } = useRole(treeRef); |
| | | |
| | | onMounted(() => { |
| | | useResizeObserver(contentRef, async () => { |
| | | await nextTick(); |
| | | delay(60).then(() => { |
| | | treeHeight.value = parseFloat( |
| | | subBefore(tableRef.value.getTableDoms().tableWrapper.style.height, "px") |
| | | ); |
| | | }); |
| | | }); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="main"> |
| | | <el-form |
| | | ref="formRef" |
| | | :inline="true" |
| | | :model="form" |
| | | class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" |
| | | > |
| | | <el-form-item label="角色名称:" prop="name"> |
| | | <el-input |
| | | v-model="form.name" |
| | | placeholder="请输入角色名称" |
| | | clearable |
| | | class="w-[180px]!" |
| | | /> |
| | | </el-form-item> |
| | | <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="status"> |
| | | <el-select |
| | | v-model="form.status" |
| | | placeholder="请选择状态" |
| | | clearable |
| | | class="w-[180px]!" |
| | | > |
| | | <el-option label="已启用" value="1" /> |
| | | <el-option label="已停用" value="0" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon('ri/search-line')" |
| | | :loading="loading" |
| | | @click="onSearch" |
| | | > |
| | | 搜索 |
| | | </el-button> |
| | | <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div |
| | | ref="contentRef" |
| | | :class="['flex', deviceDetection() ? 'flex-wrap' : '']" |
| | | > |
| | | <PureTableBar |
| | | :class="[isShow && !deviceDetection() ? 'w-[60vw]!' : 'w-full']" |
| | | style="transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1)" |
| | | title="角色管理(仅演示,操作后不生效)" |
| | | :columns="columns" |
| | | @refresh="onSearch" |
| | | > |
| | | <template #buttons> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog()" |
| | | > |
| | | 新增角色 |
| | | </el-button> |
| | | </template> |
| | | <template v-slot="{ size, dynamicColumns }"> |
| | | <pure-table |
| | | ref="tableRef" |
| | | align-whole="center" |
| | | showOverflowTooltip |
| | | table-layout="auto" |
| | | :loading="loading" |
| | | :size="size" |
| | | adaptive |
| | | :row-style="rowStyle" |
| | | :adaptiveConfig="{ offsetBottom: 108 }" |
| | | :data="dataList" |
| | | :columns="dynamicColumns" |
| | | :pagination="{ ...pagination, size }" |
| | | :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)" |
| | | > |
| | | 修改 |
| | | </el-button> |
| | | <el-popconfirm |
| | | :title="`是否确认删除角色名称为${row.name}的这条数据`" |
| | | @confirm="handleDelete(row)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Delete)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-popconfirm> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Menu)" |
| | | @click="handleMenu(row)" |
| | | > |
| | | 权限 |
| | | </el-button> |
| | | <!-- <el-dropdown> |
| | | <el-button |
| | | class="ml-3 mt-[2px]" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(More)" |
| | | /> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Menu)" |
| | | @click="handleMenu" |
| | | > |
| | | 菜单权限 |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Database)" |
| | | @click="handleDatabase" |
| | | > |
| | | 数据权限 |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> --> |
| | | </template> |
| | | </pure-table> |
| | | </template> |
| | | </PureTableBar> |
| | | |
| | | <div |
| | | v-if="isShow" |
| | | class="min-w-[calc(100vw-60vw-268px)]! w-full mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto" |
| | | > |
| | | <div class="flex justify-between w-full px-3 pt-5 pb-4"> |
| | | <div class="flex"> |
| | | <span :class="iconClass"> |
| | | <IconifyIconOffline |
| | | v-tippy="{ |
| | | content: '关闭' |
| | | }" |
| | | class="dark:text-white" |
| | | width="18px" |
| | | height="18px" |
| | | :icon="Close" |
| | | @click="handleMenu" |
| | | /> |
| | | </span> |
| | | <span :class="[iconClass, 'ml-2']"> |
| | | <IconifyIconOffline |
| | | v-tippy="{ |
| | | content: '保存菜单权限' |
| | | }" |
| | | class="dark:text-white" |
| | | width="18px" |
| | | height="18px" |
| | | :icon="Check" |
| | | @click="handleSave" |
| | | /> |
| | | </span> |
| | | </div> |
| | | <p class="font-bold truncate"> |
| | | 菜单权限 |
| | | {{ `${curRow?.name ? `(${curRow.name})` : ""}` }} |
| | | </p> |
| | | </div> |
| | | <el-input |
| | | v-model="treeSearchValue" |
| | | placeholder="请输入菜单进行搜索" |
| | | class="mb-1" |
| | | clearable |
| | | @input="onQueryChanged" |
| | | /> |
| | | <div class="flex flex-wrap"> |
| | | <el-checkbox v-model="isExpandAll" label="展开/折叠" /> |
| | | <el-checkbox v-model="isSelectAll" label="全选/全不选" /> |
| | | <el-checkbox v-model="isLinkage" label="父子联动" /> |
| | | </div> |
| | | <el-tree-v2 |
| | | ref="treeRef" |
| | | show-checkbox |
| | | :data="treeData" |
| | | :props="treeProps" |
| | | :height="treeHeight" |
| | | :check-strictly="!isLinkage" |
| | | :filter-method="filterMethod" |
| | | > |
| | | <template #default="{ node }"> |
| | | <span>{{ transformI18n(node.label) }}</span> |
| | | </template> |
| | | </el-tree-v2> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | :deep(.el-dropdown-menu__item i) { |
| | | margin: 0; |
| | | } |
| | | |
| | | .main-content { |
| | | margin: 24px 24px 0 !important; |
| | | } |
| | | |
| | | .search-form { |
| | | :deep(.el-form-item) { |
| | | margin-bottom: 12px; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import dayjs from "dayjs"; |
| | | import editForm from "../form.vue"; |
| | | import { handleTree } from "@/utils/tree"; |
| | | import { message } from "@/utils/message"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { usePublicHooks } from "../../hooks"; |
| | | import { transformI18n } from "@/plugins/i18n"; |
| | | import { addDialog } from "@/components/ReDialog"; |
| | | import type { FormItemProps } from "../utils/types"; |
| | | import type { PaginationProps } from "@pureadmin/table"; |
| | | import { getKeyList, deviceDetection } from "@pureadmin/utils"; |
| | | import { getRoleList, getRoleMenu, getRoleMenuIds } from "@/api/system"; |
| | | import { type Ref, reactive, ref, onMounted, h, toRaw, watch } from "vue"; |
| | | |
| | | export function useRole(treeRef: Ref) { |
| | | const form = reactive({ |
| | | name: "", |
| | | code: "", |
| | | status: "" |
| | | }); |
| | | const curRow = ref(); |
| | | const formRef = ref(); |
| | | const dataList = ref([]); |
| | | const treeIds = ref([]); |
| | | const treeData = ref([]); |
| | | const isShow = ref(false); |
| | | const loading = ref(true); |
| | | const isLinkage = ref(false); |
| | | const treeSearchValue = ref(); |
| | | const switchLoadMap = ref({}); |
| | | const isExpandAll = ref(false); |
| | | const isSelectAll = ref(false); |
| | | const { switchStyle } = usePublicHooks(); |
| | | const treeProps = { |
| | | value: "id", |
| | | label: "title", |
| | | children: "children" |
| | | }; |
| | | const pagination = reactive<PaginationProps>({ |
| | | total: 0, |
| | | pageSize: 10, |
| | | currentPage: 1, |
| | | background: true |
| | | }); |
| | | const columns: TableColumnList = [ |
| | | { |
| | | label: "角色编号", |
| | | prop: "id" |
| | | }, |
| | | { |
| | | label: "角色名称", |
| | | prop: "name" |
| | | }, |
| | | { |
| | | label: "角色标识", |
| | | prop: "code" |
| | | }, |
| | | { |
| | | label: "状态", |
| | | cellRenderer: scope => ( |
| | | <el-switch |
| | | size={scope.props.size === "small" ? "small" : "default"} |
| | | loading={switchLoadMap.value[scope.index]?.loading} |
| | | v-model={scope.row.status} |
| | | active-value={1} |
| | | inactive-value={0} |
| | | active-text="已启用" |
| | | inactive-text="已停用" |
| | | inline-prompt |
| | | style={switchStyle.value} |
| | | onChange={() => onChange(scope as any)} |
| | | /> |
| | | ), |
| | | minWidth: 90 |
| | | }, |
| | | { |
| | | label: "备注", |
| | | prop: "remark", |
| | | minWidth: 160 |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | minWidth: 160, |
| | | formatter: ({ createTime }) => |
| | | dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") |
| | | }, |
| | | { |
| | | label: "操作", |
| | | fixed: "right", |
| | | width: 210, |
| | | slot: "operation" |
| | | } |
| | | ]; |
| | | // const buttonClass = computed(() => { |
| | | // return [ |
| | | // "h-[20px]!", |
| | | // "reset-margin", |
| | | // "text-gray-500!", |
| | | // "dark:text-white!", |
| | | // "dark:hover:text-primary!" |
| | | // ]; |
| | | // }); |
| | | |
| | | function onChange({ row, index }) { |
| | | ElMessageBox.confirm( |
| | | `确认要<strong>${ |
| | | row.status === 0 ? "停用" : "启用" |
| | | }</strong><strong style='color:var(--el-color-primary)'>${ |
| | | row.name |
| | | }</strong>吗?`, |
| | | "系统提示", |
| | | { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | dangerouslyUseHTMLString: true, |
| | | draggable: true |
| | | } |
| | | ) |
| | | .then(() => { |
| | | switchLoadMap.value[index] = Object.assign( |
| | | {}, |
| | | switchLoadMap.value[index], |
| | | { |
| | | loading: true |
| | | } |
| | | ); |
| | | setTimeout(() => { |
| | | switchLoadMap.value[index] = Object.assign( |
| | | {}, |
| | | switchLoadMap.value[index], |
| | | { |
| | | loading: false |
| | | } |
| | | ); |
| | | message(`已${row.status === 0 ? "停用" : "启用"}${row.name}`, { |
| | | type: "success" |
| | | }); |
| | | }, 300); |
| | | }) |
| | | .catch(() => { |
| | | row.status === 0 ? (row.status = 1) : (row.status = 0); |
| | | }); |
| | | } |
| | | |
| | | function handleDelete(row) { |
| | | message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" }); |
| | | onSearch(); |
| | | } |
| | | |
| | | function handleSizeChange(val: number) { |
| | | console.log(`${val} items per page`); |
| | | } |
| | | |
| | | function handleCurrentChange(val: number) { |
| | | console.log(`current page: ${val}`); |
| | | } |
| | | |
| | | function handleSelectionChange(val) { |
| | | console.log("handleSelectionChange", val); |
| | | } |
| | | |
| | | async function onSearch() { |
| | | loading.value = true; |
| | | const { data } = await getRoleList(toRaw(form)); |
| | | dataList.value = data.list; |
| | | pagination.total = data.total; |
| | | pagination.pageSize = data.pageSize; |
| | | pagination.currentPage = data.currentPage; |
| | | |
| | | setTimeout(() => { |
| | | loading.value = false; |
| | | }, 500); |
| | | } |
| | | |
| | | const resetForm = formEl => { |
| | | if (!formEl) return; |
| | | formEl.resetFields(); |
| | | onSearch(); |
| | | }; |
| | | |
| | | function openDialog(title = "新增", row?: FormItemProps) { |
| | | addDialog({ |
| | | title: `${title}角色`, |
| | | props: { |
| | | formInline: { |
| | | name: row?.name ?? "", |
| | | code: row?.code ?? "", |
| | | remark: row?.remark ?? "" |
| | | } |
| | | }, |
| | | width: "40%", |
| | | draggable: true, |
| | | fullscreen: deviceDetection(), |
| | | fullscreenIcon: true, |
| | | closeOnClickModal: false, |
| | | contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), |
| | | beforeSure: (done, { options }) => { |
| | | const FormRef = formRef.value.getRef(); |
| | | const curData = options.props.formInline as FormItemProps; |
| | | function chores() { |
| | | message(`您${title}了角色名称为${curData.name}的这条数据`, { |
| | | type: "success" |
| | | }); |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | } |
| | | FormRef.validate(valid => { |
| | | if (valid) { |
| | | console.log("curData", curData); |
| | | // 表单规则校验通过 |
| | | if (title === "新增") { |
| | | // 实际开发先调用新增接口,再进行下面操作 |
| | | chores(); |
| | | } else { |
| | | // 实际开发先调用修改接口,再进行下面操作 |
| | | chores(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** 菜单权限 */ |
| | | async function handleMenu(row?: any) { |
| | | const { id } = row; |
| | | if (id) { |
| | | curRow.value = row; |
| | | isShow.value = true; |
| | | const { data } = await getRoleMenuIds({ id }); |
| | | treeRef.value.setCheckedKeys(data); |
| | | } else { |
| | | curRow.value = null; |
| | | isShow.value = false; |
| | | } |
| | | } |
| | | |
| | | /** 高亮当前权限选中行 */ |
| | | function rowStyle({ row: { id } }) { |
| | | return { |
| | | cursor: "pointer", |
| | | background: id === curRow.value?.id ? "var(--el-fill-color-light)" : "" |
| | | }; |
| | | } |
| | | |
| | | /** 菜单权限-保存 */ |
| | | function handleSave() { |
| | | const { id, name } = curRow.value; |
| | | // 根据用户 id 调用实际项目中菜单权限修改接口 |
| | | console.log(id, treeRef.value.getCheckedKeys()); |
| | | message(`角色名称为${name}的菜单权限修改成功`, { |
| | | type: "success" |
| | | }); |
| | | } |
| | | |
| | | /** 数据权限 可自行开发 */ |
| | | // function handleDatabase() {} |
| | | |
| | | const onQueryChanged = (query: string) => { |
| | | treeRef.value!.filter(query); |
| | | }; |
| | | |
| | | const filterMethod = (query: string, node) => { |
| | | return transformI18n(node.title)!.includes(query); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | onSearch(); |
| | | const { data } = await getRoleMenu(); |
| | | treeIds.value = getKeyList(data, "id"); |
| | | treeData.value = handleTree(data); |
| | | }); |
| | | |
| | | watch(isExpandAll, val => { |
| | | val |
| | | ? treeRef.value.setExpandedKeys(treeIds.value) |
| | | : treeRef.value.setExpandedKeys([]); |
| | | }); |
| | | |
| | | watch(isSelectAll, val => { |
| | | val |
| | | ? treeRef.value.setCheckedKeys(treeIds.value) |
| | | : treeRef.value.setCheckedKeys([]); |
| | | }); |
| | | |
| | | return { |
| | | form, |
| | | isShow, |
| | | curRow, |
| | | loading, |
| | | columns, |
| | | rowStyle, |
| | | dataList, |
| | | treeData, |
| | | treeProps, |
| | | isLinkage, |
| | | pagination, |
| | | isExpandAll, |
| | | isSelectAll, |
| | | treeSearchValue, |
| | | // buttonClass, |
| | | onSearch, |
| | | resetForm, |
| | | openDialog, |
| | | handleMenu, |
| | | handleSave, |
| | | handleDelete, |
| | | filterMethod, |
| | | transformI18n, |
| | | onQueryChanged, |
| | | // handleDatabase, |
| | | handleSizeChange, |
| | | handleCurrentChange, |
| | | handleSelectionChange |
| | | }; |
| | | } |
New file |
| | |
| | | import { reactive } from "vue"; |
| | | import type { FormRules } from "element-plus"; |
| | | |
| | | /** 自定义表单规则校验 */ |
| | | export const formRules = reactive(<FormRules>{ |
| | | name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }], |
| | | code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }] |
| | | }); |
New file |
| | |
| | | // 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了 |
| | | |
| | | interface FormItemProps { |
| | | /** 角色名称 */ |
| | | name: string; |
| | | /** 角色编号 */ |
| | | code: string; |
| | | /** 备注 */ |
| | | remark: string; |
| | | } |
| | | interface FormProps { |
| | | formInline: FormItemProps; |
| | | } |
| | | |
| | | export type { FormItemProps, FormProps }; |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import ReCol from "@/components/ReCol"; |
| | | import { formRules } from "../utils/rule"; |
| | | import { FormProps } from "../utils/types"; |
| | | import { usePublicHooks } from "../../hooks"; |
| | | |
| | | const props = withDefaults(defineProps<FormProps>(), { |
| | | formInline: () => ({ |
| | | title: "新增", |
| | | higherDeptOptions: [], |
| | | parentId: 0, |
| | | nickname: "", |
| | | username: "", |
| | | password: "", |
| | | phone: "", |
| | | email: "", |
| | | sex: "", |
| | | status: 1, |
| | | remark: "" |
| | | }) |
| | | }); |
| | | |
| | | const sexOptions = [ |
| | | { |
| | | value: 0, |
| | | label: "男" |
| | | }, |
| | | { |
| | | value: 1, |
| | | label: "女" |
| | | } |
| | | ]; |
| | | const ruleFormRef = ref(); |
| | | const { switchStyle } = usePublicHooks(); |
| | | const newFormInline = ref(props.formInline); |
| | | |
| | | function getRef() { |
| | | return ruleFormRef.value; |
| | | } |
| | | |
| | | defineExpose({ getRef }); |
| | | </script> |
| | | |
| | | <template> |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | :model="newFormInline" |
| | | :rules="formRules" |
| | | label-width="82px" |
| | | > |
| | | <el-row :gutter="30"> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="用户昵称" prop="nickname"> |
| | | <el-input |
| | | v-model="newFormInline.nickname" |
| | | clearable |
| | | placeholder="请输入用户昵称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="用户名称" prop="username"> |
| | | <el-input |
| | | v-model="newFormInline.username" |
| | | clearable |
| | | placeholder="请输入用户名称" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col |
| | | v-if="newFormInline.title === '新增'" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="用户密码" prop="password"> |
| | | <el-input |
| | | v-model="newFormInline.password" |
| | | clearable |
| | | placeholder="请输入用户密码" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="手机号" prop="phone"> |
| | | <el-input |
| | | v-model="newFormInline.phone" |
| | | clearable |
| | | placeholder="请输入手机号" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="邮箱" prop="email"> |
| | | <el-input |
| | | v-model="newFormInline.email" |
| | | clearable |
| | | placeholder="请输入邮箱" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="用户性别"> |
| | | <el-select |
| | | v-model="newFormInline.sex" |
| | | placeholder="请选择用户性别" |
| | | class="w-full" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in sexOptions" |
| | | :key="index" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col :value="12" :xs="24" :sm="24"> |
| | | <el-form-item label="归属部门"> |
| | | <el-cascader |
| | | v-model="newFormInline.parentId" |
| | | class="w-full" |
| | | :options="newFormInline.higherDeptOptions" |
| | | :props="{ |
| | | value: 'id', |
| | | label: 'name', |
| | | emitPath: false, |
| | | checkStrictly: true |
| | | }" |
| | | clearable |
| | | filterable |
| | | placeholder="请选择归属部门" |
| | | > |
| | | <template #default="{ node, data }"> |
| | | <span>{{ data.name }}</span> |
| | | <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span> |
| | | </template> |
| | | </el-cascader> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col |
| | | v-if="newFormInline.title === '新增'" |
| | | :value="12" |
| | | :xs="24" |
| | | :sm="24" |
| | | > |
| | | <el-form-item label="用户状态"> |
| | | <el-switch |
| | | v-model="newFormInline.status" |
| | | inline-prompt |
| | | :active-value="1" |
| | | :inactive-value="0" |
| | | active-text="启用" |
| | | inactive-text="停用" |
| | | :style="switchStyle" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | |
| | | <re-col> |
| | | <el-form-item label="备注"> |
| | | <el-input |
| | | v-model="newFormInline.remark" |
| | | placeholder="请输入备注信息" |
| | | type="textarea" |
| | | /> |
| | | </el-form-item> |
| | | </re-col> |
| | | </el-row> |
| | | </el-form> |
| | | </template> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import ReCol from "@/components/ReCol"; |
| | | import { RoleFormProps } from "../utils/types"; |
| | | |
| | | const props = withDefaults(defineProps<RoleFormProps>(), { |
| | | formInline: () => ({ |
| | | username: "", |
| | | nickname: "", |
| | | roleOptions: [], |
| | | ids: [] |
| | | }) |
| | | }); |
| | | |
| | | const newFormInline = ref(props.formInline); |
| | | </script> |
| | | |
| | | <template> |
| | | <el-form :model="newFormInline"> |
| | | <el-row :gutter="30"> |
| | | <!-- <re-col> |
| | | <el-form-item label="用户名称" prop="username"> |
| | | <el-input disabled v-model="newFormInline.username" /> |
| | | </el-form-item> |
| | | </re-col> --> |
| | | <re-col> |
| | | <el-form-item label="用户昵称" prop="nickname"> |
| | | <el-input v-model="newFormInline.nickname" disabled /> |
| | | </el-form-item> |
| | | </re-col> |
| | | <re-col> |
| | | <el-form-item label="角色列表" prop="ids"> |
| | | <el-select |
| | | v-model="newFormInline.ids" |
| | | placeholder="请选择" |
| | | class="w-full" |
| | | clearable |
| | | multiple |
| | | > |
| | | <el-option |
| | | v-for="(item, index) in newFormInline.roleOptions" |
| | | :key="index" |
| | | :value="item.id" |
| | | :label="item.name" |
| | | > |
| | | {{ item.name }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </re-col> |
| | | </el-row> |
| | | </el-form> |
| | | </template> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { ref } from "vue"; |
| | | import tree from "./tree.vue"; |
| | | import { useUser } from "./utils/hook"; |
| | | import { PureTableBar } from "@/components/RePureTableBar"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | |
| | | import Upload from "~icons/ri/upload-line"; |
| | | import Role from "~icons/ri/admin-line"; |
| | | import Password from "~icons/ri/lock-password-line"; |
| | | import More from "~icons/ep/more-filled"; |
| | | import Delete from "~icons/ep/delete"; |
| | | import EditPen from "~icons/ep/edit-pen"; |
| | | import Refresh from "~icons/ep/refresh"; |
| | | import AddFill from "~icons/ri/add-circle-line"; |
| | | |
| | | defineOptions({ |
| | | name: "SystemUser" |
| | | }); |
| | | |
| | | const treeRef = ref(); |
| | | const formRef = ref(); |
| | | const tableRef = ref(); |
| | | |
| | | const { |
| | | form, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | treeData, |
| | | treeLoading, |
| | | selectedNum, |
| | | pagination, |
| | | buttonClass, |
| | | deviceDetection, |
| | | onSearch, |
| | | resetForm, |
| | | onbatchDel, |
| | | openDialog, |
| | | onTreeSelect, |
| | | handleUpdate, |
| | | handleDelete, |
| | | handleUpload, |
| | | handleReset, |
| | | handleRole, |
| | | handleSizeChange, |
| | | onSelectionCancel, |
| | | handleCurrentChange, |
| | | handleSelectionChange |
| | | } = useUser(tableRef, treeRef); |
| | | </script> |
| | | |
| | | <template> |
| | | <div :class="['flex', 'justify-between', deviceDetection() && 'flex-wrap']"> |
| | | <tree |
| | | ref="treeRef" |
| | | :class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']" |
| | | :treeData="treeData" |
| | | :treeLoading="treeLoading" |
| | | @tree-select="onTreeSelect" |
| | | /> |
| | | <div |
| | | :class="[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :inline="true" |
| | | :model="form" |
| | | class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" |
| | | > |
| | | <el-form-item label="用户名称:" prop="username"> |
| | | <el-input |
| | | v-model="form.username" |
| | | placeholder="请输入用户名称" |
| | | clearable |
| | | class="w-[180px]!" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="手机号码:" prop="phone"> |
| | | <el-input |
| | | v-model="form.phone" |
| | | placeholder="请输入手机号码" |
| | | clearable |
| | | class="w-[180px]!" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="状态:" prop="status"> |
| | | <el-select |
| | | v-model="form.status" |
| | | placeholder="请选择" |
| | | clearable |
| | | class="w-[180px]!" |
| | | > |
| | | <el-option label="已开启" value="1" /> |
| | | <el-option label="已关闭" value="0" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon('ri/search-line')" |
| | | :loading="loading" |
| | | @click="onSearch" |
| | | > |
| | | 搜索 |
| | | </el-button> |
| | | <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> |
| | | 重置 |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <PureTableBar |
| | | title="用户管理(仅演示,操作后不生效)" |
| | | :columns="columns" |
| | | @refresh="onSearch" |
| | | > |
| | | <template #buttons> |
| | | <el-button |
| | | type="primary" |
| | | :icon="useRenderIcon(AddFill)" |
| | | @click="openDialog()" |
| | | > |
| | | 新增用户 |
| | | </el-button> |
| | | </template> |
| | | <template v-slot="{ size, dynamicColumns }"> |
| | | <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"> |
| | | 取消选择 |
| | | </el-button> |
| | | </div> |
| | | <el-popconfirm title="是否确认删除?" @confirm="onbatchDel"> |
| | | <template #reference> |
| | | <el-button type="danger" text class="mr-1!"> |
| | | 批量删除 |
| | | </el-button> |
| | | </template> |
| | | </el-popconfirm> |
| | | </div> |
| | | <pure-table |
| | | ref="tableRef" |
| | | row-key="id" |
| | | adaptive |
| | | :adaptiveConfig="{ offsetBottom: 108 }" |
| | | align-whole="center" |
| | | table-layout="auto" |
| | | :loading="loading" |
| | | :size="size" |
| | | :data="dataList" |
| | | :columns="dynamicColumns" |
| | | :pagination="{ ...pagination, size }" |
| | | :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)" |
| | | > |
| | | 修改 |
| | | </el-button> |
| | | <el-popconfirm |
| | | :title="`是否确认删除用户编号为${row.id}的这条数据`" |
| | | @confirm="handleDelete(row)" |
| | | > |
| | | <template #reference> |
| | | <el-button |
| | | class="reset-margin" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Delete)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-popconfirm> |
| | | <el-dropdown> |
| | | <el-button |
| | | class="ml-3! mt-[2px]!" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(More)" |
| | | @click="handleUpdate(row)" |
| | | /> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Upload)" |
| | | @click="handleUpload(row)" |
| | | > |
| | | 上传头像 |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Password)" |
| | | @click="handleReset(row)" |
| | | > |
| | | 重置密码 |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :size="size" |
| | | :icon="useRenderIcon(Role)" |
| | | @click="handleRole(row)" |
| | | > |
| | | 分配角色 |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </template> |
| | | </pure-table> |
| | | </template> |
| | | </PureTableBar> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | :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> |
New file |
| | |
| | | <svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg> |
New file |
| | |
| | | <svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5 1.41 1.42L22 12l-7.92-7.92-1.41 1.42 5.5 5.5H4z"/></svg> |
New file |
| | |
| | | <script setup lang="ts"> |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | import { ref, computed, watch, getCurrentInstance } from "vue"; |
| | | |
| | | import Dept from "~icons/ri/git-branch-line"; |
| | | // import Reset from "~icons/ri/restart-line"; |
| | | import More2Fill from "~icons/ri/more-2-fill?width=18&height=18"; |
| | | import OfficeBuilding from "~icons/ep/office-building"; |
| | | import LocationCompany from "~icons/ep/add-location"; |
| | | import ExpandIcon from "./svg/expand.svg?component"; |
| | | import UnExpandIcon from "./svg/unexpand.svg?component"; |
| | | |
| | | interface Tree { |
| | | id: number; |
| | | name: string; |
| | | highlight?: boolean; |
| | | children?: Tree[]; |
| | | } |
| | | |
| | | defineProps({ |
| | | treeLoading: Boolean, |
| | | treeData: Array |
| | | }); |
| | | |
| | | const emit = defineEmits(["tree-select"]); |
| | | |
| | | const treeRef = ref(); |
| | | const isExpand = ref(true); |
| | | const searchValue = ref(""); |
| | | const highlightMap = ref({}); |
| | | const { proxy } = getCurrentInstance(); |
| | | const defaultProps = { |
| | | children: "children", |
| | | label: "name" |
| | | }; |
| | | const buttonClass = computed(() => { |
| | | return [ |
| | | "h-[20px]!", |
| | | "text-sm!", |
| | | "reset-margin", |
| | | "text-(--el-text-color-regular)!", |
| | | "dark:text-white!", |
| | | "dark:hover:text-primary!" |
| | | ]; |
| | | }); |
| | | |
| | | const filterNode = (value: string, data: Tree) => { |
| | | if (!value) return true; |
| | | return data.name.includes(value); |
| | | }; |
| | | |
| | | function nodeClick(value) { |
| | | const nodeId = value.$treeNodeId; |
| | | highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight |
| | | ? Object.assign({ id: nodeId }, highlightMap.value[nodeId], { |
| | | highlight: false |
| | | }) |
| | | : Object.assign({ id: nodeId }, highlightMap.value[nodeId], { |
| | | highlight: true |
| | | }); |
| | | Object.values(highlightMap.value).forEach((v: Tree) => { |
| | | if (v.id !== nodeId) { |
| | | v.highlight = false; |
| | | } |
| | | }); |
| | | emit( |
| | | "tree-select", |
| | | highlightMap.value[nodeId]?.highlight |
| | | ? Object.assign({ ...value, selected: true }) |
| | | : Object.assign({ ...value, selected: false }) |
| | | ); |
| | | } |
| | | |
| | | function toggleRowExpansionAll(status) { |
| | | isExpand.value = status; |
| | | const nodes = (proxy.$refs["treeRef"] as any).store._getAllNodes(); |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | nodes[i].expanded = status; |
| | | } |
| | | } |
| | | |
| | | /** 重置部门树状态(选中状态、搜索框值、树初始化) */ |
| | | function onTreeReset() { |
| | | highlightMap.value = {}; |
| | | searchValue.value = ""; |
| | | toggleRowExpansionAll(true); |
| | | } |
| | | |
| | | watch(searchValue, val => { |
| | | treeRef.value!.filter(val); |
| | | }); |
| | | |
| | | defineExpose({ onTreeReset }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div |
| | | v-loading="treeLoading" |
| | | class="h-full bg-bg_color overflow-hidden relative" |
| | | :style="{ minHeight: `calc(100vh - 141px)` }" |
| | | > |
| | | <div class="flex items-center h-[34px]"> |
| | | <el-input |
| | | v-model="searchValue" |
| | | class="ml-2" |
| | | size="small" |
| | | placeholder="请输入部门名称" |
| | | clearable |
| | | > |
| | | <template #suffix> |
| | | <el-icon class="el-input__icon"> |
| | | <IconifyIconOffline |
| | | v-show="searchValue.length === 0" |
| | | icon="ri/search-line" |
| | | /> |
| | | </el-icon> |
| | | </template> |
| | | </el-input> |
| | | <el-dropdown :hide-on-click="false"> |
| | | <More2Fill class="w-[28px] cursor-pointer outline-hidden" /> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)" |
| | | @click="toggleRowExpansionAll(isExpand ? false : true)" |
| | | > |
| | | {{ isExpand ? "折叠全部" : "展开全部" }} |
| | | </el-button> |
| | | </el-dropdown-item> |
| | | <!-- <el-dropdown-item> |
| | | <el-button |
| | | :class="buttonClass" |
| | | link |
| | | type="primary" |
| | | :icon="useRenderIcon(Reset)" |
| | | @click="onTreeReset" |
| | | > |
| | | 重置状态 |
| | | </el-button> |
| | | </el-dropdown-item> --> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | | <el-divider /> |
| | | <el-scrollbar height="calc(90vh - 88px)"> |
| | | <el-tree |
| | | ref="treeRef" |
| | | :data="treeData" |
| | | node-key="id" |
| | | size="small" |
| | | :props="defaultProps" |
| | | default-expand-all |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterNode" |
| | | @node-click="nodeClick" |
| | | > |
| | | <template #default="{ node, data }"> |
| | | <div |
| | | :class="[ |
| | | 'rounded-sm', |
| | | 'flex', |
| | | 'items-center', |
| | | 'select-none', |
| | | 'hover:text-primary', |
| | | searchValue.trim().length > 0 && |
| | | node.label.includes(searchValue) && |
| | | 'text-red-500', |
| | | highlightMap[node.id]?.highlight ? 'dark:text-primary' : '' |
| | | ]" |
| | | :style="{ |
| | | color: highlightMap[node.id]?.highlight |
| | | ? 'var(--el-color-primary)' |
| | | : '', |
| | | background: highlightMap[node.id]?.highlight |
| | | ? 'var(--el-color-primary-light-7)' |
| | | : 'transparent' |
| | | }" |
| | | > |
| | | <IconifyIconOffline |
| | | :icon=" |
| | | data.type === 1 |
| | | ? OfficeBuilding |
| | | : data.type === 2 |
| | | ? LocationCompany |
| | | : Dept |
| | | " |
| | | /> |
| | | <span class="w-[120px]! truncate!" :title="node.label"> |
| | | {{ node.label }} |
| | | </span> |
| | | </div> |
| | | </template> |
| | | </el-tree> |
| | | </el-scrollbar> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | :deep(.el-divider) { |
| | | margin: 0; |
| | | } |
| | | |
| | | :deep(.el-tree) { |
| | | --el-tree-node-hover-bg-color: transparent; |
| | | } |
| | | </style> |
New file |
| | |
| | | import "./reset.css"; |
| | | import dayjs from "dayjs"; |
| | | import roleForm from "../form/role.vue"; |
| | | import editForm from "../form/index.vue"; |
| | | import { zxcvbn } from "@zxcvbn-ts/core"; |
| | | import { handleTree } from "@/utils/tree"; |
| | | import { message } from "@/utils/message"; |
| | | import userAvatar from "@/assets/user.jpg"; |
| | | import { usePublicHooks } from "../../hooks"; |
| | | import { addDialog } from "@/components/ReDialog"; |
| | | import type { PaginationProps } from "@pureadmin/table"; |
| | | import ReCropperPreview from "@/components/ReCropperPreview"; |
| | | import type { FormItemProps, RoleFormItemProps } from "../utils/types"; |
| | | import { |
| | | getKeyList, |
| | | isAllEmpty, |
| | | hideTextAtIndex, |
| | | deviceDetection |
| | | } from "@pureadmin/utils"; |
| | | import { |
| | | getRoleIds, |
| | | getDeptList, |
| | | getUserList, |
| | | getAllRoleList |
| | | } from "@/api/system"; |
| | | import { |
| | | ElForm, |
| | | ElInput, |
| | | ElFormItem, |
| | | ElProgress, |
| | | ElMessageBox |
| | | } from "element-plus"; |
| | | import { |
| | | type Ref, |
| | | h, |
| | | ref, |
| | | toRaw, |
| | | watch, |
| | | computed, |
| | | reactive, |
| | | onMounted |
| | | } from "vue"; |
| | | |
| | | export function useUser(tableRef: Ref, treeRef: Ref) { |
| | | const form = reactive({ |
| | | // 左侧部门树的id |
| | | deptId: "", |
| | | username: "", |
| | | phone: "", |
| | | status: "" |
| | | }); |
| | | const formRef = ref(); |
| | | const ruleFormRef = ref(); |
| | | const dataList = ref([]); |
| | | const loading = ref(true); |
| | | // 上传头像信息 |
| | | const avatarInfo = ref(); |
| | | const switchLoadMap = ref({}); |
| | | const { switchStyle } = usePublicHooks(); |
| | | const higherDeptOptions = ref(); |
| | | const treeData = ref([]); |
| | | const treeLoading = ref(true); |
| | | const selectedNum = ref(0); |
| | | const pagination = reactive<PaginationProps>({ |
| | | total: 0, |
| | | pageSize: 10, |
| | | currentPage: 1, |
| | | background: true |
| | | }); |
| | | const columns: TableColumnList = [ |
| | | { |
| | | label: "勾选列", // 如果需要表格多选,此处label必须设置 |
| | | type: "selection", |
| | | fixed: "left", |
| | | reserveSelection: true // 数据刷新后保留选项 |
| | | }, |
| | | { |
| | | label: "用户编号", |
| | | prop: "id", |
| | | width: 90 |
| | | }, |
| | | { |
| | | label: "用户头像", |
| | | prop: "avatar", |
| | | cellRenderer: ({ row }) => ( |
| | | <el-image |
| | | fit="cover" |
| | | preview-teleported={true} |
| | | src={row.avatar || userAvatar} |
| | | preview-src-list={Array.of(row.avatar || userAvatar)} |
| | | class="w-[24px] h-[24px] rounded-full align-middle" |
| | | /> |
| | | ), |
| | | width: 90 |
| | | }, |
| | | { |
| | | label: "用户名称", |
| | | prop: "username", |
| | | minWidth: 130 |
| | | }, |
| | | { |
| | | label: "用户昵称", |
| | | prop: "nickname", |
| | | minWidth: 130 |
| | | }, |
| | | { |
| | | label: "性别", |
| | | prop: "sex", |
| | | minWidth: 90, |
| | | cellRenderer: ({ row, props }) => ( |
| | | <el-tag |
| | | size={props.size} |
| | | type={row.sex === 1 ? "danger" : null} |
| | | effect="plain" |
| | | > |
| | | {row.sex === 1 ? "女" : "男"} |
| | | </el-tag> |
| | | ) |
| | | }, |
| | | { |
| | | label: "部门", |
| | | prop: "dept.name", |
| | | minWidth: 90 |
| | | }, |
| | | { |
| | | label: "手机号码", |
| | | prop: "phone", |
| | | minWidth: 90, |
| | | formatter: ({ phone }) => hideTextAtIndex(phone, { start: 3, end: 6 }) |
| | | }, |
| | | { |
| | | label: "状态", |
| | | prop: "status", |
| | | minWidth: 90, |
| | | cellRenderer: scope => ( |
| | | <el-switch |
| | | size={scope.props.size === "small" ? "small" : "default"} |
| | | loading={switchLoadMap.value[scope.index]?.loading} |
| | | v-model={scope.row.status} |
| | | active-value={1} |
| | | inactive-value={0} |
| | | active-text="已启用" |
| | | inactive-text="已停用" |
| | | inline-prompt |
| | | style={switchStyle.value} |
| | | onChange={() => onChange(scope as any)} |
| | | /> |
| | | ) |
| | | }, |
| | | { |
| | | label: "创建时间", |
| | | minWidth: 90, |
| | | prop: "createTime", |
| | | formatter: ({ createTime }) => |
| | | dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") |
| | | }, |
| | | { |
| | | label: "操作", |
| | | fixed: "right", |
| | | width: 180, |
| | | slot: "operation" |
| | | } |
| | | ]; |
| | | const buttonClass = computed(() => { |
| | | return [ |
| | | "h-[20px]!", |
| | | "reset-margin", |
| | | "text-gray-500!", |
| | | "dark:text-white!", |
| | | "dark:hover:text-primary!" |
| | | ]; |
| | | }); |
| | | // 重置的新密码 |
| | | const pwdForm = reactive({ |
| | | newPwd: "" |
| | | }); |
| | | const pwdProgress = [ |
| | | { color: "#e74242", text: "非常弱" }, |
| | | { color: "#EFBD47", text: "弱" }, |
| | | { color: "#ffa500", text: "一般" }, |
| | | { color: "#1bbf1b", text: "强" }, |
| | | { color: "#008000", text: "非常强" } |
| | | ]; |
| | | // 当前密码强度(0-4) |
| | | const curScore = ref(); |
| | | const roleOptions = ref([]); |
| | | |
| | | function onChange({ row, index }) { |
| | | ElMessageBox.confirm( |
| | | `确认要<strong>${ |
| | | row.status === 0 ? "停用" : "启用" |
| | | }</strong><strong style='color:var(--el-color-primary)'>${ |
| | | row.username |
| | | }</strong>用户吗?`, |
| | | "系统提示", |
| | | { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | dangerouslyUseHTMLString: true, |
| | | draggable: true |
| | | } |
| | | ) |
| | | .then(() => { |
| | | switchLoadMap.value[index] = Object.assign( |
| | | {}, |
| | | switchLoadMap.value[index], |
| | | { |
| | | loading: true |
| | | } |
| | | ); |
| | | setTimeout(() => { |
| | | switchLoadMap.value[index] = Object.assign( |
| | | {}, |
| | | switchLoadMap.value[index], |
| | | { |
| | | loading: false |
| | | } |
| | | ); |
| | | message("已成功修改用户状态", { |
| | | type: "success" |
| | | }); |
| | | }, 300); |
| | | }) |
| | | .catch(() => { |
| | | row.status === 0 ? (row.status = 1) : (row.status = 0); |
| | | }); |
| | | } |
| | | |
| | | function handleUpdate(row) { |
| | | console.log(row); |
| | | } |
| | | |
| | | function handleDelete(row) { |
| | | message(`您删除了用户编号为${row.id}的这条数据`, { type: "success" }); |
| | | onSearch(); |
| | | } |
| | | |
| | | function handleSizeChange(val: number) { |
| | | console.log(`${val} items per page`); |
| | | } |
| | | |
| | | function handleCurrentChange(val: number) { |
| | | console.log(`current page: ${val}`); |
| | | } |
| | | |
| | | /** 当CheckBox选择项发生变化时会触发该事件 */ |
| | | function handleSelectionChange(val) { |
| | | selectedNum.value = val.length; |
| | | // 重置表格高度 |
| | | tableRef.value.setAdaptive(); |
| | | } |
| | | |
| | | /** 取消选择 */ |
| | | function onSelectionCancel() { |
| | | selectedNum.value = 0; |
| | | // 用于多选表格,清空用户的选择 |
| | | tableRef.value.getTableRef().clearSelection(); |
| | | } |
| | | |
| | | /** 批量删除 */ |
| | | function onbatchDel() { |
| | | // 返回当前选中的行 |
| | | const curSelected = tableRef.value.getTableRef().getSelectionRows(); |
| | | // 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除 |
| | | message(`已删除用户编号为 ${getKeyList(curSelected, "id")} 的数据`, { |
| | | type: "success" |
| | | }); |
| | | tableRef.value.getTableRef().clearSelection(); |
| | | onSearch(); |
| | | } |
| | | |
| | | async function onSearch() { |
| | | loading.value = true; |
| | | const { data } = await getUserList(toRaw(form)); |
| | | dataList.value = data.list; |
| | | pagination.total = data.total; |
| | | pagination.pageSize = data.pageSize; |
| | | pagination.currentPage = data.currentPage; |
| | | |
| | | setTimeout(() => { |
| | | loading.value = false; |
| | | }, 500); |
| | | } |
| | | |
| | | const resetForm = formEl => { |
| | | if (!formEl) return; |
| | | formEl.resetFields(); |
| | | form.deptId = ""; |
| | | treeRef.value.onTreeReset(); |
| | | onSearch(); |
| | | }; |
| | | |
| | | function onTreeSelect({ id, selected }) { |
| | | form.deptId = selected ? id : ""; |
| | | onSearch(); |
| | | } |
| | | |
| | | function formatHigherDeptOptions(treeList) { |
| | | // 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理) |
| | | if (!treeList || !treeList.length) return; |
| | | const newTreeList = []; |
| | | for (let i = 0; i < treeList.length; i++) { |
| | | treeList[i].disabled = treeList[i].status === 0 ? true : false; |
| | | formatHigherDeptOptions(treeList[i].children); |
| | | newTreeList.push(treeList[i]); |
| | | } |
| | | return newTreeList; |
| | | } |
| | | |
| | | function openDialog(title = "新增", row?: FormItemProps) { |
| | | addDialog({ |
| | | title: `${title}用户`, |
| | | props: { |
| | | formInline: { |
| | | title, |
| | | higherDeptOptions: formatHigherDeptOptions(higherDeptOptions.value), |
| | | parentId: row?.dept.id ?? 0, |
| | | nickname: row?.nickname ?? "", |
| | | username: row?.username ?? "", |
| | | password: row?.password ?? "", |
| | | phone: row?.phone ?? "", |
| | | email: row?.email ?? "", |
| | | sex: row?.sex ?? "", |
| | | status: row?.status ?? 1, |
| | | remark: row?.remark ?? "" |
| | | } |
| | | }, |
| | | width: "46%", |
| | | draggable: true, |
| | | fullscreen: deviceDetection(), |
| | | fullscreenIcon: true, |
| | | closeOnClickModal: false, |
| | | contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), |
| | | beforeSure: (done, { options }) => { |
| | | const FormRef = formRef.value.getRef(); |
| | | const curData = options.props.formInline as FormItemProps; |
| | | function chores() { |
| | | message(`您${title}了用户名称为${curData.username}的这条数据`, { |
| | | type: "success" |
| | | }); |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | } |
| | | FormRef.validate(valid => { |
| | | if (valid) { |
| | | console.log("curData", curData); |
| | | // 表单规则校验通过 |
| | | if (title === "新增") { |
| | | // 实际开发先调用新增接口,再进行下面操作 |
| | | chores(); |
| | | } else { |
| | | // 实际开发先调用修改接口,再进行下面操作 |
| | | chores(); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | const cropRef = ref(); |
| | | /** 上传头像 */ |
| | | function handleUpload(row) { |
| | | addDialog({ |
| | | title: "裁剪、上传头像", |
| | | width: "40%", |
| | | closeOnClickModal: false, |
| | | fullscreen: deviceDetection(), |
| | | contentRenderer: () => |
| | | h(ReCropperPreview, { |
| | | ref: cropRef, |
| | | imgSrc: row.avatar || userAvatar, |
| | | onCropper: info => (avatarInfo.value = info) |
| | | }), |
| | | beforeSure: done => { |
| | | console.log("裁剪后的图片信息:", avatarInfo.value); |
| | | // 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可 |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | }, |
| | | closeCallBack: () => cropRef.value.hidePopover() |
| | | }); |
| | | } |
| | | |
| | | watch( |
| | | pwdForm, |
| | | ({ newPwd }) => |
| | | (curScore.value = isAllEmpty(newPwd) ? -1 : zxcvbn(newPwd).score) |
| | | ); |
| | | |
| | | /** 重置密码 */ |
| | | function handleReset(row) { |
| | | addDialog({ |
| | | title: `重置 ${row.username} 用户的密码`, |
| | | width: "30%", |
| | | draggable: true, |
| | | closeOnClickModal: false, |
| | | fullscreen: deviceDetection(), |
| | | contentRenderer: () => ( |
| | | <> |
| | | <ElForm ref={ruleFormRef} model={pwdForm}> |
| | | <ElFormItem |
| | | prop="newPwd" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: "请输入新密码", |
| | | trigger: "blur" |
| | | } |
| | | ]} |
| | | > |
| | | <ElInput |
| | | clearable |
| | | show-password |
| | | type="password" |
| | | v-model={pwdForm.newPwd} |
| | | placeholder="请输入新密码" |
| | | /> |
| | | </ElFormItem> |
| | | </ElForm> |
| | | <div class="my-4 flex"> |
| | | {pwdProgress.map(({ color, text }, idx) => ( |
| | | <div |
| | | class="w-[19vw]" |
| | | style={{ marginLeft: idx !== 0 ? "4px" : 0 }} |
| | | > |
| | | <ElProgress |
| | | striped |
| | | striped-flow |
| | | duration={curScore.value === idx ? 6 : 0} |
| | | percentage={curScore.value >= idx ? 100 : 0} |
| | | color={color} |
| | | stroke-width={10} |
| | | show-text={false} |
| | | /> |
| | | <p |
| | | class="text-center" |
| | | style={{ color: curScore.value === idx ? color : "" }} |
| | | > |
| | | {text} |
| | | </p> |
| | | </div> |
| | | ))} |
| | | </div> |
| | | </> |
| | | ), |
| | | closeCallBack: () => (pwdForm.newPwd = ""), |
| | | beforeSure: done => { |
| | | ruleFormRef.value.validate(valid => { |
| | | if (valid) { |
| | | // 表单规则校验通过 |
| | | message(`已成功重置 ${row.username} 用户的密码`, { |
| | | type: "success" |
| | | }); |
| | | console.log(pwdForm.newPwd); |
| | | // 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可 |
| | | done(); // 关闭弹框 |
| | | onSearch(); // 刷新表格数据 |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** 分配角色 */ |
| | | async function handleRole(row) { |
| | | // 选中的角色列表 |
| | | const ids = (await getRoleIds({ userId: row.id })).data ?? []; |
| | | addDialog({ |
| | | title: `分配 ${row.username} 用户的角色`, |
| | | props: { |
| | | formInline: { |
| | | username: row?.username ?? "", |
| | | nickname: row?.nickname ?? "", |
| | | roleOptions: roleOptions.value ?? [], |
| | | ids |
| | | } |
| | | }, |
| | | width: "400px", |
| | | draggable: true, |
| | | fullscreen: deviceDetection(), |
| | | fullscreenIcon: true, |
| | | closeOnClickModal: false, |
| | | contentRenderer: () => h(roleForm), |
| | | beforeSure: (done, { options }) => { |
| | | const curData = options.props.formInline as RoleFormItemProps; |
| | | console.log("curIds", curData.ids); |
| | | // 根据实际业务使用curData.ids和row里的某些字段去调用修改角色接口即可 |
| | | done(); // 关闭弹框 |
| | | } |
| | | }); |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | treeLoading.value = true; |
| | | onSearch(); |
| | | |
| | | // 归属部门 |
| | | const { data } = await getDeptList(); |
| | | higherDeptOptions.value = handleTree(data); |
| | | treeData.value = handleTree(data); |
| | | treeLoading.value = false; |
| | | |
| | | // 角色列表 |
| | | roleOptions.value = (await getAllRoleList()).data; |
| | | }); |
| | | |
| | | return { |
| | | form, |
| | | loading, |
| | | columns, |
| | | dataList, |
| | | treeData, |
| | | treeLoading, |
| | | selectedNum, |
| | | pagination, |
| | | buttonClass, |
| | | deviceDetection, |
| | | onSearch, |
| | | resetForm, |
| | | onbatchDel, |
| | | openDialog, |
| | | onTreeSelect, |
| | | handleUpdate, |
| | | handleDelete, |
| | | handleUpload, |
| | | handleReset, |
| | | handleRole, |
| | | handleSizeChange, |
| | | onSelectionCancel, |
| | | handleCurrentChange, |
| | | handleSelectionChange |
| | | }; |
| | | } |
New file |
| | |
| | | /** 局部重置 ElProgress 的部分样式 */ |
| | | .el-progress-bar__outer, |
| | | .el-progress-bar__inner { |
| | | border-radius: 0; |
| | | } |
New file |
| | |
| | | import { reactive } from "vue"; |
| | | import type { FormRules } from "element-plus"; |
| | | import { isPhone, isEmail } from "@pureadmin/utils"; |
| | | |
| | | /** 自定义表单规则校验 */ |
| | | export const formRules = reactive(<FormRules>{ |
| | | nickname: [{ required: true, message: "用户昵称为必填项", trigger: "blur" }], |
| | | username: [{ required: true, message: "用户名称为必填项", trigger: "blur" }], |
| | | password: [{ required: true, message: "用户密码为必填项", trigger: "blur" }], |
| | | phone: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === "") { |
| | | callback(); |
| | | } else if (!isPhone(value)) { |
| | | callback(new Error("请输入正确的手机号码格式")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | // trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可 |
| | | } |
| | | ], |
| | | email: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value === "") { |
| | | callback(); |
| | | } else if (!isEmail(value)) { |
| | | callback(new Error("请输入正确的邮箱格式")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "blur" |
| | | } |
| | | ] |
| | | }); |
New file |
| | |
| | | interface FormItemProps { |
| | | id?: number; |
| | | /** 用于判断是`新增`还是`修改` */ |
| | | title: string; |
| | | higherDeptOptions: Record<string, unknown>[]; |
| | | parentId: number; |
| | | nickname: string; |
| | | username: string; |
| | | password: string; |
| | | phone: string | number; |
| | | email: string; |
| | | sex: string | number; |
| | | status: number; |
| | | dept?: { |
| | | id?: number; |
| | | name?: string; |
| | | }; |
| | | remark: string; |
| | | } |
| | | interface FormProps { |
| | | formInline: FormItemProps; |
| | | } |
| | | |
| | | interface RoleFormItemProps { |
| | | username: string; |
| | | nickname: string; |
| | | /** 角色列表 */ |
| | | roleOptions: any[]; |
| | | /** 选中的角色列表 */ |
| | | ids: Record<number, unknown>[]; |
| | | } |
| | | interface RoleFormProps { |
| | | formInline: RoleFormItemProps; |
| | | } |
| | | |
| | | export type { FormItemProps, FormProps, RoleFormItemProps, RoleFormProps }; |