192 lines
5.0 KiB
Vue
192 lines
5.0 KiB
Vue
<template>
|
|
<section class="g-issue-list-box">
|
|
<panel :blank="false">
|
|
<template #header>
|
|
<div class="g-issue-list-box-header">
|
|
<span v-if="showCheckbox" class="g-issue-list-box-checkbox">
|
|
<d-checkbox :half-checked="allCheckedHalf" v-model="allChecked" @change="onCheckChange" />
|
|
</span>
|
|
<slot name="header-left">
|
|
<div class="g-issue-list-box-header-left">
|
|
<div v-for="(item, index) in statusItems" :key="index" class="g-issue-list-box-status">
|
|
<span v-if="item.icon" class="g-issue-list-box-status-icon">
|
|
<d-icon :name="item.icon" :size="item.size || size" :style="{ color: item.color }"></d-icon>
|
|
</span>
|
|
<span class="g-issue-list-box-status-title" :style="{ color: item.color, fontWeight: item.color ? 'bold' : '' }">
|
|
{{ item.title }}
|
|
</span>
|
|
<custom-tag v-if="item.count" border-color="transparent" bg-color="#EAEAEA" color="#606060">
|
|
{{ item.count }}
|
|
</custom-tag>
|
|
</div>
|
|
</div>
|
|
</slot>
|
|
</div>
|
|
</template>
|
|
<template #headerRight>
|
|
<slot name="header-right">
|
|
右边插槽
|
|
</slot>
|
|
</template>
|
|
<slot name="content">
|
|
<div class="g-issue-list-box-content">
|
|
<div v-for="(item, index) in localData"
|
|
:key="`${item.id}_${index}`"
|
|
class="g-issue-list-box-item"
|
|
:class="{ 'is-last': index === localData.length - 1 }"
|
|
>
|
|
<span v-if="showCheckbox" class="g-issue-list-box-checkbox"><d-checkbox v-model="checkedList[index]" /></span>
|
|
<slot :data="item" :index="index">
|
|
列表内容插槽
|
|
</slot>
|
|
</div>
|
|
</div>
|
|
</slot>
|
|
</panel>
|
|
</section>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
export default {
|
|
name: 'issue-list-box'
|
|
};
|
|
</script>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch, nextTick } from 'vue';
|
|
import Panel from '@/components/Panel/index.vue';
|
|
import CustomTag from '@/components/CustomTag/index.vue';
|
|
|
|
const props = withDefaults(defineProps<{
|
|
data: Record<string, any>[];
|
|
showCheckbox?: boolean;
|
|
size?: string;
|
|
statusItems?: { icon?: string; count?: number; title: string; size?: string; color?: string; }[]
|
|
}>(), {
|
|
data: () => [],
|
|
showCheckbox: true,
|
|
size: '18px',
|
|
statusItems: () => []
|
|
});
|
|
|
|
const emits = defineEmits<{(e: 'change', data: Record<string, any>[]): void
|
|
}>();
|
|
|
|
const allChecked = ref<boolean>(false);
|
|
const allCheckedHalf = ref<boolean>(false);
|
|
const checkedList = ref<boolean[]>([]);
|
|
|
|
const localData = ref<Record<string, any>[]>([...props.data]);
|
|
|
|
watch(() => props.data, (val) => {
|
|
if (JSON.stringify(val) !== JSON.stringify(localData.value)) {
|
|
checkedList.value = [];
|
|
}
|
|
localData.value = [...val];
|
|
}, {
|
|
deep: true,
|
|
flush: 'post'
|
|
});
|
|
|
|
watch(() => allChecked.value, (val, oldValue) => {
|
|
if (val === oldValue) return false;
|
|
if (val) {
|
|
nextTick(() => {
|
|
setChecked(true);
|
|
});
|
|
} else {
|
|
nextTick(() => {
|
|
setChecked(false);
|
|
});
|
|
}
|
|
});
|
|
|
|
watch(() => checkedList.value, (val, oldValue) => {
|
|
if (JSON.parse(JSON.stringify(val)) === JSON.parse(JSON.stringify(oldValue))) return false;
|
|
handlerData(val);
|
|
if ((checkedList.value).filter(item => item).length === localData.value.length) {
|
|
allChecked.value = true;
|
|
} else if (checkedList.value.length === 0 || checkedList.value.filter(item => !item).length === localData.value.length) {
|
|
allChecked.value = false;
|
|
} else {
|
|
allCheckedHalf.value = true;
|
|
}
|
|
}, {
|
|
deep: true
|
|
});
|
|
|
|
const setChecked = (enable: boolean) => {
|
|
if (!enable) {
|
|
checkedList.value = [];
|
|
allCheckedHalf.value = false;
|
|
return false;
|
|
}
|
|
allCheckedHalf.value = false;
|
|
const arr = [];
|
|
localData.value.forEach((item, index) => {
|
|
arr[index] = true;
|
|
});
|
|
checkedList.value = [...arr];
|
|
};
|
|
|
|
const onCheckChange = (val) => {
|
|
if (val) allCheckedHalf.value = false;
|
|
};
|
|
|
|
const handlerData = (val) => {
|
|
const arr = [];
|
|
val.forEach((item, index) => {
|
|
if (item) arr.push(index);
|
|
});
|
|
const data = localData.value.filter((item, index) => {
|
|
return arr.includes(index);
|
|
});
|
|
emits('change', data);
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
$g-issue-list-box-border-color: #D3D3D3;
|
|
$g-issue-list-box-color: #606060;
|
|
.g-issue-list-box {
|
|
&-header {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
&-checkbox {
|
|
margin-right: 16px;
|
|
}
|
|
&-content {
|
|
height: auto;
|
|
}
|
|
&-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 32px 16px 16px 16px;
|
|
border-bottom: 1px solid $g-issue-list-box-border-color;
|
|
&.is-last {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
&-header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
&-status {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-right: 32px;
|
|
color: $g-issue-list-box-color;
|
|
&:last-of-type {
|
|
margin-right: 0;
|
|
}
|
|
}
|
|
&-status-icon {
|
|
margin-bottom: -4px;
|
|
}
|
|
&-status-title {
|
|
padding: 0 8px 0 16px;
|
|
}
|
|
}
|
|
</style>
|