mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-12-09 14:40:27 +08:00
Merge branch 'main' into gitee
This commit is contained in:
62
src/views/account-settings/components/accountManagement.vue
Normal file
62
src/views/account-settings/components/accountManagement.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
title: "账户密码",
|
||||
illustrate: "当前密码强度:强",
|
||||
button: "修改"
|
||||
},
|
||||
{
|
||||
title: "密保手机",
|
||||
illustrate: "已经绑定手机:158****6789",
|
||||
button: "修改"
|
||||
},
|
||||
{
|
||||
title: "密保问题",
|
||||
illustrate: "未设置密保问题,密保问题可有效保护账户安全",
|
||||
button: "修改"
|
||||
},
|
||||
{
|
||||
title: "备用邮箱",
|
||||
illustrate: "已绑定邮箱:pure***@163.com",
|
||||
button: "修改"
|
||||
}
|
||||
]);
|
||||
|
||||
function onClick(item) {
|
||||
console.log("onClick", item.title);
|
||||
message("请根据具体业务自行实现", { type: "success" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">账户管理</h3>
|
||||
<div v-for="(item, index) in list" :key="index">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p>{{ item.title }}</p>
|
||||
<el-text class="mx-1" type="info">{{ item.illustrate }}</el-text>
|
||||
</div>
|
||||
<el-button type="primary" text @click="onClick(item)">
|
||||
{{ item.button }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-divider--horizontal {
|
||||
border-top: 0.1px var(--el-border-color) var(--el-border-style);
|
||||
}
|
||||
</style>
|
||||
65
src/views/account-settings/components/preferences.vue
Normal file
65
src/views/account-settings/components/preferences.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { message } from "@/utils/message";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
title: "账户密码",
|
||||
illustrate: "其他用户的消息将以站内信的形式通知",
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
title: "系统消息",
|
||||
illustrate: "系统消息将以站内信的形式通知",
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
title: "待办任务",
|
||||
illustrate: "待办任务将以站内信的形式通知",
|
||||
checked: true
|
||||
}
|
||||
]);
|
||||
|
||||
function onChange(val, item) {
|
||||
console.log("onChange", val);
|
||||
message(`${item.title}设置成功`, { type: "success" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">偏好设置</h3>
|
||||
<div v-for="(item, index) in list" :key="index">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p>{{ item.title }}</p>
|
||||
<p class="wp-4">
|
||||
<el-text class="mx-1" type="info">
|
||||
{{ item.illustrate }}
|
||||
</el-text>
|
||||
</p>
|
||||
</div>
|
||||
<el-switch
|
||||
v-model="item.checked"
|
||||
inline-prompt
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="val => onChange(val, item)"
|
||||
/>
|
||||
</div>
|
||||
<el-divider />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-divider--horizontal {
|
||||
border-top: 0.1px var(--el-border-color) var(--el-border-style);
|
||||
}
|
||||
</style>
|
||||
187
src/views/account-settings/components/profile.vue
Normal file
187
src/views/account-settings/components/profile.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { formUpload } from "@/api/mock";
|
||||
import { message } from "@/utils/message";
|
||||
import { type UserInfo, getMine } from "@/api/user";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import ReCropperPreview from "@/components/ReCropperPreview";
|
||||
import { createFormData, deviceDetection } from "@pureadmin/utils";
|
||||
import uploadLine from "@iconify-icons/ri/upload-line";
|
||||
|
||||
const imgSrc = ref("");
|
||||
const cropperInfo = ref();
|
||||
const cropRef = ref();
|
||||
const uploadRef = ref();
|
||||
const isShow = ref(false);
|
||||
const userInfoFormRef = ref<FormInstance>();
|
||||
|
||||
const userInfos = reactive({
|
||||
avatar: "",
|
||||
nickname: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
description: ""
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules<UserInfo>>({
|
||||
nickname: [{ required: true, message: "昵称必填", trigger: "blur" }]
|
||||
});
|
||||
|
||||
function queryEmail(queryString, callback) {
|
||||
const emailList = [
|
||||
{ value: "@qq.com" },
|
||||
{ value: "@126.com" },
|
||||
{ value: "@163.com" }
|
||||
];
|
||||
let results = [];
|
||||
let queryList = [];
|
||||
emailList.map(item =>
|
||||
queryList.push({ value: queryString.split("@")[0] + item.value })
|
||||
);
|
||||
results = queryString
|
||||
? queryList.filter(
|
||||
item =>
|
||||
item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||||
)
|
||||
: queryList;
|
||||
callback(results);
|
||||
}
|
||||
|
||||
const onChange = uploadFile => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
imgSrc.value = e.target.result as string;
|
||||
isShow.value = true;
|
||||
};
|
||||
reader.readAsDataURL(uploadFile.raw);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
cropRef.value.hidePopover();
|
||||
uploadRef.value.clearFiles();
|
||||
isShow.value = false;
|
||||
};
|
||||
|
||||
const onCropper = info => (cropperInfo.value = info);
|
||||
|
||||
const handleSubmitImage = () => {
|
||||
const formData = createFormData({
|
||||
files: new File([cropperInfo.value], "avatar")
|
||||
});
|
||||
formUpload(formData)
|
||||
.then(({ success, data }) => {
|
||||
if (success) {
|
||||
message("更新头像成功", { type: "success" });
|
||||
handleClose();
|
||||
} else {
|
||||
message("更新头像失败");
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
message(`提交异常 ${error}`, { type: "error" });
|
||||
});
|
||||
};
|
||||
|
||||
// 更新信息
|
||||
const onSubmit = async (formEl: FormInstance) => {
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
console.log(userInfos);
|
||||
message("更新信息成功", { type: "success" });
|
||||
} else {
|
||||
console.log("error submit!", fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getMine().then(res => {
|
||||
Object.assign(userInfos, res.data);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">个人信息</h3>
|
||||
<el-form
|
||||
ref="userInfoFormRef"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
:model="userInfos"
|
||||
>
|
||||
<el-form-item label="头像">
|
||||
<el-avatar :size="80" :src="userInfos.avatar" />
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
accept="image/*"
|
||||
action="#"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="onChange"
|
||||
>
|
||||
<el-button plain class="ml-4">
|
||||
<IconifyIconOffline :icon="uploadLine" />
|
||||
<span class="ml-2">更新头像</span>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="userInfos.nickname" placeholder="请输入昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-autocomplete
|
||||
v-model="userInfos.email"
|
||||
:fetch-suggestions="queryEmail"
|
||||
:trigger-on-focus="false"
|
||||
placeholder="请输入邮箱"
|
||||
clearable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input
|
||||
v-model="userInfos.phone"
|
||||
placeholder="请输入联系电话"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介">
|
||||
<el-input
|
||||
v-model="userInfos.description"
|
||||
placeholder="请输入简介"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 6, maxRows: 8 }"
|
||||
maxlength="56"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(userInfoFormRef)">
|
||||
更新信息
|
||||
</el-button>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
v-model="isShow"
|
||||
width="40%"
|
||||
title="编辑头像"
|
||||
destroy-on-close
|
||||
:closeOnClickModal="false"
|
||||
:before-close="handleClose"
|
||||
:fullscreen="deviceDetection()"
|
||||
>
|
||||
<ReCropperPreview ref="cropRef" :imgSrc="imgSrc" @cropper="onCropper" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button bg text @click="handleClose">取消</el-button>
|
||||
<el-button bg text type="primary" @click="handleSubmitImage">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
87
src/views/account-settings/components/securityLog.vue
Normal file
87
src/views/account-settings/components/securityLog.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs";
|
||||
import { getMineLogs } from "@/api/user";
|
||||
import { reactive, ref, onMounted } from "vue";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
|
||||
const loading = ref(true);
|
||||
const dataList = ref([]);
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true,
|
||||
layout: "prev, pager, next"
|
||||
});
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "详情",
|
||||
prop: "summary",
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
label: "IP 地址",
|
||||
prop: "ip",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "地点",
|
||||
prop: "address",
|
||||
minWidth: 140
|
||||
},
|
||||
{
|
||||
label: "操作系统",
|
||||
prop: "system",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "浏览器类型",
|
||||
prop: "browser",
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
label: "时间",
|
||||
prop: "operatingTime",
|
||||
minWidth: 180,
|
||||
formatter: ({ operatingTime }) =>
|
||||
dayjs(operatingTime).format("YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
];
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getMineLogs();
|
||||
dataList.value = data.list;
|
||||
pagination.total = data.total;
|
||||
pagination.pageSize = data.pageSize;
|
||||
pagination.currentPage = data.currentPage;
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'min-w-[180px]',
|
||||
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
|
||||
]"
|
||||
>
|
||||
<h3 class="my-8">安全日志</h3>
|
||||
<pure-table
|
||||
row-key="id"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
183
src/views/account-settings/index.vue
Normal file
183
src/views/account-settings/index.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { getMine } from "@/api/user";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref, onBeforeMount } from "vue";
|
||||
import { ReText } from "@/components/ReText";
|
||||
import Profile from "./components/profile.vue";
|
||||
import Preferences from "./components/preferences.vue";
|
||||
import SecurityLog from "./components/securityLog.vue";
|
||||
import { useGlobal, deviceDetection } from "@pureadmin/utils";
|
||||
import AccountManagement from "./components/accountManagement.vue";
|
||||
import TopCollapse from "@/layout/components/sidebar/topCollapse.vue";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
|
||||
import leftLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import ProfileIcon from "@iconify-icons/ri/user-3-line";
|
||||
import PreferencesIcon from "@iconify-icons/ri/settings-3-line";
|
||||
import SecurityLogIcon from "@iconify-icons/ri/window-line";
|
||||
import AccountManagementIcon from "@iconify-icons/ri/profile-line";
|
||||
|
||||
defineOptions({
|
||||
name: "AccountSettings"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const isOpen = ref(deviceDetection() ? false : true);
|
||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||
onBeforeMount(() => {
|
||||
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
|
||||
});
|
||||
|
||||
const userInfo = ref({
|
||||
avatar: "",
|
||||
username: "",
|
||||
nickname: ""
|
||||
});
|
||||
const panes = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "个人信息",
|
||||
icon: ProfileIcon,
|
||||
component: Profile
|
||||
},
|
||||
{
|
||||
key: "preferences",
|
||||
label: "偏好设置",
|
||||
icon: PreferencesIcon,
|
||||
component: Preferences
|
||||
},
|
||||
{
|
||||
key: "securityLog",
|
||||
label: "安全日志",
|
||||
icon: SecurityLogIcon,
|
||||
component: SecurityLog
|
||||
},
|
||||
{
|
||||
key: "accountManagement",
|
||||
label: "账户管理",
|
||||
icon: AccountManagementIcon,
|
||||
component: AccountManagement
|
||||
}
|
||||
];
|
||||
const witchPane = ref("profile");
|
||||
|
||||
getMine().then(res => {
|
||||
userInfo.value = res.data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container class="h-full">
|
||||
<el-aside
|
||||
v-if="isOpen"
|
||||
class="settings-sidebar px-2 dark:!bg-[var(--el-bg-color)]"
|
||||
:width="deviceDetection() ? '180px' : '240px'"
|
||||
>
|
||||
<el-menu :default-active="witchPane" class="settings-menu">
|
||||
<el-menu-item
|
||||
class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]"
|
||||
@click="router.go(-1)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<IconifyIconOffline :icon="leftLine" />
|
||||
<span class="ml-2">返回</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
<div class="flex items-center ml-8 mt-4 mb-4">
|
||||
<el-avatar :size="48" :src="userInfo.avatar" />
|
||||
<div class="ml-4 flex flex-col max-w-[130px]">
|
||||
<ReText class="font-bold !self-baseline">
|
||||
{{ userInfo.nickname }}
|
||||
</ReText>
|
||||
<ReText class="!self-baseline" type="info">
|
||||
{{ userInfo.username }}
|
||||
</ReText>
|
||||
</div>
|
||||
</div>
|
||||
<el-menu-item
|
||||
v-for="item in panes"
|
||||
:key="item.key"
|
||||
:index="item.key"
|
||||
@click="
|
||||
() => {
|
||||
witchPane = item.key;
|
||||
if (deviceDetection()) {
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="flex items-center z-10">
|
||||
<el-icon><IconifyIconOffline :icon="item.icon" /></el-icon>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<TopCollapse
|
||||
v-if="deviceDetection()"
|
||||
class="px-0"
|
||||
:is-active="isOpen"
|
||||
@toggleClick="isOpen = !isOpen"
|
||||
/>
|
||||
<component
|
||||
:is="panes.find(item => item.key === witchPane).component"
|
||||
:class="[!deviceDetection() && 'ml-[120px]']"
|
||||
/>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings-sidebar {
|
||||
overflow: hidden;
|
||||
background: $menuBg;
|
||||
border-right: 1px solid var(--pure-border-color);
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
::v-deep(.el-menu-item) {
|
||||
height: 48px !important;
|
||||
color: $menuText !important;
|
||||
background-color: transparent !important;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: #fff !important;
|
||||
|
||||
&:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
inset: 0 8px;
|
||||
margin: 4px 0;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body[layout] {
|
||||
.el-menu--vertical .is-active {
|
||||
color: #fff !important;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,7 @@
|
||||
| `frameLoading` | 加载动画(内嵌的`iframe`页面是否开启首次加载动画) |
|
||||
| `keepAlive` | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) |
|
||||
| `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) |
|
||||
| `fixedTag` | 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) |
|
||||
| `showLink` | 菜单(是否显示该菜单) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/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)) |
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import ReAnimateSelector from "@/components/ReAnimateSelector";
|
||||
import {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
fixedTagOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
@@ -26,7 +27,7 @@ const props = withDefaults(defineProps<FormProps>(), {
|
||||
path: "",
|
||||
component: "",
|
||||
rank: 99,
|
||||
redirect: " ",
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
@@ -37,6 +38,7 @@ const props = withDefaults(defineProps<FormProps>(), {
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
})
|
||||
@@ -258,33 +260,6 @@ defineExpose({ getRef });
|
||||
</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 !== 3"
|
||||
:value="12"
|
||||
@@ -305,7 +280,7 @@ defineExpose({ getRef });
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="8"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
@@ -321,6 +296,47 @@ defineExpose({ getRef });
|
||||
/>
|
||||
</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>
|
||||
|
||||
@@ -61,7 +61,7 @@ const {
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
title="菜单管理(初版,持续完善中)"
|
||||
title="菜单管理(仅演示,操作后不生效)"
|
||||
:columns="columns"
|
||||
:isExpandAll="false"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
|
||||
@@ -32,6 +32,19 @@ const showLinkOptions: Array<OptionsType> = [
|
||||
}
|
||||
];
|
||||
|
||||
const fixedTagOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "固定",
|
||||
tip: "当前菜单名称固定显示在标签页且不可关闭",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "不固定",
|
||||
tip: "当前菜单名称不固定显示在标签页且可关闭",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const keepAliveOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "缓存",
|
||||
@@ -87,6 +100,7 @@ const frameLoadingOptions: Array<OptionsType> = [
|
||||
export {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
fixedTagOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
|
||||
@@ -155,6 +155,7 @@ export function useMenu() {
|
||||
frameLoading: row?.frameLoading ?? true,
|
||||
keepAlive: row?.keepAlive ?? false,
|
||||
hiddenTag: row?.hiddenTag ?? false,
|
||||
fixedTag: row?.fixedTag ?? false,
|
||||
showLink: row?.showLink ?? true,
|
||||
showParent: row?.showParent ?? false
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ interface FormItemProps {
|
||||
frameLoading: boolean;
|
||||
keepAlive: boolean;
|
||||
hiddenTag: boolean;
|
||||
fixedTag: boolean;
|
||||
showLink: boolean;
|
||||
showParent: boolean;
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import { ref } from "vue";
|
||||
import ReCropper from "@/components/ReCropper";
|
||||
import { formatBytes } from "@pureadmin/utils";
|
||||
|
||||
const props = defineProps({
|
||||
imgSrc: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(["cropper"]);
|
||||
|
||||
const infos = ref();
|
||||
const popoverRef = ref();
|
||||
const refCropper = ref();
|
||||
const showPopover = ref(false);
|
||||
const cropperImg = ref<string>("");
|
||||
|
||||
function onCropper({ base64, blob, info }) {
|
||||
infos.value = info;
|
||||
cropperImg.value = base64;
|
||||
emit("cropper", { base64, blob, info });
|
||||
}
|
||||
|
||||
function hidePopover() {
|
||||
popoverRef.value.hide();
|
||||
}
|
||||
|
||||
defineExpose({ hidePopover });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="!showPopover" element-loading-background="transparent">
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
:visible="showPopover"
|
||||
placement="right"
|
||||
width="18vw"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="w-[18vw]">
|
||||
<ReCropper
|
||||
ref="refCropper"
|
||||
:src="props.imgSrc"
|
||||
circled
|
||||
@cropper="onCropper"
|
||||
@readied="showPopover = true"
|
||||
/>
|
||||
<p v-show="showPopover" class="mt-1 text-center">
|
||||
温馨提示:右键上方裁剪区可开启功能菜单
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-wrap justify-center items-center text-center">
|
||||
<el-image
|
||||
v-if="cropperImg"
|
||||
:src="cropperImg"
|
||||
:preview-src-list="Array.of(cropperImg)"
|
||||
fit="cover"
|
||||
/>
|
||||
<div v-if="infos" class="mt-1">
|
||||
<p>
|
||||
图像大小:{{ parseInt(infos.width) }} ×
|
||||
{{ parseInt(infos.height) }}像素
|
||||
</p>
|
||||
<p>
|
||||
文件大小:{{ formatBytes(infos.size) }}({{ infos.size }} 字节)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
@@ -5,11 +5,11 @@ import editForm from "../form/index.vue";
|
||||
import { zxcvbn } from "@zxcvbn-ts/core";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import croppingUpload from "../upload.vue";
|
||||
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,
|
||||
@@ -365,11 +365,10 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
addDialog({
|
||||
title: "裁剪、上传头像",
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
fullscreen: deviceDetection(),
|
||||
contentRenderer: () =>
|
||||
h(croppingUpload, {
|
||||
h(ReCropperPreview, {
|
||||
ref: cropRef,
|
||||
imgSrc: row.avatar || userAvatar,
|
||||
onCropper: info => (avatarInfo.value = info)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { message } from "@/utils/message";
|
||||
import { tableData } from "../data";
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
// 如果您不习惯tsx写法,可以传slot,然后在template里写
|
||||
// 需是hooks写法(函数中有return),避免失去响应性
|
||||
export function useColumns() {
|
||||
const search = ref("");
|
||||
@@ -26,8 +25,9 @@ export function useColumns() {
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "日期",
|
||||
prop: "date"
|
||||
prop: "date",
|
||||
// 自定义表头,slot用法 #nameHeader="{ column, $index }"
|
||||
headerSlot: "nameHeader"
|
||||
},
|
||||
{
|
||||
label: "姓名",
|
||||
@@ -39,7 +39,7 @@ export function useColumns() {
|
||||
},
|
||||
{
|
||||
align: "right",
|
||||
// 自定义表头
|
||||
// 自定义表头,tsx用法
|
||||
headerRenderer: () => (
|
||||
<el-input
|
||||
v-model={search.value}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { useColumns } from "./columns";
|
||||
import Calendar from "@iconify-icons/ri/calendar-2-line";
|
||||
|
||||
const { columns, filterTableData } = useColumns();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pure-table :data="filterTableData" :columns="columns" />
|
||||
<pure-table :data="filterTableData" :columns="columns">
|
||||
<template #nameHeader>
|
||||
<span class="flex items-center">
|
||||
<IconifyIconOffline :icon="Calendar" />
|
||||
日期
|
||||
</span>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user