Init project
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,7 @@
|
||||
import Xtc from "./model/Xtc";
|
||||
import XTbl from "./model/XTbl";
|
||||
|
||||
export {Xtc};
|
||||
|
||||
export default XTbl;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user