Files
asco_admin/src/components/new-x-table/XTable.vue
T

449 lines
15 KiB
Vue
Raw Normal View History

2021-12-30 16:13:42 +03:30
<template>
<div>
<ACard ref="tblCard" class="overflow-auto">
<template #cover>
<ASpin :spinning="loading">
<div class="flex justify-between p-4">
<div class="flex items-center">
<a-input-search class="md:ml-4" placeholder="عبارت مورد نظر برای جستجو را وارد کنید"
enter-button="جستجو"
@search="onSearch" v-if="!model._noSearch"/>
<AButton @click="toggleVisibleFilter" :type="getFilterType">
<div class="flex items-center">
<feather-icon icon="FilterIcon" svg-classes="h-4 w-4 color-grey cursor-pointer"
class="ml-4"/>
<div v-if="query && query.filters.length > 0">
{{ query.filters.length }}
فیلتر
</div>
<div v-else>
فیلتر ها
</div>
</div>
</AButton>
</div>
<div class="filter-backdrop p-4" v-if="visibleDownload">
<feather-icon icon="XIcon" @click="toggleVisibleDownload"
svg-classes="h-4 w-4 color-grey cursor-pointer"
class="ml-4"/>
<AAlert >
<template #message>
لیست فعلی را می توانید در یکی از قالب های زیر دریافت کنید.
</template>
</AAlert>
<div class="grid grid-cols-3 p-8">
<div class="flex items-center justify-center flex-col cursor-pointer" @click="fetch('json')">
<img :src="jsonIcon" style="max-height: 45px" class="mb-4"/>
خروجی json
</div>
<div class="flex items-center justify-center flex-col cursor-pointer" @click="fetch('excel')">
<img :src="excelIcon" style="max-height: 45px" class="mb-4"/>
خروجی اکسل
</div>
<div class="flex items-center justify-center flex-col cursor-pointer" @click="fetch('print')">
<img :src="printIcon" style="max-height: 45px" class="mb-4"/>
خروجی پرینت
</div>
</div>
</div>
<div class="filter-backdrop p-4" v-if="activeFilter">
<div>
<div class="flex items-center mb-4">
<feather-icon icon="XIcon" @click="toggleVisibleFilter"
svg-classes="h-4 w-4 color-grey cursor-pointer"
class="ml-4"/>
<div class="font-bold">
فیلتر ها
</div>
</div>
<div v-if="query">
<div v-for="(item,index) in query.filters" :key="item.id" class="mb-4">
<XFilterRow :value="item"
@remove="removeFilter"
@input="filterChange"
:index="index"
:id="item.id"
:col-filters="getColsFilter"
:is-first="index < 1"
/>
</div>
</div>
<div class="flex items-center justify-center">
<AButton @click="addFilterRow">
افزودن ردیف جدید
</AButton>
<div class="flex-1"></div>
<AButton @click="doFilter" type="primary" v-if="query" :disabled="query.filters.length == 0">
انجام فیلتر
</AButton>
</div>
<ADivider orientation="left">مرتب سازی</ADivider>
<div class="md:flex gap-4">
<ARadioGroup v-model="sortDirection" class="ml-4" dir="ltr">
<ARadioButton value="asc">صعودی</ARadioButton>
<ARadioButton value="desc">نزولی</ARadioButton>
</ARadioGroup>
<ASelect style="min-width: 250px" placeholder="مرتب سازی" allow-clear v-model="sort">
<ASelectOption :key="index" :value="item.key" v-for="(item, index) in model.getSortFields()">
{{ item.title }}
</ASelectOption>
</ASelect>
<div class="flex-1">
</div>
<AButton @click="doFilter" type="primary">
بروزرسانی
</AButton>
</div>
</div>
</div>
<div class="flex justify-end gap-4 items-center" v-if="!model._noSettings">
<feather-icon :icon="getFullIcon" svg-classes="w-5 h-5 cursor-pointer text-primary" @click="fullScreen"/>
<ADropdown :trigger="['hover']">
<div slot="overlay" class="p-2" style="min-width: 250px">
<ACard @click.stop="noThings" :body-style="{padding:'8px'}">
ستون های جدول
<ADivider class="my-2"/>
<div class="flex flex-col items-start" style="max-height:150px;overflow:auto">
<Draggable v-model="cols">
<div v-for="item,index in cols" :key="index" class="flex items-start">
<feather-icon icon="MoreVerticalIcon" svg-classes="h-4 w-4 color-grey cursor-pointer"
class="mr-0"/>
<feather-icon icon="MoreVerticalIcon" svg-classes="h-4 w-4 color-grey cursor-pointer"
style="position: relative;left: 9px;"/>
<ACheckbox v-model="item.show">
{{ item.title }}
</ACheckbox>
</div>
</Draggable>
</div>
</ACard>
</div>
<feather-icon icon="SettingsIcon" svg-classes="w-5 h-5 cursor-pointer refresh-btn text-primary"/>
</ADropdown>
<feather-icon icon="RotateCwIcon" svg-classes="w-5 h-5 refresh-btn cursor-pointer text-primary"
@click="fetch"/>
<!-- <feather-icon icon="DownloadIcon" svg-classes="w-5 h-5 cursor-pointer text-primary"
@click="showDownload"/>-->
</div>
</div>
<a-table
@change="change"
:indentSize="5"
:pagination="false"
ref="tbl"
:locale="locale"
:columns="visibleCols"
:data-source="data"
>
<template :slot="item" slot-scope="name, record, index" v-for="item,ind in customRender">
<slot :name="item" :text="name" :record="record" :index="index"/>
</template>
<div slot="footer" slot-scope="{item}">
<div class="flex items-center">
<div class="flex-1">
<div v-if="query.total">
<span>{{ query.total }}</span>
رکورد یافت شد.
</div>
</div>
<APagination dir="ltr"
show-size-changer
:pageSizeOptions="['5','10','20']"
:page-size.sync="query.limit"
@change="changePage"
@showSizeChange="changePage"
:default-current="1" :total="query.total" v-model.sync="query.page">
<template slot="buildOptionText" slot-scope="props">
<div dir="rtl">
<span> {{ props.value }} </span>
<span>رکورد</span>
</div>
</template>
</APagination>
</div>
</div>
</a-table>
</ASpin>
</template>
</ACard>
</div>
</template>
<script>
import XTbl from "./model/XTbl";
import Draggable from 'vuedraggable'
import axios from "../../axios";
import {closeFullscreen, openFullscreen} from "@/plugins/fullscreen";
import printIcon from './assets/print.svg'
import excelIcon from './assets/excel.svg'
import jsonIcon from './assets/json.svg'
import FeatherIcon from '@/components/FeatherIcon.vue'
import XFilterRow from "./components/XFilterRow";
export default {
name: 'XTable',
components: {
XFilterRow,
FeatherIcon,
Draggable,
// DatePickerBetweenXTableFilter
},
props: {
model: {
type: XTbl.default
}
},
data() {
return {
data: [],
jsonIcon,
excelIcon,
printIcon,
lastHashStringFilter: undefined,
query: {
search: undefined,
filters: [],
total: 0,
limit: 10,
page: 1,
},
locale: {
emptyText: 'هیج رکوردی یافت نشد.'
},
loading: false,
activeFilter: false,
full: false,
sort: undefined,
sortDirection: 'asc',
colSelector: false,
visibleDownload: false
}
},
methods: {
getFilters() {
return this.query.filters
},
showDownload() {
this.visibleDownload = true;
},
doFilter() {
this.activeFilter = false;
this.fetch();
},
removeFilter(index) {
console.log("index ==> ", index);
this.$delete(this.query.filters, index)
this.$forceUpdate();
},
filterChange(value) {
console.log("value ==> " , value);
this.query.filters[value.index] = value;
},
calcHash() {
},
addFilterRow() {
if (!this.query) this.query = {};
if (!this.query.filters) this.query.filters = []
let rand = parseFloat(`${this.query.filters.length}.${Math.floor(Math.random() * 1000)}`);
this.query.filters.push({id: rand});
this.$forceUpdate();
},
toggleVisibleFilter() {
this.activeFilter = !this.activeFilter;
},
toggleVisibleDownload() {
this.visibleDownload = !this.visibleDownload;
},
// submitFilter({model, column}) {
// if (!this.query.filters) {
// this.query.filters = new Map()
// }
// const e = this.query.filters.values().filter((i) => i.field == (column.__fkey || column.dataIndex));
// if (e) {
// e.forEach((i) => {
// this.$delete(this.query.filters, this.query.filters.indexOf(i));
// });
// }
//
// const vModel = model.filter((i) => i.value)
// this.query.filters = this.query.filters.concat(vModel);
// console.log("this.query.filters ==> ", this.query.filters);
// this.fetch()
// },
hasFiltered(item) {
for (const i of this.query) {
if (item.startsWith(i.field)) {
return true;
}
}
return false;
},
change(item, filter, sorter) {
if (sorter) {
const {columnKey, order} = sorter;
this.query.order = [`${order == 'ascend' ? '-' : '+'}${columnKey}`];
}
this.fetch();
},
changePage(item) {
this.query.page = item;
this.fetch();
},
fullScreen() {
if (this.full) {
closeFullscreen()
this.full = false;
return;
}
openFullscreen(this.$refs.tblCard.$el);
this.full = true;
},
requestData(op) {
this.query.order = this.order;
this.query.filters = this.query.filters.filter((item) => item.value || ['null', 'nn'].includes(item.op) || item.value == 0);
const select = this.visibleCols.map((i) => i.key);
const titles = {};
let hash = JSON.stringify(this.query.filters);
if (this.query.search) {
hash += this.query.search;
}
if (hash != this.lastHashStringFilter) {
this.query.page = 1;
}
this.lastHashStringFilter = hash;
this.visibleCols.forEach((i) => titles[i.key] = i.title);
if (op) {
return {...this.query, op, select, titles, report_title: this.model.title};
}
return {...this.query, select, titles, report_title: this.model.title};
},
async fetch(op) {
try {
this.loading = true;
const {data} = await axios.post(this.model.url, this.requestData(op));
if (op == 'excel' || op == 'json' || op == 'print') {
window.open(data.url);
return;
}
this.data = data.rows;
this.query = data.query
console.log("this.query ==> ", this.query);
this.$emit('fetch', data)
} catch (e) {
this.$message.error('خطا در دریافت اطلاعات');
throw e
} finally {
this.loading = false;
this.visibleDownload = false;
}
},
noThings() {
},
getFilterData() {
},
onSearch(value) {
this.query.search = value;
this.fetch();
// if (value) {
// }
},
showColSelector() {
this.colSelector = !this.colSelector;
}
},
computed: {
order: {
set() {
},
get() {
if (this.sort) {
const direc = (this.sortDirection == 'asc') ? '+' : '-';
return `${direc}${this.sort}`
}
}
},
getFilterType() {
return (this.query && this.query.filters && this.query.filters.length > 0) ? 'primary' : '';
},
getFullIcon() {
return !this.full ? 'Maximize2Icon' : 'Minimize2Icon';
},
customRender() {
return this.model.cols.filter((item) => item.scopedSlots && item.scopedSlots.customRender).map((i) => i.scopedSlots.customRender)
},
filterIcons() {
return this.model.cols.filter((item) => item.scopedSlots && item.scopedSlots.filterIcon).map((i) => i.scopedSlots.filterIcon)
},
cols: {
get() {
return this.model.cols || [];
},
set(value) {
this.model.cols = value;
}
},
getColsFilter() {
return this.cols.filter(item => item.filtered)
},
visibleCols() {
console.log(" ==> " , this.cols.filter(item => item.show));
return this.cols.filter(item => item.show)
}
},
mounted() {
this.fetch();
}
}
</script>
<style scoped>
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 0.5;
transition: 0.5s ease;
}
.fade-leave {
opacity: 1;
}
.fade-leave-active {
opacity: 0;
transition: 0.5s ease;
}
.refresh-btn {
transition: all 0.15s ease;
}
.refresh-btn:hover {
transform: rotate(90deg);
}
.filter-backdrop {
position: absolute;
z-index: 1000;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: white;
}
</style>