449 lines
15 KiB
Vue
449 lines
15 KiB
Vue
<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>
|