2023-06-01 10:29:25 +08:00

545 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="cl-view-group" :class="[isExpand ? 'is-expand' : 'is-collapse']">
<div class="cl-view-group__wrap">
<!-- 左侧 -->
<div class="cl-view-group__left">
<slot name="left">
<div class="scope">
<div class="head">
<span>{{ config.label }}</span>
<el-tooltip content="刷新">
<el-icon @click="refresh()">
<icon-refresh />
</el-icon>
</el-tooltip>
<el-tooltip content="添加">
<el-icon
@click="edit()"
v-permission="config.service.permission.add"
>
<plus />
</el-icon>
</el-tooltip>
</div>
<div class="list" v-loading="loading">
<el-scrollbar>
<ul
v-infinite-scroll="onMore"
:infinite-scroll-immediate="false"
:infinite-scroll-disabled="loaded"
>
<li
v-for="(item, index) in list"
:key="index"
@click="select(item)"
@contextmenu="
(e) => {
onContextMenu(e, item);
}
"
>
<slot
name="item"
:item="item"
:selected="selected"
:index="index"
>
<div
class="item"
:class="{
'is-active': selected?.id == item.id
}"
>
<slot
name="item-name"
:item="item"
:selected="selected"
:index="index"
>
<span>{{ item.name }}</span>
</slot>
<el-icon
class="arrow-right"
v-show="selected?.id == item.id"
>
<arrow-right-bold />
</el-icon>
</div>
</slot>
</li>
<el-empty v-if="list.length == 0" :image-size="80" />
</ul>
</el-scrollbar>
</div>
</div>
</slot>
<!-- 收起按钮 -->
<div class="collapse-btn" @click="expand(false)" v-if="browser.isMini">
<el-icon>
<arrow-right />
</el-icon>
</div>
</div>
<!-- 右侧 -->
<div class="cl-view-group__right">
<div class="head">
<div class="icon" @click="expand()">
<el-icon v-if="isExpand"><arrow-left /></el-icon>
<el-icon v-else><arrow-right /></el-icon>
</div>
<slot name="title" :selected="selected">
<span class="title">
{{ config.title }}{{ selected?.name || "未选择" }}
</span>
</slot>
</div>
<div class="content" v-if="selected">
<slot name="right"></slot>
</div>
<el-empty :image-size="80" v-else />
</div>
</div>
<cl-form ref="Form" />
</div>
</template>
<script lang="ts" name="cl-view-group" setup>
import { inject, nextTick, onMounted, reactive, ref, useSlots } from "vue";
import {
ArrowLeft,
ArrowRight,
ArrowRightBold,
Refresh as IconRefresh,
Plus
} from "@element-plus/icons-vue";
import { useBrowser } from "/@/cool";
import { ContextMenu, useForm, setFocus } from "@cool-vue/crud";
import { ElMessage, ElMessageBox } from "element-plus";
import { ClViewGroup } from "./hook";
import { isEmpty } from "lodash-es";
const { browser, onScreenChange } = useBrowser();
const slots = useSlots();
const Form = useForm();
// 配置
const config = reactive(
Object.assign(
{
label: "组",
title: "列表",
leftWidth: "300px",
service: {}
},
inject("useViewGroup__options")
)
) as ClViewGroup.Options;
// 左侧内容是否自定义
const isCustom = !!slots.left;
if (isEmpty(config.service) && !isCustom) {
console.error("[cl-view-group] 参数 service 不能为空");
}
// 加载中
const loading = ref(false);
// 列表
const list = ref<ClViewGroup.Item[]>([]);
// 是否展开
const isExpand = ref(true);
// 选中值
const selected = ref<ClViewGroup.Item>();
// 收起、展开
function expand(value?: boolean) {
isExpand.value = value === undefined ? !isExpand.value : value;
}
// 设置选中值
function select(data?: ClViewGroup.Item) {
if (!data) {
data = list.value[0];
}
selected.value = data;
nextTick(() => {
if (data) {
if (browser.isMini) {
expand(false);
}
if (config.onSelect) {
config.onSelect(data);
}
}
});
}
// 编辑
function edit(item?: ClViewGroup.Item) {
Form.value?.open(
{
title: (item ? "编辑" : "添加") + config.label,
form: {
...item
},
on: {
submit(data, { close, done }) {
config.service[item ? "update" : "add"](data)
.then(() => {
ElMessage.success("保存成功");
if (item) {
Object.assign(item, data);
} else {
refresh();
}
close();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
}
},
...config.onEdit?.(item)
},
[setFocus()]
);
}
// 删除
function remove(item: ClViewGroup.Item) {
ElMessageBox.confirm("此操作将会删除选择的数据,是否继续?", "提示", {
type: "warning"
})
.then(() => {
config.service
.delete({
ids: [item.id]
})
.then(async () => {
ElMessage.success("删除成功");
// 刷新列表
await refresh();
// 删除当前
if (selected.value?.id == item.id) {
select();
}
})
.catch((err) => {
ElMessage.error(err.message);
});
})
.catch(() => null);
}
// 请求参数
const reqParams = {
order: "createTime",
sort: "asc",
page: 1,
size: 20
};
// 是否加载完
const loaded = ref(false);
// 刷新
async function refresh(params?: any) {
Object.assign(reqParams, params);
loading.value = true;
await config.service
.page(reqParams)
.then((res) => {
const arr = config.onData?.(res.list) || res.list;
if (reqParams.page == 1) {
list.value = arr;
} else {
list.value.push(...arr);
}
if (!selected.value) {
select(list.value[0]);
}
loaded.value = res.pagination.total <= list.value.length;
})
.catch((err) => {
ElMessage.error(err.message);
});
loading.value = false;
}
// 加载更多
function onMore() {
refresh({
page: reqParams.page + 1
});
}
// 右键菜单
function onContextMenu(e: any, item: ClViewGroup.Item) {
ContextMenu.open(e, {
hover: {
target: "item"
},
list: [
{
label: "编辑",
hidden: !config.service._permission.update,
callback(done) {
done();
edit(item);
}
},
{
label: "删除",
hidden: !config.service._permission.delete,
callback(done) {
done();
remove(item);
}
}
],
...(config.onContextMenu && config.onContextMenu(item))
});
}
// 监听屏幕变化
onScreenChange(() => {
expand(!browser.isMini);
});
defineExpose({
selected,
isExpand,
expand,
select,
browser,
edit,
remove
});
onMounted(() => {
if (!isCustom) {
refresh();
}
});
</script>
<style lang="scss" scoped>
.cl-view-group {
height: 100%;
width: 100%;
$left-width: v-bind("config.leftWidth");
$bg: var(--el-bg-color);
&__wrap {
display: flex;
height: 100%;
width: 100%;
position: relative;
}
&__left {
position: relative;
height: 100%;
width: $left-width;
background-color: $bg;
.collapse-btn {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 20px;
bottom: 30px;
height: 40px;
width: 40px;
border-radius: 100%;
background-color: var(--color-primary);
box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.3);
.el-icon {
color: #fff;
font-size: 24px;
}
}
.scope {
height: 100%;
width: 100%;
box-sizing: border-box;
white-space: nowrap;
.head {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
font-size: 14px;
padding: 0 10px;
& > span {
&:nth-child(1) {
flex: 1;
}
}
.el-icon {
padding: 5px;
font-size: 18px;
margin-left: 5px;
cursor: pointer;
&:hover {
background-color: var(--el-fill-color-lighter);
}
}
}
.list {
height: calc(100% - 40px);
box-sizing: border-box;
ul {
height: 100%;
li {
.item {
display: flex;
align-items: center;
list-style: none;
box-sizing: border-box;
padding: 10px 35px 10px 10px;
margin: 0 10px;
cursor: pointer;
font-size: 13px;
margin-bottom: 10px;
border-radius: 3px;
color: #666;
position: relative;
background-color: #f7f7f7;
.arrow-right {
position: absolute;
right: 10px !important;
}
&.is-active {
background-color: var(--color-primary);
color: #fff;
}
&:hover {
opacity: 0.8;
}
}
}
&::after {
display: block;
content: "";
height: 1px;
}
}
}
}
}
&__right {
display: flex;
flex-direction: column;
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 100%;
transition: width 0.3s;
background-color: $bg;
.head {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
position: relative;
font-size: 14px;
.title {
white-space: nowrap;
overflow: hidden;
}
.icon {
display: flex;
align-items: center;
position: absolute;
left: 0;
top: 0;
font-size: 18px;
cursor: pointer;
height: 100%;
width: 80px;
padding-left: 10px;
}
}
.content {
height: calc(100% - 40px);
}
}
&.is-expand {
.cl-view-group__right {
width: calc(100% - $left-width);
border-left: 1px solid var(--el-border-color);
}
}
@media only screen and (max-width: 768px) {
.cl-view-group__left {
overflow: hidden;
transition: width 0.2s;
width: 0;
z-index: 9;
}
.cl-view-group__right {
width: 100% !important;
}
&.is-expand {
.cl-view-group__left {
width: 100%;
}
}
}
}
</style>