Init project

This commit is contained in:
alirezakarari
2021-01-09 20:53:13 +03:30
commit 8d06c89a89
1005 changed files with 105625 additions and 0 deletions
@@ -0,0 +1,24 @@
<!-- =========================================================================================
File Name: ChangeTimeDurationDropdown.vue
Description: Change duration dropdown component
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<!-- :class="data.class" -->
<template functional>
<vs-dropdown vs-trigger-click class="cursor-pointer" :class="data.staticClass">
<small class="flex cursor-pointer">
<span>Last 7 days</span>
<feather-icon icon="ChevronDownIcon" svgClasses="h-4 w-4" class="ml-1" />
</small>
<vs-dropdown-menu class="w-32">
<vs-dropdown-item @click="listeners.timeDurationChanged ? { timeDurationChanged: listeners.timeDurationChanged('last-28-days') } : null">Last 28 days</vs-dropdown-item>
<vs-dropdown-item @click="listeners.timeDurationChanged ? { timeDurationChanged: listeners.timeDurationChanged('last-month') } : null">Last Month</vs-dropdown-item>
<vs-dropdown-item @click="listeners.timeDurationChanged ? { timeDurationChanged: listeners.timeDurationChanged('last-year') } : null">Last Year</vs-dropdown-item>
</vs-dropdown-menu>
</vs-dropdown>
</template>
+38
View File
@@ -0,0 +1,38 @@
<!-- =========================================================================================
File Name: FeatherIcon.vue
Description: Feather icon component. Creates feather icon based on 'icon' prop
Component Name: FeatherIcon
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<script>
import * as icons from 'vue-feather-icons'
export default {
functional:true,
name: 'feather-icon',
props: {
icon: { required: true },
svgClasses: { type: [String, Object, Array], default: '' },
badge: {}
},
render (h, {props, data}) {
// Add feather classes
data.staticClass ? data.staticClass = `${data.staticClass} feather-icon select-none relative` : data.staticClass = 'feather-icon select-none relative'
const svg = h(icons[props.icon], {class: props.svgClasses})
const badgeEl = h('span', {class: 'feather-icon-badge bg-primary text-white h-5 w-5 absolute rounded-full text-xs flex items-center justify-center', style: 'top: -7px; right: -5px'}, [props.badge])
const children = [svg]
if (props.badge) children.push(badgeEl)
return h('span', data, children)
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/featherIcon.scss"
</style>
+17
View File
@@ -0,0 +1,17 @@
<template>
<div>
<a-checkbox @change="activeIcon">ارسال از طریق چت</a-checkbox>
<a-checkbox @change="activeIcon">ارسال از طریق نوتیفیکیشن</a-checkbox>
<a-checkbox @change="activeIcon">ارسال از طریق پیامک</a-checkbox>
</div>
</template>
<script>
export default {
name: "SendMsg"
}
</script>
<style scoped>
</style>
+123
View File
@@ -0,0 +1,123 @@
<!-- =========================================================================================
File Name: VxTour.vue
Description: Tour Component
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<template>
<v-tour name="vuexyTour" :steps="steps">
<template slot-scope="tour">
<transition name="fade">
<v-step
v-if="tour.currentStep === index"
v-for="(step, index) of tour.steps"
:key="index"
:step="step"
:previous-step="tour.previousStep"
:next-step="tour.nextStep"
:stop="tour.stop"
:is-first="tour.isFirst"
:is-last="tour.isLast"
:labels="tour.labels">
<div slot="actions" class="flex justify-center">
<vs-button
size="small"
class="mr-3"
@click="tour.stop"
icon-pack="feather"
icon="icon-x"
icon-after
color="#fff"
type="border"
v-if="tour.currentStep != tour.steps.length - 1">
Skip
</vs-button>
<vs-button
size="small"
@click="tour.previousStep"
icon-pack="feather"
icon="icon-chevrons-left"
color="#fff"
type="border"
class="mr-3"
v-if="tour.currentStep">
Previous
</vs-button>
<vs-button
size="small"
@click="tour.nextStep"
icon-pack="feather"
icon="icon-chevrons-right"
icon-after
color="#fff"
type="border"
class="btn-tour-next"
v-if="tour.currentStep != tour.steps.length - 1">
Next
</vs-button>
<vs-button
size="small"
@click="tour.stop"
icon-pack="feather"
icon="icon-check-circle"
icon-after
color="#fff"
type="border"
class="btn-tour-finish"
v-if="tour.currentStep == tour.steps.length - 1">
Finish
</vs-button>
</div>
</v-step>
</transition>
</template>
</v-tour>
</template>
<script>
export default{
name: 'vx-tour',
props: {
steps: {
required: true,
type: Array
}
},
watch: {
'$route.path' () {
this.$tours['vuexyTour'].stop()
}
},
mounted () {
this.$tours['vuexyTour'].start()
}
}
</script>
<style lang="scss">
.v-tour {
.v-step {
z-index: 55000;
background-color: rgba(var(--vs-primary),1);
border-radius: .5rem;
padding: 1.5rem;
filter: drop-shadow(0 0 7px rgba(0,0,0,.5));
.v-step__arrow {
border-color: rgba(var(--vs-primary),1);
}
.vs-button-border:not(.btn-tour-next):not(.btn-tour-finish) {
border-color: rgba(255, 255, 255, .5) !important;
}
}
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="excel-import">
<input type="file" ref="fileInput" class="hidden" accept=".xlsx, .xls" @change="handleClick">
<div
@click="$refs.fileInput.click()"
@drop="handleDrop"
@dragover="handleDragover"
@dragenter="handleDragover"
class="px-8 py-16 cursor-pointer text-center border-2 border-dashed d-theme-border-grey-light d-theme-dark-bg text-xl">
<feather-icon icon="UploadCloudIcon" svgClasses="h-16 w-16 stroke-current text-grey" class="block" />
<span>Drop Excel File or </span>
<span class="font-medium text-primary" @click.stop="$refs.fileInput.click()">Browse</span>
<!-- <vs-button type="border" @click.stop="$refs.fileInput.click()">Browse</vs-button> -->
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
props: {
onSuccess: {
type: Function,
required: true
}
},
data () {
return {
excelData: {
header: null,
results: null,
meta: null
}
}
},
methods: {
generateData ({ header, results, meta }) {
this.excelData.header = header
this.excelData.results = results
this.excelData.meta = meta
if (this.onSuccess) this.onSuccess(this.excelData)
},
getHeaderRow (sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C = undefined
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = `UNKNOWN ${ C}` // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
handleDragover (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleDrop (e) {
e.stopPropagation()
e.preventDefault()
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$vs.notify({
title: 'Login Attempt',
text: 'Only support uploading one file!',
iconPack: 'feather',
icon: 'icon-alert-circle',
color: 'warning'
})
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$vs.notify({
title: 'Login Attempt',
text: 'Only supports upload .xlsx, .xls, .csv suffix files',
iconPack: 'feather',
icon: 'icon-alert-circle',
color: 'warning'
})
return false
}
this.uploadFile(rawFile)
},
readerData (rawFile) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
const meta = { sheetName: firstSheetName }
this.generateData({ header, results, meta })
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
handleClick (e) {
const files = e.target.files
const rawFile = files[0]
if (!rawFile) return
this.uploadFile(rawFile)
},
isExcel (file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
},
uploadFile (file) {
this.$refs['fileInput'].value = null // fix can't select the same excel
this.readerData(file)
}
}
}
</script>
@@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "FormCreator"
}
</script>
<style scoped>
</style>
@@ -0,0 +1,73 @@
<template>
<a-select
show-search
:value="value"
placeholder="جستجو"
:default-active-first-option="false"
:show-arrow="false"
:v-decorator.sync="vDecorator"
:filter-option="false"
:not-found-content="null"
@search="handleSearch"
@change="handleChange"
@select="select"
>
<a-select-option v-for="d in data" :key="d.id">
<div class="flex">
<div class="flex-1">{{ d.title }}</div>
<div class="ml-2">
<ATag color="blue">{{d.product_code}}</ATag>
</div>
</div>
</a-select-option>
</a-select>
</template>
<script>
let timeout;
let currentValue;
import axios from '@/axios';
function fetch(value, callback ,url) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
currentValue = value;
async function query() {
let res = await axios.get(url, {params: {text_search: value}});
callback(res.data.rows);
}
timeout = setTimeout(query, 300);
}
export default {
name: 'QuerySelect',
props: [
'vDecorator',
'url'
],
data() {
return {
data: [],
value: undefined,
};
},
methods: {
select(v){
console.log("v ==> " , v);
},
handleSearch(value) {
fetch(value, data => (this.data = data) , this.url);
},
handleChange(value) {
this.value = value;
fetch(value, data => (this.data = data) , this.url);
this.$emit('change' , value)
},
},
mounted() {
console.log("this.url ==> " , this.url);
}
};
</script>
@@ -0,0 +1,138 @@
<!-- =========================================================================================
File Name: StatisticsCard.vue
Description: Statistics card component
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<template>
<vx-card class="overflow-hidden">
<div slot="no-body">
<div class="p-6" :class="{
'flex justify-between flex-row-reverse items-center': iconRight,
'text-center': !iconRight && hideChart,
'pb-0': !hideChart
}">
<feather-icon :icon="icon" class="p-3 inline-flex rounded-full" :class="[`text-${color}`, {'mb-4': !iconRight}]" :style="{background: `rgba(var(--vs-${color}),.15)`}"></feather-icon>
<div class="truncate">
<h2 class="mb-1 font-bold">{{ statistic }}</h2>
<span>{{ statisticTitle }}</span>
</div>
</div>
<div class="line-area-chart" v-if="!hideChart">
<vue-apex-charts ref="apexChart" :type="type" height="100" width="100%" :options="chartOptions" :series="chartData" />
</div>
</div>
</vx-card>
</template>
<script>
import VueApexCharts from 'vue-apexcharts'
import chartConfigs from './chartConfigs.js'
import _color from '@/assets/utils/color.js'
export default{
props: {
icon: {
type: String,
required: true
},
statistic: {
type: [Number, String],
required: true
},
statisticTitle: {
type: String
},
chartData: {
// type: Array,
// required: true
},
color: {
type: String,
default: 'primary'
},
colorTo: {
type: String
},
// chartType: {
// type: String,
// default: 'line-chart',
// },
type: {
type: String,
default: 'line'
},
iconRight: {
type: Boolean,
default: false
},
hideChart: {
type: Boolean,
default: false
}
},
data () {
return {
chartOptions: null
}
},
watch: {
themePrimaryColor () {
this.$refs.apexChart.updateOptions({ theme: { monochrome: { color: this.getHex(this.color) } } })
}
},
computed: {
themePrimaryColor () {
return this.$store.state.themePrimaryColor
}
},
methods: {
getHex (color) {
if (_color.isColor(color)) {
let rgb = window.getComputedStyle(document.documentElement).getPropertyValue(`--vs-${color}`)
rgb = rgb.split(',')
return `#${ ((1 << 24) + (Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16).slice(1)}`
}
return color
},
gradientToColor (color) {
const gradientToColors = {
'primary': '#A9A2F6',
'success': '#55DD92',
'warning': '#ffc085',
'danger': '#F97794'
}
return gradientToColors[color] ? gradientToColors[color] : gradientToColors['primary']
}
},
components: {
VueApexCharts
},
created () {
if (this.type === 'area') {
// assign chart options
this.chartOptions = Object.assign({}, chartConfigs.areaChartOptions)
this.chartOptions['theme'] = {
monochrome: {
enabled: true,
color: this.getHex(this.color),
shadeTo: 'light',
shadeIntensity: 0.65
}
}
} else if (this.type === 'line') {
// Assign chart options
this.chartOptions = JSON.parse(JSON.stringify(chartConfigs.lineChartOptions))
this.chartOptions.fill.gradient.gradientToColors = [this.gradientToColor(this.colorTo || this.color)]
this.chartOptions.colors = [this.getHex(this.color)]
}
}
}
</script>
@@ -0,0 +1,114 @@
export default {
areaChartOptions: {
grid: {
show: false,
padding: {
left: 0,
right: 0
}
},
chart: {
toolbar: {
show: false
},
sparkline: {
enabled: true
}
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth',
width: 2.5
},
fill: {
type: 'gradient',
gradient: {
shadeIntensity: 0.9,
opacityFrom: 0.5,
opacityTo: 0.2,
stops: [0, 80, 100]
}
},
xaxis: {
type: 'numeric',
lines: {
show: false
},
axisBorder: {
show: false
},
labels: { show: false }
},
yaxis: [
{
y: 0,
offsetX: 0,
offsetY: 0,
padding: {
left: 0,
right: 0
}
}
],
tooltip: {
x: { show: false }
}
},
lineChartOptions: {
grid: {
show: false,
padding: {
left: 0,
right: 0
}
},
chart: {
type: 'line',
dropShadow: {
enabled: true,
top: 5,
left: 0,
blur: 4,
opacity: 0.10
},
toolbar: {
show: false
},
sparkline: {
enabled: true
}
},
stroke: {
width: 5,
curve: 'smooth'
},
xaxis: {
type: 'numeric'
},
// colors: ['#7367F0'],
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
// gradientToColors: ['#A9A2F6'],
shadeIntensity: 1,
type: 'horizontal',
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100, 100, 100]
}
},
markers: {
size: 0,
hover: {
size: 5
}
},
tooltip: {
x: { show: false }
}
}
}
+81
View File
@@ -0,0 +1,81 @@
<template>
<div>
<template v-for="(tag, index) in value">
<a-tooltip v-if="tag.length > 20" :key="tag" :title="tag">
<a-tag :color="color" :key="tag" :closable="index !== -1" @close="() => handleClose(tag)">
{{ `${tag.slice(0, 20)}...` }}
</a-tag>
</a-tooltip>
<a-tag :color="color" v-else :key="tag" :closable="index !== -1" @close="() => handleClose(tag)">
{{ tag }}
</a-tag>
</template>
<a-input
v-if="inputVisible"
ref="input"
type="text"
size="small"
:style="{ width: '78px' }"
:value="inputValue"
@change="handleInputChange"
@blur="handleInputConfirm"
@keydown.enter="handleInputConfirm"
/>
<a-tag v-else style="background: #fff; borderStyle: dashed;" @click="showInput">
<a-icon type="plus"/>
افزودن
</a-tag>
</div>
</template>
<script>
export default {
props: {
value:{
type:Array,
default:[]
},
color:{
default:''
}
},
data() {
return {
inputVisible: false,
inputValue: '',
};
},
methods: {
handleClose(removedTag) {
const tags = this.value.filter(tag => tag !== removedTag);
this.value = tags;
this.$emit('input', tags);
this.$emit('change', tags);
},
showInput() {
this.inputVisible = true;
this.$nextTick(function () {
this.$refs.input.focus();
});
},
handleInputChange(e) {
this.inputValue = e.target.value;
console.log(this.inputValue,'this.inputValue');
},
handleInputConfirm(e) {
e.preventDefault()
const inputValue = this.inputValue;
console.log(this.inputValue,'this.inputValue');
let tags = this.value;
if (inputValue && tags.indexOf(inputValue) === -1) {
tags = [...tags, inputValue];
}
Object.assign(this, {
inputVisible: false,
inputValue: '',
});
this.$emit('input', tags);
this.$emit('change', tags);
},
},
};
</script>
+40
View File
@@ -0,0 +1,40 @@
<!-- =========================================================================================
File Name: VxTimeline.vue
Description: Timeline Component
Component Name: VxTimeline
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<template>
<ul class="vx-timeline">
<li v-for="item in data" :key="item.title">
<div class="timeline-icon" :class="`bg-${item.color}`">
<feather-icon :icon="item.icon" svgClasses="text-white stroke-current w-5 h-5" />
</div>
<div class="timeline-info">
<p class="font-semibold">{{ item.title }}</p>
<span class="activity-desc">{{ item.desc }}</span>
</div>
<small class="text-grey activity-e-time">{{ item.time }}</small>
</li>
</ul>
</template>
<script>
export default {
name: 'vx-timeline',
props: {
data: {
type: Array,
required: true
}
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/vxTimeline.scss";
</style>
@@ -0,0 +1,263 @@
<template>
<div class="vx-auto-suggest">
<div class="flex items-center relative">
<!-- Input -->
<vs-input
ref="input"
:placeholder="placeholder"
:class="inputClassses"
class="z-50"
icon-pack="feather"
icon="icon-search"
icon-no-border
v-model="searchQuery"
@keyup.esc="escPressed"
@keyup.up="increaseIndex(false)"
@keyup.down="increaseIndex"
@keyup.enter="suggestionSelected"
@focus="updateInputFocus"
@blur="updateInputFocus(false)" />
</div>
<!-- Group List -->
<ul
ref="scrollContainer"
:class="{'hidden': !inputFocused}"
class="auto-suggest-suggestions-list z-50 rounded-lg mt-2 shadow-lg overflow-x-hidden"
@mouseenter="insideSuggestions = true"
@mouseleave="insideSuggestions = false"
@focus="updateInputFocus"
@blur="updateInputFocus(false)"
tabindex="-1">
<li
ref="grp_list"
v-for="(suggestion_list, grp_name, grp_index) in filteredData"
:key="grp_index"
class="auto-suggest__suggestion-group-container">
<!-- Group Header -->
<p class="auto-suggest__suggestion-group-title pt-3 pb-1 px-4" v-if="!hideGroupTitle">
<slot name="group" :group_name="grp_name"></slot>
</p>
<!-- Suggestion List of each group -->
<ul>
<li
v-for="(suggestion, index) in suggestion_list"
:key="index"
class="auto-suggest__suggestion-group__suggestion py-3 px-4 cursor-pointer"
:class="{'vx-auto-suggest__current-selected': currentSelected == `${grp_index}.${index}`}"
@mouseenter="currentSelected = `${grp_index}.${index}`"
@click="suggestionSelected">
<slot :name="grp_name" :suggestion="suggestion"></slot>
</li>
<li class="auto-suggest__suggestion-group__suggestion py-3 px-4 no-results" v-if="!suggestion_list.length && searchQuery">
<slot name="noResult" :group_name="grp_name">
<p>No Results Found.</p>
</slot>
</li>
</ul>
</li>
</ul>
</div>
</template>
<script>
export default{
props: {
placeholder: {
type: String,
default: 'Search..'
},
data: {
type: Object,
required: true
},
initalData: {
type: Object,
default: () => new Object
},
inputClassses: {
type: [String, Object, Array]
},
autoFocus: {
type: Boolean,
default: false
},
showPinned: {
type: Boolean,
default: false
},
searchLimit: {
type: Number,
default: 4
},
hideGroupTitle: {
type: Boolean,
default: false
}
},
data () {
return {
searchQuery: '',
filteredData: {},
currentSelected: -1,
inputFocused: false,
insideSuggestions: false
}
},
watch: {
// UPDATE SUGGESTIONS LIST
searchQuery (val) {
this.$emit('input', val)
if (val === '') {
this.inputInit()
} else {
const queried_data = {}
const data_grps = Object.keys(this.data)
data_grps.forEach((grp, i) => {
queried_data[data_grps[i]] = this.filter_grp(this.data[grp])
})
// Check if any of group has at least one queried item
if (!Object.values(queried_data).some(obj => obj.length)) {
this.currentSelected = -1
}
this.filteredData = queried_data
}
},
autoFocus (val) {
if (val) this.focusInput()
else this.searchQuery = ''
},
filteredData (val) {
// Auto Select first item if it's not item-404
let grp_index = null
for (const [index, grp_suggestions] of Object.values(val).entries()) {
if (grp_suggestions.length) {
grp_index = index
break
}
}
if (grp_index !== null) this.currentSelected = `${grp_index }.0`
}
},
methods: {
escPressed () {
this.$emit('closeSearchbar')
this.searchQuery = ''
},
filter_grp (grp) {
const exactEle = grp.data.filter((item) => {
return item[grp.key].toLowerCase().startsWith(this.searchQuery.toLowerCase())
})
const containEle = grp.data.filter((item) => {
return !item[grp.key].toLowerCase().startsWith(this.searchQuery.toLowerCase()) && item[grp.key].toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
})
return exactEle.concat(containEle).slice(0, this.searchLimit)
},
inputInit () {
if (Object.entries(this.initalData).length === 0 && this.initalData.constructor === Object) {
this.filteredData = {}
} else {
this.filteredData = this.initalData
}
},
updateInputFocus (val = true) {
if (val) {
if (this.searchQuery === '') this.inputInit()
setTimeout(() => {
this.inputFocused = true
}, 100)
} else {
if (this.insideSuggestions) return
setTimeout(() => {
this.inputFocused = false
}, 100)
this.escPressed()
}
},
suggestionSelected () {
if (this.currentSelected > -1) {
const [grp_index, item_index] = this.currentSelected.split('.')
const grp_of_selected_item = Object.keys(this.data)[grp_index]
const selected_item = this.filteredData[grp_of_selected_item][item_index]
this.$emit('selected', {[grp_of_selected_item]: selected_item})
this.searchQuery = ''
}
},
increaseIndex (val = true) {
/* eslint-disable no-lonely-if */
// If there's no matching items
if (!Object.values(this.filteredData).some(grp_items => grp_items.length)) return
const [grp_i, item_i] = this.currentSelected.split('.')
const grp_arr = Object.entries(this.filteredData)
const active_grp_total_items = grp_arr[grp_i][1].length
if (val) {
// If active item is not of last item in grp
if (active_grp_total_items - 1 > item_i) {
this.currentSelected = `${grp_i }.${ Number(item_i) + 1}`
// If active item grp is not last in grp list
} else if (grp_i < grp_arr.length - 1) {
for (let i = Number(grp_i) + 1; i < grp_arr.length; i++) {
// If navigating group have items => Then move in that group
if (grp_arr[i][1].length > 0) {
this.currentSelected = `${Number(i) }.0`
break
}
}
}
} else {
// If active item is not of first item in grp
if (Number(item_i)) {
this.currentSelected = `${grp_i }.${ Number(item_i) - 1}`
// If active item grp is not first in grp list
} else if (Number(grp_i)) {
for (let i = Number(grp_i) - 1; i >= 0; i--) {
// If navigating group have items => Then move in that group
if (grp_arr[i][1].length > 0) {
this.currentSelected = `${i }.${ grp_arr[i][1].length - 1}`
break
}
}
}
}
/* eslint-enable no-lonely-if */
},
focusInput () {
this.$refs.input.$el.querySelector('input').focus()
}
},
mounted () {
if (this.autoFocus) this.focusInput()
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/vxAutoSuggest.scss";
</style>
+310
View File
@@ -0,0 +1,310 @@
<!-- =========================================================================================
File Name: VxCard.vue
Description: Card Component
Component Name: VxCard
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<template>
<div class="vx-card" ref="card" :class="[
{'overflow-hidden': tempHidden},
{'no-shadow': noShadow},
{'rounded-none': noRadius},
{'card-border': cardBorder},
cardClasses ]" :style="cardStyles"
v-on="$listeners">
<div class="vx-card__header" v-if="hasHeader">
<!-- card title -->
<div class="vx-card__title">
<h4 v-if="this.$props.title" :style="titleStyles" :class="titleClasses">{{ title }}</h4>
<h6 v-if="this.$props.subtitle" :style="subtitleStyles" :class="subtitleClasses">{{ subtitle }}</h6>
</div>
<!-- card actions -->
<div class="vx-card__actions" v-if="hasAction">
<slot name="actions">
<div class="vx-card__action-buttons"
v-if="(actionButtons || collapseAction || refreshContentAction || removeCardAction) && !codeToggler">
<feather-icon @click="toggleContent" icon="ChevronUpIcon"
:class="{rotate180: !isContentCollapsed}" class="ml-4"
v-if="actionButtons || collapseAction"/>
<feather-icon @click="refreshcard" icon="RotateCwIcon" class="ml-4"
v-if="actionButtons || refreshContentAction"/>
<feather-icon @click="removeCard" icon="XIcon" class="ml-4"
v-if="actionButtons || removeCardAction"/>
</div>
<div class="vx-card__code-toggler sm:block hidden" v-if="codeToggler && !actionButtons">
<feather-icon icon="CodeIcon"
:class="{'border border-solid border-primary border-t-0 border-r-0 border-l-0': showCode}"
@click="toggleCode"></feather-icon>
</div>
</slot>
</div>
</div>
<div class="vx-card__collapsible-content vs-con-loading__container" ref="content"
:class="[{collapsed: isContentCollapsed}, {'overflow-hidden': tempHidden}]" :style="StyleItems">
<!-- content with no body(no padding) -->
<slot name="no-body"></slot>
<!-- content inside body(with padding) -->
<div class="vx-card__body" v-if="this.$slots.default">
<slot></slot>
</div>
<!-- content with no body(no padding) -->
<slot name="no-body-bottom"></slot>
<div class="vx-card__footer" v-if="this.$slots.footer">
<slot name="footer"></slot>
</div>
</div>
<div class="vx-card__code-container" ref="codeContainer" v-show="this.$slots.codeContainer"
:style="codeContainerStyles" :class="{collapsed: !showCode}">
<div class="code-content">
<prism :language="codeLanguage" :key="$vs.rtl">
<slot name="codeContainer"></slot>
</prism>
</div>
</div>
</div>
</template>
<script>
import Prism from 'vue-prism-component'
import _color from '@/assets/utils/color.js'
export default {
name: 'vx-card',
props: {
title: String,
subtitle: String,
actionButtons: {
type: Boolean,
default: false
},
actionButtonsColor: {
type: String,
default: 'success'
},
codeToggler: {
type: Boolean,
default: false
},
noShadow: {
default: false,
type: Boolean
},
noRadius: {
default: false,
type: Boolean
},
cardBorder: {
default: false,
type: Boolean
},
codeLanguage: {
default: 'markup',
type: String
},
collapseAction: {
default: false,
type: Boolean
},
refreshContentAction: {
default: false,
type: Boolean
},
removeCardAction: {
default: false,
type: Boolean
},
headerBackground: {
default: '',
type: String
},
// bodyBackground: {
// default: '',
// type: String
// },
// headerbackground: {
// default: '',
// type: String
// },
cardBackground: {
default: '',
type: String
},
contentColor: {
default: '',
type: String
},
titleColor: {
default: '',
type: String
},
subtitleColor: {
default: '#b8c2cc',
type: String
}
},
data() {
return {
isContentCollapsed: false,
showCode: false,
maxHeight: null,
cardMaxHeight: null,
codeContainerMaxHeight: '0px',
tempHidden: false
}
},
computed: {
hasAction() {
return this.$slots.actions || (this.actionButtons || this.collapseAction || this.refreshContentAction || this.removeCardAction || this.codeToggler)
},
hasHeader() {
return this.hasAction || (this.title || this.subtitle)
},
StyleItems() {
return {maxHeight: this.maxHeight}
},
cardStyles() {
const obj = {maxHeight: this.cardMaxHeight}
if (!_color.isColor(this.cardBackground)) obj.background = _color.getColor(this.cardBackground)
if (!_color.isColor(this.contentColor)) obj.color = _color.getColor(this.contentColor)
return obj
},
codeContainerStyles() {
return {maxHeight: this.codeContainerMaxHeight}
},
cardClasses() {
let str = ''
// Add bg class
if (_color.isColor(this.cardBackground)) {
str += ` bg-${this.cardBackground}`
}
// add content color
if (_color.isColor(this.contentColor)) {
str += ` text-${this.contentColor}`
}
return str.trim()
},
titleStyles() {
return {
color: _color.getColor(this.titleColor)
}
},
titleClasses() {
let str = ''
// add content color
if (_color.isColor(this.titleColor)) {
str += ` text-${this.titleColor}`
}
return str.trim()
},
subtitleStyles() {
const obj = {}
if (!_color.isColor(this.subtitleColor)) obj.color = _color.getColor(this.subtitleColor)
return obj
},
subtitleClasses() {
let str = ''
// add content color
if (_color.isColor(this.subtitleColor)) {
str += ` text-${this.subtitleColor}`
}
return str.trim()
}
},
methods: {
toggleContent() {
this.$refs.content.style.overflow = 'hidden'
const scrollHeight = this.$refs.content.scrollHeight
if (this.maxHeight === '1.5rem') {
this.maxHeight = `${scrollHeight}px`
setTimeout(() => {
this.maxHeight = 'none'
this.$refs.content.style.overflow = null
}, 300)
} else {
this.maxHeight = `${scrollHeight}px`
setTimeout(() => {
this.maxHeight = '1.5rem'
this.$refs.content.style.overflow = null
}, 50)
}
this.isContentCollapsed = !this.isContentCollapsed
this.$emit('toggleCollapse', this.isContentCollapsed)
},
refreshcard() {
this.$vs.loading({
container: this.$refs.content,
scale: 0.5
})
this.tempHidden = true
this.$emit('refresh', this)
},
visibleAnimation() {
this.$vs.loading({
container: this.$refs.content,
scale: 0.5
})
this.tempHidden = true
},
removeRefreshAnimation(time = 100) {
setTimeout(() => {
this.$vs.loading.close(this.$refs.content)
this.tempHidden = false
}, time)
},
removeCard() {
const scrollHeight = this.$refs.card.scrollHeight
this.cardMaxHeight = `${scrollHeight}px`
this.$el.style.overflow = 'hidden'
setTimeout(() => {
this.cardMaxHeight = '0px'
}, 50)
this.$emit('remove')
},
toggleCode() {
this.tempHidden = true
this.showCode = !this.showCode
const scrollHeight = this.$refs.codeContainer.scrollHeight
if (this.codeContainerMaxHeight === '0px') {
this.codeContainerMaxHeight = `${scrollHeight}px`
setTimeout(() => {
this.codeContainerMaxHeight = 'none'
this.tempHidden = false
}, 300)
} else {
this.codeContainerMaxHeight = `${scrollHeight}px`
setTimeout(() => {
this.codeContainerMaxHeight = '0px'
this.tempHidden = false
}, 150)
}
}
},
components: {
Prism
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/vxCard.scss"
</style>
@@ -0,0 +1,33 @@
<template functional>
<div class="vx-input-group flex" :class="data.staticClass">
<!-- SLOT: PREPEND -->
<div class="vx-input-group-prepend flex" :class="props.prependClasses" v-if="slots().prepend">
<slot name="prepend"></slot>
</div>
<!-- SLOT: DEFAULT -->
<div class="vx-input-group-default flex-grow">
<slot></slot>
</div>
<!-- SLOT: APPEND -->
<div class="vx-input-group-append flex" :class="props.appendClasses" v-if="slots().append">
<slot name="append"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'vx-input-group',
props: {
prependClasses : { type: String },
appendClasses : { type: String }
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/vxInputGroup.scss";
</style>
+59
View File
@@ -0,0 +1,59 @@
<!-- =========================================================================================
File Name: VxList.vue
Description: list Component
Component Name: VxList
----------------------------------------------------------------------------------------
Item Name: Vuexy - Vuejs, HTML & Laravel Admin Dashboard Template
Author: Pixinvent
Author URL: http://www.themeforest.net/user/pixinvent
========================================================================================== -->
<template>
<ul class="list">
<li class="list__item" v-for="(item, index) in list" :key="index">
<feather-icon :icon="rtlSpecificIcon" class="w-5 h-5 mr-1"></feather-icon><span v-html="item"></span>
</li>
</ul>
</template>
<script>
export default {
name: 'vx-list',
props: {
list: {
type: Array,
required: true
},
icon: {
type: String,
default: 'ChevronsRightIcon'
}
},
computed: {
rtlSpecificIcon () {
let i = this.icon
if (this.$vs.rtl) {
if (i.includes('Left')) {
i = i.replace('Left', 'Right')
} else if (i.includes('Right')) {
i = i.replace('Right', 'Left')
}
if (i.includes('Left')) {
i = i.replace('Left', 'Right')
} else if (i.includes('Right')) {
i = i.replace('Right', 'Left')
}
}
return i
}
}
}
</script>
<style lang="scss">
@import "@/assets/scss/vuexy/components/vxList.scss";
</style>
+27
View File
@@ -0,0 +1,27 @@
<template>
<div>
<a-table :columns="cols" :data-source="data">
<slot/>
<slot name="header">
wow
</slot>
<div
slot="filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }"
style="padding: 8px"
>
</div>
</a-table>
</div>
</template>
<script>
export default {
name: "VxTable",
props: ['cols', 'data']
}
</script>
<style scoped>
</style>
@@ -0,0 +1,83 @@
<template>
<div>
<a-range-picker
:default-value="[moment('2015/01/01', dateFormat), moment('2015/01/01', dateFormat)]"
:format="dateFormat"
/>
<a-button v-if="this.n==0"
@click="change"
type="primary"
size="small"
style="width: 90px; margin-right: 8px"
>
کمتر از
</a-button>
<a-button v-if="this.n==1"
@click="change"
type="primary"
size="small"
style="width: 90px; margin-right: 8px"
>
مساوی با
</a-button>
<a-button v-if="this.n==2"
@click="change"
type="primary"
size="small"
style="width: 90px; margin-right: 8px"
>
بیشتر از
</a-button>
</div>
</template>
<script>
import moment from 'moment';
export default {
name: 'TextSearchXTableFilter',
props:[
'setSelectedKeys',
'selectedKeys',
'confirm',
'clearFilters',
'column'
],
data () {
return {
dateFormat: 'YYYY/MM/DD',
monthFormat: 'YYYY/MM',
dateFormatList: ['DD/MM/YYYY', 'DD/MM/YY'],
size: 'default',
searchText: '',
searchedColumn: undefined,
n:"0"
}
},
methods: {
moment,
handleSearch (selectedKeys, confirm, dataIndex) {
confirm()
this.searchText = selectedKeys[0]
this.searchedColumn = dataIndex
},
handleReset (clearFilters) {
clearFilters()
this.searchText = ''
},
change(){
this.n++;
if(this.n==3){
this.n=0
}
console.log("n:",this.n)
}
},
mounted () {
console.log(this.props)
},
created () {
}
}
</script>
@@ -0,0 +1,93 @@
<template>
<div class="p-3" style="min-width: 250px">
<div class="flex mb-2" v-for="item,index in filter" :key="index">
<a-button @click="toggleCr(item)"
>{{getCrTitle(item)}}
</a-button>
<ADatePicker :disabled="item.op === 'nil'"
v-model="item.value"
@pressEnter="() => search()"
/>
</div>
<ADivider class="my-2"/>
<div class="flex">
<a @click="add">
<feather-icon icon="PlusIcon" svg-classes="w-5 h-5"/>
</a>
<div class="flex-1"></div>
<a v-if="filter.length > 1" @click="remove">
<feather-icon icon="MinusIcon" svg-classes="w-5 h-5 color-danger"/>
</a>
</div>
<div class="flex">
<AButton class="flex-1 mr-2" type="flat" size="small" @click="reset">ریست</AButton>
<AButton class="flex-1" type="primary" size="small" @click="search">فیلتر</AButton>
</div>
</div>
</template>
<script>
export default {
name: 'DateXTableFilter',
props: ['column'],
data() {
return {
filter: [],
crs: ['not', 'nn', 'nil', 'gt', 'lt']
}
},
methods: {
toggleCr(item) {
const ind = this.crs.indexOf(item.op) + 1;
if (ind in this.crs) {
item.op = this.crs[ind]
return;
}
item.op = this.crs[0];
},
add() {
this.filter.push({
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
})
},
remove() {
this.filter.pop();
},
search() {
this.$emit('search', {model: this.filter.filter((item) => item.value), column: this.column})
},
reset() {
this.filter = [
{
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
}
];
this.$emit('search', {model: this.filter.filter((item) => item.value), key: this.column})
},
getCrTitle(item) {
return {and: 'و', or: 'یا', not: 'نباشد', nn: 'انباشته', nil: 'تهی', gt: "بزرگتر", lt: 'کوچکتر'}[item.op];
},
getCrType(item) {
return {
and: 'primary',
or: 'danger',
not: 'warning',
nn: 'flat',
nil: '',
gt: "primary",
lt: 'primary'
}[item.op]
}
},
created() {
this.add()
}
}
</script>
@@ -0,0 +1,93 @@
<template>
<div class="p-3" style="min-width: 250px">
<div class="flex mb-2" v-for="item,index in filter" :key="index">
<a-button @click="toggleCr(item)"
>{{getCrTitle(item)}}
</a-button>
<AInputNumber :disabled="item.op === 'nil'"
v-model="item.value"
@pressEnter="() => search()"
/>
</div>
<ADivider class="my-2"/>
<div class="flex">
<a @click="add">
<feather-icon icon="PlusIcon" svg-classes="w-5 h-5"/>
</a>
<div class="flex-1"></div>
<a v-if="filter.length > 1" @click="remove">
<feather-icon icon="MinusIcon" svg-classes="w-5 h-5 color-danger"/>
</a>
</div>
<div class="flex">
<AButton class="flex-1 mr-2" type="flat" size="small" @click="reset">ریست</AButton>
<AButton class="flex-1" type="primary" size="small" @click="search">فیلتر</AButton>
</div>
</div>
</template>
<script>
export default {
name: 'NumberXTableFilter',
props: ['column'],
data() {
return {
filter: [],
crs: ['not', 'nn', 'nil', 'gt', 'lt']
}
},
methods: {
toggleCr(item) {
const ind = this.crs.indexOf(item.op) + 1;
if (ind in this.crs) {
item.op = this.crs[ind]
return;
}
item.op = this.crs[0];
},
add() {
this.filter.push({
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
})
},
remove() {
this.filter.pop();
},
search() {
this.$emit('search', {model: this.filter.filter((item) => item.value), column: this.column})
},
reset() {
this.filter = [
{
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
}
];
this.$emit('search', {model: this.filter.filter((item) => item.value), key: this.column})
},
getCrTitle(item) {
return {and: 'و', or: 'یا', not: 'نباشد', nn: 'انباشته', nil: 'تهی', gt: "بزرگتر", lt: 'کوچکتر'}[item.op];
},
getCrType(item) {
return {
and: 'primary',
or: 'danger',
not: 'warning',
nn: 'flat',
nil: '',
gt: "primary",
lt: 'primary'
}[item.op]
}
},
created() {
this.add()
}
}
</script>
@@ -0,0 +1,59 @@
<template>
<div class="flex">
<a-button @click="changeStatus()" style="width:20%;margin: 0px 0 0" type="primary" v-if="status==1">و</a-button>
<a-button @click="changeStatus()" style="width:20%;margin: 0px 0 0" type="danger" v-if="status==2">یا</a-button>
<a-button @click="changeStatus()" style="width:20%;margin: 0px 0 0" v-if="status==3">تهی</a-button>
<a-select v-if="status===3" disabled v-ant-ref="c => (searchInput = c)" :value="selectedKeys[0]" :default-value="0" style="width: 80%; margin-bottom: 8px; display: block;" @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])" @pressEnter="() => handleSearch(selectedKeys, confirm, column.dataIndex)">
<a-select-option v-for="i in 25" :key="(i + 9).toString(36) + i">
{{i}}
</a-select-option>
</a-select>
<a-select v-else v-ant-ref="c => (searchInput = c)" :value="selectedKeys[0]" :default-value="0" style="width: 80%; margin-bottom: 8px; display: block;" @change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])" @pressEnter="() => handleSearch(selectedKeys, confirm, column.dataIndex)">
<a-select-option v-for="i in 25" :key="(i + 9).toString(36) + i">
{{i}}
</a-select-option>
</a-select>
</div>
</template>
<script>
export default {
name: 'TextSearchXTableFilter',
props: [
'setSelectedKeys',
'selectedKeys',
'confirm',
'clearFilters',
'column'
],
data() {
return {
searchText: '',
searchedColumn: undefined,
status: 1
}
},
methods: {
handleSearch(selectedKeys, confirm, dataIndex) {
confirm()
this.searchText = selectedKeys[0]
this.searchedColumn = dataIndex
},
changeStatus() {
this.status = this.status + 1;
if (this.status === 4) {
this.status = 1;
}
},
handleReset(clearFilters) {
clearFilters()
this.searchText = ''
}
},
mounted() {
console.log(this.props)
},
created() {}
}
</script>
@@ -0,0 +1,79 @@
<template>
<div class="p-3" style="min-width: 250px">
<div class="flex mb-2 items-center" v-for="item,index in filter" :key="index">
<div class="flex-1"></div>
<ASwitch :disabled="item.op === 'nil'"
v-model="item.value"
@pressEnter="() => search()"
/>
</div>
<ADivider class="my-2"/>
<div class="flex">
<AButton class="flex-1 mr-2" type="flat" size="small" @click="reset">ریست</AButton>
<AButton class="flex-1" type="primary" size="small" @click="search">فیلتر</AButton>
</div>
</div>
</template>
<script>
export default {
name: 'SwitchXTableFilter',
props: ['column'],
data() {
return {
filter: [],
crs: ['and', 'or']
}
},
methods: {
toggleCr(item) {
const ind = this.crs.indexOf(item.op) + 1;
if (ind in this.crs) {
item.op = this.crs[ind]
return;
}
item.op = this.crs[0];
},
add() {
this.filter.push({
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: true
})
},
remove() {
this.filter.pop();
},
search() {
this.$emit('search', {model: this.filter.filter((item) => item.value != undefined), column: this.column})
},
reset() {
this.filter = [{
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
}];
this.$emit('search', {model: this.filter.filter((item) => item.value != undefined), key: this.column})
},
getCrTitle(item) {
return {and: 'و', or: 'یا'}[item.op];
},
getCrType(item) {
return {
and: 'primary',
or: 'danger',
not: 'warning',
nn: 'flat',
nil: '',
gt: "primary",
lt: 'primary'
}[item.op]
}
},
created() {
this.add()
}
}
</script>
@@ -0,0 +1,93 @@
<template>
<div class="p-3" style="min-width: 250px">
<div class="flex mb-2" v-for="item,index in filter" :key="index">
<a-button @click="toggleCr(item)"
>{{getCrTitle(item)}}
</a-button>
<AInput :disabled="item.op === 'nil'"
v-model="item.value"
@pressEnter="() => search()"
/>
</div>
<ADivider class="my-2"/>
<div class="flex">
<a @click="add">
<feather-icon icon="PlusIcon" svg-classes="w-5 h-5"/>
</a>
<div class="flex-1"></div>
<a v-if="filter.length > 1" @click="remove">
<feather-icon icon="MinusIcon" svg-classes="w-5 h-5 color-danger"/>
</a>
</div>
<div class="flex">
<AButton class="flex-1 mr-2" type="flat" size="small" @click="reset">ریست</AButton>
<AButton class="flex-1" type="primary" size="small" @click="search">فیلتر</AButton>
</div>
</div>
</template>
<script>
export default {
name: 'TextXTableFilter',
props: ['column'],
data() {
return {
filter: [],
crs: ['not', 'nn', 'nil']
}
},
methods: {
toggleCr(item) {
const ind = this.crs.indexOf(item.op) + 1;
if (ind in this.crs) {
item.op = this.crs[ind]
return;
}
item.op = this.crs[0];
},
add() {
this.filter.push({
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
})
},
remove() {
this.filter.pop();
},
search() {
this.$emit('search', {model: this.filter.filter((item) => item.value), column: this.column})
},
reset() {
this.filter = [
{
op: 'and',
id: `${this.column.key}-${this.filter.length}`,
field: this.column.key,
value: undefined
}
];
this.$emit('search', {model: this.filter.filter((item) => item.value), key: this.column})
},
getCrTitle(item) {
return {and: 'و', or: 'یا', not: 'نباشد', nn: 'انباشته', nil: 'تهی', gt: "بزرگتر", lt: 'کوچکتر'}[item.op];
},
getCrType(item) {
return {
and: 'primary',
or: 'danger',
not: 'warning',
nn: 'flat',
nil: '',
gt: "primary",
lt: 'primary'
}[item.op]
}
},
created() {
this.add()
}
}
</script>
+286
View File
@@ -0,0 +1,286 @@
<template>
<div>
<ACard ref="tblCard">
<ASpin :spinning="loading">
<div class="flex w-full justify-between">
<a-input-search class="w-1/4 pb-4 input-ltr" placeholder="جستجو بر اساس نام / کد ملی / کد پرسنلی / شمار بیمه" enter-button="جستجو" @search="onSearch"/>
<div class="flex items-center">
<a class="left-table-btn" @click="fullScreen">
<feather-icon :icon="getFullIcon" svg-classes="w-5 h-5"/>
</a>
<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>
<a class="left-table-btn ">
<feather-icon icon="SettingsIcon" svg-classes="w-5 h-5"/>
</a>
</ADropdown>
<a class="left-table-btn refresh-btn" @click="fetch">
<feather-icon icon="RotateCwIcon" svg-classes="w-5 h-5"/>
</a>
</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
show-size-changer
:pageSizeOptions="['5','10','20']"
:page-size.sync="query.pageSize"
@change="changePage"
@showSizeChange="changePage"
:default-current="1" :total="query.total" v-model.sync="query.currentPage">
<template slot="buildOptionText" slot-scope="props">
<div dir="rtl">
<span> {{ props.value }} </span>
<span>رکورد</span>
</div>
</template>
</APagination>
</div>
</div>
<div v-for="item,index in getColsFilter" :slot="item.scopedSlots.filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div>
<TextXTableFilter v-if="item.filterType === 'default'"
:column="column"
@search="submitFilter"
/>
<SwitchXTableFilter v-if="item.filterType === 'switch'"
:column="column"
@search="submitFilter"
/>
<NumberXTableFilter v-if="item.filterType === 'number'"
:column="column"
@search="submitFilter"
/>
<SelectOptionXTableFilter v-if="item.filterType === 'select'" :column="item"
:set-selected-keys="setSelectedKeys" :selected-keys="selectedKeys"
:confirm="confirm" :clear-filters="clearFilters"/>
<DateXTableFilter v-if="item.filterType === 'date'"
:column="column"
@search="submitFilter"
/>
</div>
<a-icon :slot="item.scopedSlots.filterIcon" slot-scope="filtered" type="search"
:style="{ color: filtered ? '#108ee9' : undefined }"/>
</div>
</a-table>
</ASpin>
</ACard>
</div>
</template>
<script>
import SwitchXTableFilter from './SwitchXTableFilter'
import SelectOptionXTableFilter from './SelectOptionXTableFilter'
import DateXTableFilter from './DateXTableFilter'
import TextXTableFilter from './TextXTableFilter'
import XTbl from "./model/XTbl";
import Draggable from 'vuedraggable'
import axios from "../../axios";
import {openFullscreen, closeFullscreen} from "@/plugins/fullscreen";
import NumberXTableFilter from "./NumberXTableFilter";
export default {
name: 'XTable',
components: {
TextXTableFilter,
NumberXTableFilter,
SwitchXTableFilter,
SelectOptionXTableFilter,
Draggable,
DateXTableFilter,
// DatePickerBetweenXTableFilter
},
props: {
model: {
type: XTbl.default
}
},
data() {
return {
data: [],
query: {
search: undefined,
total: 0,
pageSize: 10,
currentPage: 1,
},
locale: {
emptyText: 'هیج رکوردی یافت نشد.'
},
loading: false,
full: false,
colSelector: false
}
},
methods: {
submitFilter({model, column}) {
if (!this.query.filters) {
this.query.filters = []
}
this.query.filters = this.query.filters.filter((item) => item.field !== column.key);
this.query.filters = this.query.filters.concat(model);
this.fetch()
},
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() {
return this.query;
},
async fetch() {
try {
this.loading = true;
const {data: {list, pagination}} = await axios.post(this.model.url, this.requestData());
this.data = list;
this.query = pagination;
} catch (e) {
throw e
} finally {
this.loading = false;
}
},
noThings() {
},
onSearch(value) {
console.log(value);
this.query.search = value;
if (value) {
this.fetch();
}
},
showColSelector() {
this.colSelector = !this.colSelector;
}
},
computed: {
getFullIcon() {
return !this.full ? 'Maximize2Icon' : 'Minimize2Icon';
},
customRender() {
return this.model.cols.filter((item) => item.scopedSlots && item.scopedSlots.customRender).map((i) => i.scopedSlots.customRender)
},
cols: {
get() {
return this.model.cols || [];
},
set(value) {
this.model.cols = value;
}
},
getColsFilter() {
return this.cols.filter(item => item.filtered)
},
visibleCols() {
return this.cols.filter(item => item.show)
}
},
mounted() {
this.fetch();
console.log("this.model ==> ", this.model);
}
}
</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;
}
.left-table-btn {
height: 30px;
margin-left: 15px;
}
.refresh-btn {
transition: all 0.2s ease;
}
.refresh-btn:hover {
transform: rotate(-45deg);
}
.input-ltr {
direction: ltr !important;
}
.ant-input-wrapper.ant-input-group input[type="text"]{
direction: rtl !important;
}
</style>
+7
View File
@@ -0,0 +1,7 @@
import Xtc from "./model/Xtc";
import XTbl from "./model/XTbl";
export {Xtc};
export default XTbl;
+30
View File
@@ -0,0 +1,30 @@
export interface Xtc {
title: string;
key: string;
filter(): Xtc;
filterSwitch(): Xtc;
filterSelect(items: {}[]): Xtc;
filterDate(): Xtc;
filterNumber(): Xtc;
renderSlot(slotName: string): Xtc;
export(): {};
}
export interface XTbl {
title: string;
url: string;
cols: Xtc[];
addCol(col: Xtc): void;
addAll(cols: Xtc[]): void;
}
+47
View File
@@ -0,0 +1,47 @@
import Xtc from './Xtc.js';
export default class XTbl {
title;
url;
cols;
opt;
constructor(url, title) {
this.url = url;
this.title = title;
}
add(col) {
if (!this.cols) {
this.cols = [];
}
this.cols.push(col);
}
addOpt(col, slotEdit, slotDelete, ridEdit, ridDelete) {
this.opt = new Xtc('opt', 'عملیات').noSort();
this.opt.opt = true;
this.opt.slotEdit = slotEdit;
this.opt.slotDelete = slotDelete;
this.cols.push(this.opt)
return this;
}
hidePagination() {
this._hidePageination = true;
return this;
}
hideSearch() {
this._hidePageination = true;
return this;
}
addExtraBtn(btn) {
this._hidePageination = true;
return this;
}
}
+97
View File
@@ -0,0 +1,97 @@
export default class Xtc {
constructor(key, title) {
this.key = key;
this.title = title;
this.dataIndex = key;
this.show = true;
this.sorter = true;
}
export() {
let exp = {};
exp['key'] = this.key;
exp['dataIndex'] = this.key;
exp['title'] = this.title;
if (this._slots) {
exp['scopedSlots'] = this._slots;
}
if (this.filter) {
this.filter = this.filterType;
this.filtered = true;
}
return exp;
}
renderSlot(name) {
this._slots = {
customRender: name
};
if (!this.scopedSlots) {
this.scopedSlots = {}
}
this.scopedSlots.customRender = name;
return this;
}
__filter() {
this.filtered = true;
if (!this._slots) {
this.scopedSlots = {};
}
this.scopedSlots.filterDropdown = `${this.key}_filterDropdown`;
}
filterText() {
this.__filter();
this.filterType = 'default';
return this;
}
filterSwitch() {
this.__filter();
this.filterType = 'switch';
return this;
}
filterQuery(action) {
this.__filter();
this.filterType = 'query';
this.filterAction = action;
return this;
}
filterDate() {
this.__filter();
this.filterType = 'date';
return this;
}
filterSelect(items) {
this.__filter();
this.filterType = 'select';
this.filterItems = items;
return this;
}
filterNumber() {
this.__filter();
this.filterType = 'number';
return this;
}
noSort() {
this.sorter = false;
return this;
}
hidden() {
this.show = false;
return this;
}
}
+15
View File
@@ -0,0 +1,15 @@
import XTbl, {Xtc} from './index';
const tbl = new XTbl('http://kheyr.flut.ir/api/get-person', 'جدول مشتریان');
tbl.add(new Xtc('id', 'آیدی').filterNumber());
tbl.add(new Xtc('title', 'عنوان').filterText())
tbl.add(new Xtc('fname', 'نام کوچک').filterText())
tbl.add(new Xtc('lname', 'نام بزرگ').filterText())
tbl.add(new Xtc('ncode', 'کد ملی').filterText().hidden().noSort())
tbl.add(new Xtc('birth_date', 'تاریخ تولد').filterDate().hidden().noSort())
tbl.add(new Xtc('sts_str', 'وضعیت').renderSlot('sts_str').filterSelect({1: 'گزینه اول'}).noSort())
tbl.add(new Xtc('active', 'فعال').filterSwitch().hidden().noSort())
tbl.add(new Xtc('wow', 'فعال').filterSwitch().renderSlot('wow').noSort())
tbl.addOpt()
export default tbl;
+122
View File
@@ -0,0 +1,122 @@
<template>
<div>
<a-upload
name="آپلود تصویر"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:before-upload="beforeUpload"
:customRequest="req"
:file-list="files"
>
<div
class="h-full"
@mouseenter="visibleDelete = true"
@mouseout="visibleDelete = false"
>
<div v-if="imageUrl" >
<img :src="imageUrl" alt="avatar"/>
<i class="feather icon-trash" v-if="visibleDelete" @click.stop="removeFile()"/>
</div>
<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'"/>
<div class="ant-upload-text">
انتخاب تصویر
</div>
</div>
</div>
</a-upload>
</div>
</template>
<script>
import axios from "../../axios";
export default {
name: "x-upload",
props: {
url: {type: String},
maxSize: {default: 10},
value: {default: []},
fileTypes: {default: ['image/jpeg', 'image/png','audio/mpeg','audio/ogg']},
file:{type: Object}
},
data() {
return {
files: [],
loading: false,
visibleDelete: false,
imageUrl: '',
};
},
mounted() {
this.imageUrl = this.value;
console.log(this.file,'this.file')
},
methods: {
removeFile() {
this.imageUrl = undefined;
},
async req(input) {
try {
console.log(input,'input1 input');
input.loading = true;
let formData = new FormData();
formData.append("data[file]", input.file);
formData.append("data[has_thumb]", 0);
formData.append("data[has_watermark]", 1);
formData.append("data[type]", 1);
const {data} = await axios.post(this.url,formData, {'Content-Type': 'multipart/form-data'});
this.imageUrl = data.src;
this.$emit('input', data.src);
this.$emit('change', data);
} catch (e) {
this.$message.error('خطا در بارگذاری اطلاعات');
} finally {
input.loading = false;
}
},
beforeUpload(file) {
const isJpgOrPng = this.fileTypes.some((item) => item === file.type);
if (!isJpgOrPng) {
this.$message.error('نوع فایل قابل قبول نیست.');
}
const isLt2M = file.size / 1024 / 1024 < this.maxSize;
if (!isLt2M) {
this.$message.error(`سایز فایل نمی تواند بیشتر از ${this.maxSize} باشد.`);
}
return isJpgOrPng && isLt2M;
},
}
}
</script>
<style scoped>
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.ant-upload {
width: 150px;
height: 150px;
}
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<div>
<a-upload
name="آپلود تصویر"
list-type="picture-card"
class="avatar-uploader"
:show-upload-list="false"
:before-upload="beforeUpload"
:customRequest="req"
:file-list="files"
>
<div
class="h-full"
@mouseenter="visibleDelete = true"
@mouseout="visibleDelete = false"
>
<div v-if="imageUrl" >
<img :src="imageUrl" alt="avatar"/>
<i class="feather icon-trash" v-if="visibleDelete" @click.stop="removeFile()"/>
</div>
<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'"/>
<div class="ant-upload-text">
انتخاب تصویر
</div>
</div>
</div>
</a-upload>
</div>
</template>
<script>
import axios from "../../axios";
export default {
name: "x-upload",
props: {
url: {type: String},
maxSize: {default: 5},
value: {default: []},
fileTypes: {default: ['image/jpeg', 'image/png']},
file:{type: Object}
},
data() {
return {
files: [],
loading: false,
visibleDelete: false,
imageUrl: '',
};
},
mounted() {
this.imageUrl = this.value;
console.log(this.file,'this.file')
},
methods: {
removeFile() {
this.imageUrl = undefined;
},
async req(input) {
try {
input.loading = true;
let formData = new FormData();
this.file = (this.file !== undefined && this.file) ? this.file : ''
formData.append("data[file]", input.file);
formData.append("data[has_thumb]", 0);
formData.append("data[has_watermark]", 1);
formData.append("data[alt]",null);
formData.append("data[description]",null);
formData.append("data[subject]", 2);
formData.append("data[business_card]", 1);
const {data} = await axios.post(this.url,formData, {'Content-Type': 'multipart/form-data'});
this.imageUrl = data.src;
this.$emit('input', data.src);
this.$emit('change', data);
} catch (e) {
this.$message.error('خطا در بارگذاری اطلاعات');
} finally {
input.loading = false;
}
},
beforeUpload(file) {
const isJpgOrPng = this.fileTypes.some((item) => item === file.type);
if (!isJpgOrPng) {
this.$message.error('نوع فایل قابل قبول نیست.');
}
const isLt2M = file.size / 1024 / 1024 < this.maxSize;
if (!isLt2M) {
this.$message.error(`سایز فایل نمی تواند بیشتر از ${this.maxSize} باشد.`);
}
return isJpgOrPng && isLt2M;
},
}
}
</script>
<style scoped>
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.ant-upload {
width: 150px;
height: 150px;
}
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>