ilk commit
This commit is contained in:
17
src/components/global/Breadcrumb.vue
Normal file
17
src/components/global/Breadcrumb.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="breadcrumb-wrapper">
|
||||
<icon-button
|
||||
@click="$router.go(-1)"
|
||||
icon="arrow"
|
||||
classList="breadcrumbs-back breadcrumb-link" />
|
||||
<div class="breadcrumbs-list">
|
||||
<icon-button toRoute="/" icon="dashboard" classList="breadcrumbs-dashboard" />
|
||||
<span>{{ currentPageText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
currentPageText: string
|
||||
}>()
|
||||
</script>
|
||||
225
src/components/global/DataTable.vue
Normal file
225
src/components/global/DataTable.vue
Normal file
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div class="list-wrapper">
|
||||
<table class="table-border table-colored table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-head-row-number" v-if="rowNumber">
|
||||
<div class="table-head-content">Sıra No</div>
|
||||
</th>
|
||||
<template v-for="(headCell, h) in tableHeader">
|
||||
<th :style="headCell.style || ''">
|
||||
<div
|
||||
:class="[
|
||||
'table-head-content',
|
||||
headCell.sort !== undefined && headCell.sort ? 'clickable' : ''
|
||||
]"
|
||||
@click="SortColumn(headCell)">
|
||||
<span>{{ headCell.title }}</span>
|
||||
<template
|
||||
v-if="
|
||||
headCell.sort !== undefined && headCell.sort && tableData.length > 1
|
||||
">
|
||||
<i
|
||||
class="ico-c ico-sort"
|
||||
v-if="
|
||||
localSort?.sortOrder === '' ||
|
||||
headCell.name !== localSort?.sortColumn
|
||||
">
|
||||
<svg>
|
||||
<use href="@/assets/images/icons.svg#arrowsortable" />
|
||||
</svg>
|
||||
</i>
|
||||
<i
|
||||
:class="[
|
||||
'ico-c',
|
||||
'ico-sort',
|
||||
localSort?.sortOrder === 'asc' ? 'order-asc' : ''
|
||||
]"
|
||||
v-if="
|
||||
headCell.name === localSort?.sortColumn &&
|
||||
localSort?.sortOrder !== ''
|
||||
">
|
||||
<svg>
|
||||
<use href="@/assets/images/icons.svg#arrowline" />
|
||||
</svg>
|
||||
</i>
|
||||
</template>
|
||||
</div>
|
||||
</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="tableData.length === 0 && !isPreview">
|
||||
<td :colspan="rowNumber ? tableHeader.length + 1 : tableHeader.length">
|
||||
Veri bulunamadı
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-else
|
||||
v-for="(dataRow, i) in tableData"
|
||||
@click="LocalRowAction(dataRow)"
|
||||
:class="[rowAction !== undefined && rowAction !== '' ? 'clickable' : '']">
|
||||
<td v-if="rowNumber">
|
||||
{{
|
||||
showPagination && localPagination.pageNumber !== undefined
|
||||
? (Number(localPagination.pageNumber) - 1) *
|
||||
Number(localPagination.pageSize) +
|
||||
i +
|
||||
1
|
||||
: i + 1
|
||||
}}
|
||||
</td>
|
||||
<slot :name="'dataRow' + i" :rowData="dataRow" :rowIndex="i">
|
||||
<template v-for="(dataCell, j) in tableHeader">
|
||||
<td v-if="dataCell.computeHtml === undefined">
|
||||
<slot
|
||||
:name="'dataCell' + j"
|
||||
:cellData="CellData(dataRow, dataCell.name)"
|
||||
:cellIndex="j">
|
||||
{{
|
||||
dataCell.compute !== undefined
|
||||
? dataCell.compute(dataRow)
|
||||
: dataRow[dataCell.name]
|
||||
}}
|
||||
</slot>
|
||||
</td>
|
||||
<td v-else v-html="dataCell.computeHtml(dataRow)"></td>
|
||||
</template>
|
||||
</slot>
|
||||
</tr>
|
||||
<template
|
||||
v-if="
|
||||
localTotalValues !== undefined &&
|
||||
Object.keys(localTotalValues).length > 0 &&
|
||||
tableData.length > 0
|
||||
">
|
||||
<tr>
|
||||
<td :colspan="totalValuesSpanLength" class="table-cell-align-right">
|
||||
<strong>TOPLAM:</strong>
|
||||
</td>
|
||||
<template v-for="(val, i) in tableHeader">
|
||||
<td v-if="localTotalValues[val.name] !== undefined">
|
||||
{{
|
||||
val.price
|
||||
? globalStore.toTrLocale(localTotalValues[val.name])
|
||||
: localTotalValues[val.name]
|
||||
}}
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<data-table-pagination
|
||||
v-if="pagination !== undefined && showPagination && !isPreview"
|
||||
v-model:pagination="localPagination" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
interface ITableHead {
|
||||
[key: string]: any
|
||||
title: string
|
||||
name: string
|
||||
compute?: Function
|
||||
sort?: boolean
|
||||
computeHtml?: Function
|
||||
}
|
||||
interface IPagination {
|
||||
[key: string]: any
|
||||
pageNumber?: number
|
||||
pageSize: number
|
||||
totalRecords: number
|
||||
}
|
||||
interface ISort {
|
||||
[key: string]: any
|
||||
sortColumn?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
export interface Props {
|
||||
tableHeader: ITableHead[]
|
||||
tableData: Record<string, any>
|
||||
rowAction?: Function | string
|
||||
pagination?: IPagination
|
||||
sortData?: ISort
|
||||
rowNumber?: boolean
|
||||
totalValues?: Record<string, any>
|
||||
isPreview?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
tableData: () => [],
|
||||
rowNumber: false,
|
||||
isPreview: false
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:sortData', 'update:pagination'])
|
||||
|
||||
const localSort: Ref<ISort> = ref(props.sortData as ISort)
|
||||
const localPagination: Ref<IPagination> = ref(props.pagination as IPagination)
|
||||
const localTotalValues = reactive<Record<string, any>>(
|
||||
props.totalValues as Record<string, any>
|
||||
)
|
||||
|
||||
const totalValuesSpanLength = ref<number>(0)
|
||||
|
||||
if (localTotalValues !== undefined) {
|
||||
let totalValuesLength = Object.keys(localTotalValues).length
|
||||
let headerLength: number =
|
||||
props.rowNumber !== undefined
|
||||
? props.tableHeader.length + 1
|
||||
: props.tableHeader.length
|
||||
totalValuesSpanLength.value = headerLength - totalValuesLength
|
||||
}
|
||||
|
||||
const showPagination = computed<boolean>(() => {
|
||||
if (props.pagination !== undefined) {
|
||||
if (Math.ceil(localPagination.value.totalRecords / globalStore.perPage) > 1)
|
||||
return true
|
||||
else return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const LocalRowAction = (d: Record<string, any>) => {
|
||||
if (props.rowAction !== undefined) {
|
||||
(props.rowAction! as Function)(d)
|
||||
}
|
||||
}
|
||||
|
||||
const SortColumn = (d: Record<string, any>) => {
|
||||
if (d.sort && props.tableData.length > 1) {
|
||||
let order = ''
|
||||
if (localSort.value.sortColumn === d.name) {
|
||||
if (localSort.value.sortOrder === '') order = 'desc'
|
||||
if (localSort.value.sortOrder === 'desc') order = 'asc'
|
||||
if (localSort.value.sortOrder === 'asc') {
|
||||
order = ''
|
||||
delete localSort.value.sortColumn
|
||||
}
|
||||
localSort.value.sortOrder = order
|
||||
} else {
|
||||
localSort.value.sortColumn = d.name
|
||||
localSort.value.sortOrder = 'desc'
|
||||
}
|
||||
emit('update:sortData', localSort.value)
|
||||
}
|
||||
}
|
||||
const CellData = (d: Record<string, any>, key: string): any => {
|
||||
if (d[key] === null) return d
|
||||
else return d[key]
|
||||
}
|
||||
|
||||
watch(
|
||||
() => localPagination,
|
||||
() => {
|
||||
emit('update:pagination', localPagination.value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
88
src/components/global/DataTablePagination.vue
Normal file
88
src/components/global/DataTablePagination.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="list-paging-wrapper">
|
||||
<div class="pagination-content">
|
||||
<icon-button
|
||||
classList="pagination-nav pagination-nav-prev"
|
||||
@click="changePage('<')"
|
||||
icon="arrow" />
|
||||
<div class="pagination-input">
|
||||
<input
|
||||
type="text"
|
||||
class="pagination-current-page"
|
||||
:value="pageNumber"
|
||||
@keydown="validationStore.allowNumbersWithKeys"
|
||||
@keyup="InputPageControl($event)"
|
||||
@focus="PageNumberFocus($event)"
|
||||
min="1"
|
||||
:max="totalPage()" />
|
||||
</div>
|
||||
<div class="pagination-total-page">
|
||||
/
|
||||
<span>{{ totalPage() }}</span>
|
||||
</div>
|
||||
<div class="button-c" @click="getPage">Git</div>
|
||||
<icon-button
|
||||
classList="pagination-nav pagination-nav-next"
|
||||
@click="changePage('>')"
|
||||
icon="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
import { useValidationStore } from '@/stores/validationStore'
|
||||
|
||||
const globalStore = useGlobalStore()
|
||||
const validationStore = useValidationStore()
|
||||
|
||||
interface IPagination {
|
||||
pageNumber: number
|
||||
pageSize: number
|
||||
totalRecords: number
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
pagination: IPagination
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:pagination'])
|
||||
|
||||
const localPagination: Ref<IPagination> = ref(props.pagination)
|
||||
const pageNumber: Ref<number> = ref(1)
|
||||
|
||||
const totalPage = (): number => {
|
||||
return Math.ceil(localPagination.value.totalRecords / globalStore.perPage)
|
||||
}
|
||||
|
||||
const changePage = (v: string) => {
|
||||
if (v === '<') {
|
||||
if (Number(pageNumber.value) > 1) pageNumber.value--
|
||||
} else {
|
||||
if (Number(pageNumber.value) < totalPage()) pageNumber.value++
|
||||
}
|
||||
localPagination.value.pageNumber = pageNumber.value
|
||||
emit('update:pagination', localPagination.value)
|
||||
}
|
||||
|
||||
const InputPageControl = (e: Event) => {
|
||||
if (Number((e.target as HTMLInputElement).value) < 1)
|
||||
(e.target as HTMLInputElement).value = '1'
|
||||
if (Number((e.target as HTMLInputElement).value) > totalPage())
|
||||
(e.target as HTMLInputElement).value = String(totalPage())
|
||||
pageNumber.value = Number((e.target as HTMLInputElement).value)
|
||||
|
||||
if((e as KeyboardEvent).key === 'Enter') getPage()
|
||||
}
|
||||
const PageNumberFocus = (e: Event) => {
|
||||
;(e.target as HTMLInputElement).select()
|
||||
}
|
||||
const getPage = () => {
|
||||
localPagination.value.pageNumber = pageNumber.value
|
||||
emit('update:pagination', localPagination.value)
|
||||
}
|
||||
</script>
|
||||
136
src/components/global/DialogItem.vue
Normal file
136
src/components/global/DialogItem.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div class="dialog-w">
|
||||
<span class="dialog-close" @click="CloseDialog"></span>
|
||||
<div class="dialog-c">
|
||||
<div
|
||||
class="dialog-header"
|
||||
v-if="localData.title !== undefined && localData.title !== ''">
|
||||
<h3>{{ localData.title }}</h3>
|
||||
</div>
|
||||
<div
|
||||
class="dialog-content"
|
||||
v-if="localData.content !== undefined && localData.content !== ''">
|
||||
{{ localData.content }}
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<template
|
||||
v-if="localData.buttons !== undefined && localData.buttons.length > 0"
|
||||
v-for="(button, i) in dialogData.buttons">
|
||||
<button
|
||||
:class="['button-c', 'button-' + button.type]"
|
||||
@click="button.function(id, button.data)">
|
||||
{{ button.label }}
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-if="localData.showClose"
|
||||
class="button-c button-cancel"
|
||||
@click="localData.closeFunction">
|
||||
{{ localData.closeText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialog-shadow" @click="CloseDialog"></div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, reactive } from 'vue'
|
||||
import { useDialogStore } from './dialogStore'
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
export interface Props {
|
||||
id: number
|
||||
dialogData: Record<string, any>
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
const localData = reactive({
|
||||
title: '',
|
||||
content: '',
|
||||
showClose: true,
|
||||
closeText: 'Kapat',
|
||||
closeFunction: () => {
|
||||
CloseDialog()
|
||||
},
|
||||
buttons: []
|
||||
})
|
||||
|
||||
const CreateData = () => {
|
||||
Object.assign(localData, props.dialogData)
|
||||
}
|
||||
|
||||
const CloseDialog = () => {
|
||||
delete dialogStore.dialogs[props.id]
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
CreateData()
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.dialog-w {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
.dialog-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
.dialog-c {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
max-width: 480px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
padding: 24px;
|
||||
}
|
||||
.dialog-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.dialog-content {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
}
|
||||
.dialog-close {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dialog-close::before,
|
||||
.dialog-close::after {
|
||||
content: '';
|
||||
flex-direction: block;
|
||||
width: 32px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.dialog-close::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
</style>
|
||||
12
src/components/global/Dialogs.vue
Normal file
12
src/components/global/Dialogs.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<template v-if="Object.keys(dialogStore.dialogs).length > 0">
|
||||
<template v-for="(dialog, i) in dialogStore.dialogs">
|
||||
<dialog-item :id="dialog.id" :dialogData="dialog.data" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import DialogItem from './DialogItem.vue'
|
||||
import { useDialogStore } from './dialogStore'
|
||||
const dialogStore = useDialogStore()
|
||||
</script>
|
||||
103
src/components/global/FileListItem.vue
Normal file
103
src/components/global/FileListItem.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<template v-if="!onlyPreview">
|
||||
<div :class="['file-item']">
|
||||
<div
|
||||
class="file-item-title"
|
||||
v-if="localData[title as string] !== undefined && localData[title as string] !== ''">
|
||||
{{ localData[title as string] }}
|
||||
</div>
|
||||
<div class="file-item-file-c">
|
||||
<a :href="localFilePath" target="_blank" class="file-item-file clickable">
|
||||
<div :class="['file-item-preview form-item-picture small-picture']">
|
||||
<div class="image-c" v-if="isPicture">
|
||||
<img :src="localFilePath" />
|
||||
</div>
|
||||
<div v-else>{{ fileType.toLocaleUpperCase() }}</div>
|
||||
</div>
|
||||
<div class="file-item-name">
|
||||
{{ fileName }}
|
||||
</div>
|
||||
</a>
|
||||
<i
|
||||
class="ico-c ico-section ico-section-header-btn ico-replace"
|
||||
@click="OnClick"
|
||||
v-if="editable">
|
||||
<svg><use href="/src/assets/images/icons.svg#replace"></use></svg>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="form-item-picture only-image clickable">
|
||||
<a :href="localFilePath" target="_blank">
|
||||
<div class="image-c" v-if="isPicture">
|
||||
<img :src="localFilePath" />
|
||||
</div>
|
||||
<div v-else>{{ fileType.toLocaleUpperCase() }}</div>
|
||||
</a>
|
||||
<i
|
||||
class="ico-c ico-section ico-section-header-btn ico-replace"
|
||||
@click="OnClick"
|
||||
v-if="editable">
|
||||
<svg><use href="/src/assets/images/icons.svg#replace"></use></svg>
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
export interface Props {
|
||||
data: Record<string, any>
|
||||
onlyPreview?: boolean
|
||||
filePath: string | null | undefined
|
||||
title?: string
|
||||
editable?: boolean
|
||||
usePath?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
onlyPreview: false,
|
||||
editable: true,
|
||||
usePath: false
|
||||
})
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const localData = reactive<Record<string, any>>(props.data)
|
||||
const localFilePath = ref<string>('')
|
||||
|
||||
const fileType = ref<string>('')
|
||||
const fileName = ref<string>('')
|
||||
const isPicture = ref<boolean>(true)
|
||||
|
||||
if (props.filePath !== undefined && props.filePath !== null) {
|
||||
localFilePath.value = props.usePath
|
||||
? props.filePath as string
|
||||
: (axios.defaults.baseURL as string + props.filePath as string) as string
|
||||
|
||||
fileType.value = globalStore.FileType(localFilePath.value)
|
||||
fileName.value = globalStore.FileName(localFilePath.value)
|
||||
isPicture.value = globalStore.IsImage(localFilePath.value)
|
||||
}
|
||||
|
||||
const OnClick = () => {
|
||||
let d = {
|
||||
fileType: fileType.value,
|
||||
fileName: fileName.value
|
||||
}
|
||||
Object.assign(localData, d)
|
||||
emit('click', localData)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.filePath,
|
||||
() => {
|
||||
localFilePath.value = props.usePath
|
||||
? props.filePath as string
|
||||
: (axios.defaults.baseURL as string + props.filePath as string) as string
|
||||
}
|
||||
)
|
||||
</script>
|
||||
125
src/components/global/FormCheckbox.vue
Normal file
125
src/components/global/FormCheckbox.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<slot name="input">
|
||||
<template v-if="listData !== undefined">
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<label :class="['label-radio', disabled ? 'disabled' : '']" v-for="(item, i) in listData">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="localValue"
|
||||
:value="item[listVal!]"
|
||||
:disabled="disabled"
|
||||
:checked="item[listVal!] === localValue"
|
||||
@click="OnClick"
|
||||
@change="OnChange($event)"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]" />
|
||||
<slot name="radiotext" :itemData="item">
|
||||
<slot :name="'checktext' + i">
|
||||
<span>{{ item[listText!] }}</span>
|
||||
</slot>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label :class="['label-radio', disabled ? 'disabled' : '']">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="localValue"
|
||||
:value="val"
|
||||
:disabled="disabled"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]" />
|
||||
<slot name="checktext">
|
||||
<span>{{ label }}</span>
|
||||
</slot>
|
||||
</label>
|
||||
</template>
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
|
||||
interface CheckObj {
|
||||
[key: string]: any
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
listData?: CheckObj[]
|
||||
listText?: string
|
||||
listVal?: string
|
||||
val?: number | string | boolean
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: (string | number)[] | boolean | string | number | null
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'click'])
|
||||
|
||||
const localValue = ref<(string | number)[] | boolean | null | string | number>(
|
||||
props.modelValue
|
||||
)
|
||||
|
||||
const InvalidMessageText = reactive<Record<string, any>>({})
|
||||
const InvalidMessages = computed<string>(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnChange = (e: Event) => {
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e)
|
||||
}
|
||||
|
||||
const OnClick = (e: Event) => {
|
||||
emit('click', e)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
Object.assign(InvalidMessageText, {})
|
||||
InvalidMessageText.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
253
src/components/global/FormDate.vue
Normal file
253
src/components/global/FormDate.vue
Normal file
@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<label>
|
||||
<span v-if="label !== undefined && label !== ''">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<input
|
||||
:value="localValue"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:min="localMin"
|
||||
:max="localMax"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
@focus="OnFocus"
|
||||
@keydown="OnKeyDown"
|
||||
@keyup="OnKeyUp"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]" />
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onBeforeMount } from 'vue'
|
||||
import { useDateStore } from '@/stores/dateStore'
|
||||
const dateStore = useDateStore()
|
||||
|
||||
export interface Props {
|
||||
type?: string
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: number | string | Date | null | undefined
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
modelKey?: string
|
||||
min?: string | Date | null
|
||||
max?: string | Date | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'datetime-local',
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'focus',
|
||||
'click',
|
||||
'keydown',
|
||||
'keyup'
|
||||
])
|
||||
const localValue = ref<any>()
|
||||
const localMin = ref<string>()
|
||||
const localMax = ref<string>()
|
||||
|
||||
const SetMin = () => {
|
||||
if (props.min !== undefined && props.min !== null) {
|
||||
if (props.type === 'datetime-local') {
|
||||
let dmin = new Date(props.min)
|
||||
dmin.setHours(0, 0, 0, 0)
|
||||
localMin.value = dateStore.dateFormat({
|
||||
date: dmin,
|
||||
pattern: 'yy-mm-dd-t',
|
||||
splitTime: 'T',
|
||||
splitDate: '-'
|
||||
})
|
||||
} else {
|
||||
localMin.value = dateStore.dateFormat({
|
||||
date: props.min,
|
||||
pattern: 'yy-mm-dd',
|
||||
splitDate: '-'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SetMax = () => {
|
||||
if (props.max !== undefined && props.max !== null) {
|
||||
if (props.type === 'datetime-local') {
|
||||
let dmax = new Date(props.max)
|
||||
dmax.setHours(0, 0, 0, 0)
|
||||
localMax.value = dateStore.dateFormat({
|
||||
date: props.max,
|
||||
pattern: 'yy-mm-dd-t',
|
||||
splitTime: 'T',
|
||||
splitDate: '-'
|
||||
})
|
||||
} else {
|
||||
localMax.value = dateStore.dateFormat({
|
||||
date: props.max,
|
||||
pattern: 'yy-mm-dd',
|
||||
splitDate: '-'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.type === 'date') {
|
||||
if (props.modelValue !== undefined && props.modelValue !== null) {
|
||||
if ((props.modelValue as string).includes('T')) {
|
||||
localValue.value = (props.modelValue as string).split('T')[0]
|
||||
} else {
|
||||
localValue.value = props.modelValue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
localValue.value = props.modelValue
|
||||
}
|
||||
|
||||
const InvalidMessageText = ref<Record<string,any>>({})
|
||||
const InvalidMessages = computed<string>(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText.value).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText.value[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnChange = (e: Event) => {
|
||||
let val = (e.target as HTMLInputElement).value
|
||||
if (val !== undefined && val !== '') {
|
||||
if (localMin.value !== undefined) {
|
||||
if (val < localMin.value) {
|
||||
let min: string
|
||||
if (props.type === 'datetime-local') {
|
||||
min = dateStore.dateFormat({
|
||||
date: localMin.value,
|
||||
pattern: 'dd-mm-yy-t'
|
||||
})
|
||||
} else {
|
||||
min = dateStore.dateFormat({
|
||||
date: localMin.value,
|
||||
pattern: 'dd-mm-yy'
|
||||
})
|
||||
}
|
||||
InvalidMessageText.value[
|
||||
props.modelKey + 'Min'
|
||||
] = `Girdiğiniz tarih ${min} tarihinden büyük olmalıdır.`
|
||||
} else {
|
||||
delete InvalidMessageText.value[props.modelKey + 'Min']
|
||||
}
|
||||
}
|
||||
if (localMax.value !== undefined) {
|
||||
if (val > localMax.value) {
|
||||
let max: string
|
||||
if (props.type === 'datetime-local') {
|
||||
max = dateStore.dateFormat({
|
||||
date: localMax.value,
|
||||
pattern: 'dd-mm-yy-t'
|
||||
})
|
||||
} else {
|
||||
max = dateStore.dateFormat({
|
||||
date: localMax.value,
|
||||
pattern: 'dd-mm-yy'
|
||||
})
|
||||
}
|
||||
InvalidMessageText.value[
|
||||
props.modelKey + 'Max'
|
||||
] = `Girdiğiniz tarih ${max} tarihinden küçük olmalıdır.`
|
||||
} else {
|
||||
delete InvalidMessageText.value[props.modelKey + 'Max']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localValue.value = (e.target as HTMLInputElement).value
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e)
|
||||
}
|
||||
const OnFocus = (e: Event) => {
|
||||
emit('focus', e)
|
||||
}
|
||||
const OnClick = (e: Event) => {
|
||||
emit('click', e)
|
||||
}
|
||||
const OnKeyDown = (e: Event) => {
|
||||
localValue.value = (e.target as HTMLInputElement).value
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('keydown', e)
|
||||
}
|
||||
const OnKeyUp = (e: Event) => {
|
||||
localValue.value = (e.target as HTMLInputElement).value
|
||||
if (props.required) {
|
||||
if (localValue.value !== undefined && localValue.value.length < 1) {
|
||||
InvalidMessageText.value[props.modelKey + 'Empty'] = 'Bu alan boş bırakılamaz'
|
||||
} else {
|
||||
delete InvalidMessageText.value[props.modelKey + 'Empty']
|
||||
}
|
||||
}
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('keyup', e)
|
||||
}
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
InvalidMessageText.value = {}
|
||||
InvalidMessageText.value.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.value.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.min,
|
||||
() => {
|
||||
if (props.min !== undefined && props.min !== '') {
|
||||
SetMin()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.max,
|
||||
() => {
|
||||
if (props.max !== undefined && props.max !== '') {
|
||||
SetMax()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
SetMin()
|
||||
SetMax()
|
||||
})
|
||||
</script>
|
||||
153
src/components/global/FormInput.vue
Normal file
153
src/components/global/FormInput.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<label>
|
||||
<span v-if="label !== undefined && label !== ''">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<input
|
||||
:value="modelValue"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="OnInput"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
@focus="OnFocus"
|
||||
@keydown="OnKeyDown"
|
||||
@keyup="OnKeyUp"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength" />
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { useValidationStore } from '@/stores/validationStore'
|
||||
const validationStore = useValidationStore()
|
||||
|
||||
export interface Props {
|
||||
type?: string
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: number | string | null | undefined
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
min?: string | number
|
||||
max?: string | number
|
||||
maxlength?: string | number
|
||||
minlength?: string | number
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
modelKey?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'text',
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'input',
|
||||
'focus',
|
||||
'click',
|
||||
'keydown',
|
||||
'keyup'
|
||||
])
|
||||
const localValue = ref<any>()
|
||||
const InvalidMessageText = reactive<Record<string, any>>({})
|
||||
|
||||
const InvalidMessages = computed<string>(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnChange = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('change', e)
|
||||
}
|
||||
const OnInput = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('input', e)
|
||||
}
|
||||
const OnFocus = (e: Event) => {
|
||||
emit('focus', e)
|
||||
}
|
||||
const OnClick = (e: Event) => {
|
||||
emit('click', e)
|
||||
}
|
||||
const OnKeyDown = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('keydown', e)
|
||||
}
|
||||
const OnKeyUp = (e: Event) => {
|
||||
if (props.minlength !== undefined) {
|
||||
if ((e.target as HTMLInputElement).value.length < Number(props.minlength)) {
|
||||
InvalidMessageText.minlength = `Girdiğiniz bilgi en az ${props.minlength} karakter uzunluğunda olmalı`
|
||||
} else {
|
||||
delete InvalidMessageText.minlength
|
||||
}
|
||||
}
|
||||
|
||||
if (props.required) {
|
||||
if ((e.target as HTMLInputElement).value.length < 1) {
|
||||
InvalidMessageText[props.modelKey + 'Empty'] = 'Bu alan boş bırakılamaz'
|
||||
} else {
|
||||
delete InvalidMessageText[props.modelKey + 'Empty']
|
||||
}
|
||||
}
|
||||
|
||||
if (props.type === 'email') {
|
||||
if (!validationStore.checkEmail((e.target as HTMLInputElement).value))
|
||||
InvalidMessageText.validmail =
|
||||
'Lütfen e-postayı doğru formatta giriniz. Örn: isim@alanadi.td'
|
||||
else delete InvalidMessageText.validmail
|
||||
}
|
||||
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('keyup', e)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
Object.assign(InvalidMessageText, {})
|
||||
InvalidMessageText.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
210
src/components/global/FormQuill.vue
Normal file
210
src/components/global/FormQuill.vue
Normal file
@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<label>
|
||||
<span v-if="label !== undefined && label !== ''">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<div :ref="'quillContainer' + rnd"></div>
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, useTemplateRef, watch } from 'vue'
|
||||
import Quill from 'quill'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
|
||||
declare const window: any
|
||||
|
||||
window.Quill = Quill
|
||||
|
||||
export interface Props {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: string
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
maxlength?: string
|
||||
minlength?: string
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
modelKey?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false,
|
||||
placeholder: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'text-change'])
|
||||
const rnd = ref<number>(Number(Math.random() * 10000000))
|
||||
|
||||
const toolbar = ref<any[]>([
|
||||
[{ font: [] }, { size: [] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ script: 'super' }, { script: 'sub' }],
|
||||
[{ header: '1' }, { header: '2' }, 'blockquote'],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
|
||||
[{ align: [] }],
|
||||
['link', 'image', 'video'],
|
||||
['clean']
|
||||
])
|
||||
|
||||
const options = reactive<Record<string, any>>({
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbar.value,
|
||||
imageResize: {
|
||||
modules: ['Resize', 'DisplaySize', 'Toolbar']
|
||||
}
|
||||
},
|
||||
placeholder: props.placeholder,
|
||||
readOnly: false,
|
||||
debug: false
|
||||
})
|
||||
|
||||
const QuillImageResize = ref<any>()
|
||||
|
||||
const editor = useTemplateRef<any>('quillContainer' + rnd.value)
|
||||
const quill = ref<Quill | null>(null)
|
||||
|
||||
const localValue = ref<string>(props.modelValue)
|
||||
|
||||
const InvalidMessageText = ref<Record<string, any>>({})
|
||||
const InvalidMessages = computed(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText.value).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText.value[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnTextChange = (e: any) => {
|
||||
if (props.minlength !== undefined) {
|
||||
if (localValue.value.length < Number(props.minlength)) {
|
||||
InvalidMessageText.value.minlength = `Girdiğiniz bilgi en az ${props.minlength} karakter uzunluğunda olmalı`
|
||||
} else {
|
||||
delete InvalidMessageText.value.minlength
|
||||
}
|
||||
}
|
||||
|
||||
if (quill.value !== null) {
|
||||
localValue.value = quill.value!.container.querySelector('.ql-editor')!.innerHTML
|
||||
}
|
||||
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e)
|
||||
}
|
||||
|
||||
const InitializeEditor = async () => {
|
||||
await setupQuillEditor()
|
||||
SetContent()
|
||||
RegisterEditorEventListeners()
|
||||
}
|
||||
|
||||
const SetContent = () => {
|
||||
if (props.modelValue) quill.value!.root.innerHTML = props.modelValue
|
||||
}
|
||||
|
||||
const setupQuillEditor = async () => {
|
||||
//let Parchment = Quill.import('parchment')
|
||||
|
||||
window.Quill.imports.parchment.Attributor.Style =
|
||||
window.Quill.imports.parchment.StyleAttributor
|
||||
|
||||
//@ts-ignore
|
||||
QuillImageResize.value = await import('quill-image-resize-module')
|
||||
|
||||
quill.value = new Quill(editor.value, options) as Quill
|
||||
;(quill.value!.getModule('toolbar') as Record<
|
||||
string,
|
||||
any
|
||||
>)!.container.addEventListener('mousedown', (e: Event) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
;(quill.value!.getModule('toolbar') as Record<
|
||||
string,
|
||||
any
|
||||
>)!.container.addEventListener('click', (e: Event) => {
|
||||
if ((e.target as HTMLElement).className !== 'ql-image') e.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
const RegisterEditorEventListeners = () => {
|
||||
quill.value!.on('text-change', OnTextChange)
|
||||
quill.value!.on('editor-change', OnTextChange)
|
||||
//image resize imaja tıklandığını anlamıyor.
|
||||
quill.value!.root.addEventListener('mouseover', OnMouseOver, false)
|
||||
ListenForEditorEvent('text-change')
|
||||
}
|
||||
const ListenForEditorEvent = (type: any) => {
|
||||
quill.value!.on(type, (...args) => {
|
||||
emit(type, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
const OnMouseOver = (e: Event) => {
|
||||
if (
|
||||
(e.target as HTMLElement)!.firstChild &&
|
||||
((e.target as HTMLElement)!.firstChild as HTMLElement)!.tagName &&
|
||||
((e.target as HTMLElement)!.firstChild as HTMLElement)!.tagName.toUpperCase() ===
|
||||
'IMG'
|
||||
) {
|
||||
;(e.target as HTMLElement)!.classList.add('remove-click-p')
|
||||
;((e.target as HTMLElement)!.firstChild as HTMLElement)!.classList.add(
|
||||
'add-click-img'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
InitializeEditor()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
InvalidMessageText.value.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.value.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.ql-toolbar {
|
||||
width: 100%;
|
||||
}
|
||||
.ql-container {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
}
|
||||
.remove-click-p {
|
||||
pointer-events: none;
|
||||
}
|
||||
.add-click-img {
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
128
src/components/global/FormRadio.vue
Normal file
128
src/components/global/FormRadio.vue
Normal file
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<slot name="input">
|
||||
<template v-if="listData !== undefined">
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<label
|
||||
:class="['label-radio', disabled ? 'disabled' : '']"
|
||||
v-for="(item, i) in listData">
|
||||
<input
|
||||
type="radio"
|
||||
:name="'radio' + rnd"
|
||||
v-model="localValue"
|
||||
:value="item[listVal!]"
|
||||
:disabled="disabled"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]" />
|
||||
<slot name="radiotext" :itemData="item">
|
||||
<slot :name="'radiotext' + i">
|
||||
<span>{{ item[listText!] }}</span>
|
||||
</slot>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label :class="['label-radio', disabled ? 'disabled' : '']">
|
||||
<input
|
||||
type="radio"
|
||||
:name="group"
|
||||
v-model="localValue"
|
||||
:value="val"
|
||||
:disabled="disabled"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]" />
|
||||
<slot name="radiotext">
|
||||
<span>{{ label }}</span>
|
||||
</slot>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
|
||||
interface RadioObj {
|
||||
[key: string]: any
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
listData?: RadioObj[]
|
||||
listText?: string
|
||||
listVal?: string
|
||||
val?: number | string | boolean
|
||||
group?: string
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: string | number | boolean | null
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'click'])
|
||||
const localValue = ref<string | number | boolean | null>(props.modelValue)
|
||||
|
||||
const rnd = ref<number>(Number(Math.floor(Math.random() * 1000000000)))
|
||||
const InvalidMessageText = reactive<Record<string, any>>({})
|
||||
const InvalidMessages = computed<string>(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnChange = (e: Event) => {
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e)
|
||||
}
|
||||
|
||||
const OnClick = (e: Event) => {
|
||||
emit('click', e)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
Object.assign(InvalidMessageText, {})
|
||||
InvalidMessageText.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
393
src/components/global/FormSelect.vue
Normal file
393
src/components/global/FormSelect.vue
Normal file
@ -0,0 +1,393 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'form-item',
|
||||
half ? 'form-item-half' : '',
|
||||
elclass || '',
|
||||
$slots.button ? 'form-item-wb' : ''
|
||||
]"
|
||||
:id="'formselect' + rnd">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<label class="form-select-list-c">
|
||||
<span v-if="label !== undefined && label !== ''">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<div
|
||||
tabindex="0"
|
||||
:class="[
|
||||
'form-select-activator',
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || '',
|
||||
disabled ? 'disabled' : '',
|
||||
disabledClass || ''
|
||||
]"
|
||||
v-if="!activated"
|
||||
@click="OpenList">
|
||||
<slot name="activator" :activatorData="selectedOption">
|
||||
{{ activatorText }}
|
||||
</slot>
|
||||
<i
|
||||
class="ico-c ico-close"
|
||||
v-if="
|
||||
clearable &&
|
||||
localValue !== '' &&
|
||||
localValue !== undefined &&
|
||||
localValue !== null
|
||||
"
|
||||
@click="ClearSelection($event)">
|
||||
<svg><use href="/src/assets/images/icons.svg#close"></use></svg>
|
||||
</i>
|
||||
<i class="ico-c">
|
||||
<svg><use href="/src/assets/images/icons.svg#arrow"></use></svg>
|
||||
</i>
|
||||
</div>
|
||||
<div class="form-select-input" v-if="activated">
|
||||
<form-input v-model="q" placeholder="Listede arayın" />
|
||||
</div>
|
||||
<teleport to="body" v-if="activated">
|
||||
<div class="form-select-list" :id="'selectlist' + rnd">
|
||||
<i
|
||||
class="ico-c ico-close select-close-selection"
|
||||
@click="CloseListFromSelection">
|
||||
<svg><use href="/src/assets/images/icons.svg#close"></use></svg>
|
||||
</i>
|
||||
<div class="form-select-list-item" v-if="selectList.length === 0">
|
||||
<span>Sonuç Bulunamadı</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
class="form-select-list-item"
|
||||
v-if="multiple"
|
||||
@click="MultipleSelectAll($event)">
|
||||
<input
|
||||
class="select-box-check"
|
||||
type="checkbox"
|
||||
v-model="multipleAllSelected" />
|
||||
<span>{{ multipleText }}</span>
|
||||
</div>
|
||||
<template v-for="(item, i) in selectList">
|
||||
<div
|
||||
v-if="localView(item)"
|
||||
:class="['form-select-list-item', isSelected(item) ? 'selected' : '']"
|
||||
@click="updateVal($event, item)">
|
||||
<input
|
||||
v-if="multiple"
|
||||
class="select-box-check"
|
||||
type="checkbox"
|
||||
:value="listVal !== undefined ? item[listVal] : item"
|
||||
v-model="localValue" />
|
||||
<slot name="option" :optionData="item">
|
||||
<span>{{ listText !== undefined ? item[listText] : item }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</teleport>
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</label>
|
||||
<div v-if="$slots.button" class="form-item-button"><slot name="button"></slot></div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
export interface Props {
|
||||
listData: Record<string, any>[]
|
||||
listText?: string
|
||||
listVal?: string
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: string | number | number[] | string[] | null
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
clearable?: boolean
|
||||
extraData?: any
|
||||
multiple?: boolean
|
||||
multipleText?: string
|
||||
view?: Function
|
||||
disabledClass?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
listData: () => [],
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false,
|
||||
clearable: false,
|
||||
extraData: '',
|
||||
multiple: false,
|
||||
multipleText: 'Tümü'
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change','clear'])
|
||||
|
||||
const activated = ref<Boolean>(false)
|
||||
const multipleAllSelected = ref<boolean>(false)
|
||||
const q = ref<string>('')
|
||||
const rnd = ref<number>(Number(Math.floor(Math.random() * 1000000000)))
|
||||
const localValue = ref<string | number | boolean | null | string[]>()
|
||||
const selectedOption = ref<Record<string, any>>({})
|
||||
|
||||
if (props.multiple) {
|
||||
if (props.modelValue === null || props.modelValue === undefined) {
|
||||
localValue.value = [] as string[]
|
||||
} else {
|
||||
localValue.value = props.modelValue as string[]
|
||||
}
|
||||
} else {
|
||||
localValue.value = props.modelValue as string
|
||||
}
|
||||
|
||||
const localView = (data: Record<string, any>) => {
|
||||
if (props.view !== undefined) {
|
||||
if (props.extraData !== undefined) return props.view(data, props.extraData)
|
||||
else return props.view(data, props)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const localExtra = ref<any>(props.extraData)
|
||||
|
||||
const InvalidMessageText = reactive<Record<string, any>>({})
|
||||
|
||||
const InvalidMessages = computed<string>(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
const activatorText = computed<any>(() => {
|
||||
if (
|
||||
localValue.value === '' ||
|
||||
localValue.value === null ||
|
||||
localValue.value === undefined
|
||||
) {
|
||||
return props.placeholder || 'Lütfen Seçim Yapınız'
|
||||
} else {
|
||||
if (props.multiple) {
|
||||
if (multipleAllSelected.value) {
|
||||
return props.multipleText
|
||||
} else {
|
||||
let textm = props.listData.filter((v: Record<string, any>) => {
|
||||
let val = props.listVal !== undefined ? v[props.listVal] : v
|
||||
return (localValue.value as string[]).includes(val)
|
||||
})
|
||||
if (textm !== undefined) {
|
||||
if (props.listVal !== undefined) {
|
||||
let listText = ''
|
||||
textm.forEach((itm: Record<string, any>) => {
|
||||
listText += itm[props.listText as string] + ', '
|
||||
})
|
||||
return listText.slice(0, -2)
|
||||
} else {
|
||||
return textm.toString()
|
||||
}
|
||||
} else {
|
||||
return 'Lütfen Seçim Yapınız'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let text = props.listData.filter((v: Record<string, any>) => {
|
||||
let val = props.listVal !== undefined ? v[props.listVal] : v
|
||||
return localValue.value === val
|
||||
})[0]
|
||||
|
||||
return text !== undefined
|
||||
? props.listText !== undefined
|
||||
? text[props.listText]
|
||||
: text
|
||||
: 'Lütfen Seçim Yapınız'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const selectList = computed<Record<string, any>[]>(() => {
|
||||
if (q.value.length > 1) {
|
||||
return props.listData.filter((v: Record<string, any>) => {
|
||||
let val = props.listText !== undefined ? v[props.listText] : v
|
||||
return globalStore.trToUpper(val).includes(globalStore.trToUpper(q.value))
|
||||
})
|
||||
} else {
|
||||
return props.listData
|
||||
}
|
||||
})
|
||||
|
||||
const isSelected = (d: Record<string, any>): boolean => {
|
||||
if (localValue.value !== null && localValue.value !== undefined) {
|
||||
let val = props.listVal !== undefined ? d[props.listVal] : d
|
||||
return props.multiple
|
||||
? (localValue.value as string[]).includes(val)
|
||||
: val === localValue.value
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const MultipleSelectAll = (e: Event) => {
|
||||
if (props.multiple) {
|
||||
if (multipleAllSelected.value) {
|
||||
localValue.value = []
|
||||
multipleAllSelected.value = false
|
||||
} else {
|
||||
props.listData.forEach((data: any, i: number) => {
|
||||
if (props.listVal !== undefined)
|
||||
(localValue.value as string[]).push(data[props.listVal])
|
||||
else (localValue.value as string[]).push(data)
|
||||
})
|
||||
multipleAllSelected.value = true
|
||||
}
|
||||
let item: Record<string, any> = {}
|
||||
emit('update:modelValue', localValue.value as string[])
|
||||
emit('change', e, localValue.value, item, localExtra.value)
|
||||
}
|
||||
}
|
||||
const ClearSelection = (e: Event) => {
|
||||
e.stopPropagation()
|
||||
if (typeof localValue.value === 'number') localValue.value = null
|
||||
if (typeof localValue.value === 'string') localValue.value = ''
|
||||
if (typeof localValue.value === 'boolean') localValue.value = false
|
||||
|
||||
let item: Record<string, any> = {}
|
||||
selectedOption.value = {}
|
||||
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e, localValue.value, item, localExtra.value)
|
||||
emit('clear', e, localValue.value)
|
||||
}
|
||||
|
||||
const CloseListFromSelection = () => {
|
||||
activated.value = false
|
||||
document.removeEventListener('click', closeList)
|
||||
}
|
||||
|
||||
const closeList = (e: Event) => {
|
||||
let p = e.composedPath && e.composedPath()
|
||||
var open = false
|
||||
if (p) {
|
||||
p.pop()
|
||||
p.forEach((pv) => {
|
||||
if (
|
||||
(pv as HTMLElement).id !== '' &&
|
||||
(pv as HTMLElement).id !== undefined &&
|
||||
(pv as HTMLElement).id.includes(String(rnd.value))
|
||||
)
|
||||
open = true
|
||||
})
|
||||
}
|
||||
if (!open) {
|
||||
activated.value = false
|
||||
document.removeEventListener('click', closeList)
|
||||
}
|
||||
}
|
||||
|
||||
const OpenList = (e: Event) => {
|
||||
if (props.disabled) return
|
||||
e.stopPropagation()
|
||||
q.value = ''
|
||||
activated.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
let actEl = document.querySelector('#formselect' + rnd.value) as HTMLElement
|
||||
let el = document.querySelector('#selectlist' + rnd.value) as HTMLElement
|
||||
let rcAct = actEl.getBoundingClientRect() as DOMRect
|
||||
|
||||
el.style.width = rcAct.width - 8 + 'px'
|
||||
el.style.left = rcAct.left + 4 + 'px'
|
||||
if (window.innerHeight - rcAct.bottom < 100) {
|
||||
el.style.bottom = window.innerHeight - rcAct.top - 24 + 'px'
|
||||
el.style.maxHeight = rcAct.top - 64 + 'px'
|
||||
} else {
|
||||
el.style.top = rcAct.bottom + 'px'
|
||||
el.style.maxHeight = window.innerHeight - rcAct.bottom - 16 + 'px'
|
||||
}
|
||||
}, 5)
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeList)
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const updateVal = (e: Event, item: Record<string, any>) => {
|
||||
e.stopPropagation()
|
||||
let val = props.listVal !== undefined ? item[props.listVal] : item
|
||||
if (props.multiple) {
|
||||
if ((localValue.value as string[]).indexOf(val) > -1)
|
||||
(localValue.value as string[]).splice(
|
||||
(localValue.value as string[]).indexOf(val),
|
||||
1
|
||||
)
|
||||
else (localValue.value as string[]).push(val)
|
||||
|
||||
multipleAllSelected.value =
|
||||
(localValue.value as string[]).length === props.listData.length
|
||||
} else {
|
||||
localValue.value = val
|
||||
activated.value = false
|
||||
document.removeEventListener('click', closeList)
|
||||
}
|
||||
selectedOption.value = item
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e, localValue.value, item, localExtra.value)
|
||||
}
|
||||
|
||||
const SetSelectedOption = () => {
|
||||
selectedOption.value = props.listData.filter((v: Record<string, any>) => {
|
||||
return v[props.listVal as string] === props.modelValue
|
||||
})[0]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.multiple) {
|
||||
multipleAllSelected.value =
|
||||
(localValue.value as string[]).length === props.listData.length
|
||||
}
|
||||
SetSelectedOption()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
Object.assign(InvalidMessageText, {})
|
||||
InvalidMessageText.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.listData,
|
||||
() => {
|
||||
if (props.modelValue !== undefined) {
|
||||
if (props.listData !== undefined) {
|
||||
SetSelectedOption()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
185
src/components/global/FormSummer.vue
Normal file
185
src/components/global/FormSummer.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<span v-if="label !== undefined && label !== ''" class="summer-label">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<div :ref="'summerContainer' + rnd" class="summer-editor"></div>
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
onMounted,
|
||||
useTemplateRef,
|
||||
watch,
|
||||
onBeforeUnmount
|
||||
} from 'vue'
|
||||
import $ from 'jquery'
|
||||
import 'summernote/dist/summernote-lite.min.css'
|
||||
import 'summernote/dist/summernote-lite.min.js'
|
||||
//import SummernoteEditor from 'vue3-summernote-editor'
|
||||
|
||||
export interface Props {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: string
|
||||
half?: boolean
|
||||
title?: string
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
maxlength?: string
|
||||
minlength?: string
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
modelKey?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
half: false,
|
||||
required: false,
|
||||
placeholder: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'text-change'])
|
||||
const rnd = ref<number>(Number(Math.random() * 10000000))
|
||||
const editorElement = useTemplateRef<any>('summerContainer' + rnd.value)
|
||||
const editor = ref<any>(null)
|
||||
|
||||
const localValue = ref<string>(props.modelValue)
|
||||
|
||||
const InvalidMessageText = ref<Record<string, any>>({})
|
||||
const InvalidMessages = computed(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText.value).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText.value[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
const SetupEditor = async () => {
|
||||
await InitializeEditor()
|
||||
SetContent()
|
||||
}
|
||||
const InitializeEditor = async () => {
|
||||
if (editorElement.value) {
|
||||
editor.value = $(editorElement.value).summernote({
|
||||
height: 300,
|
||||
codeviewFilter: false,
|
||||
codeviewIframeFilter: false,
|
||||
toolbar: [
|
||||
['style', ['style']],
|
||||
[
|
||||
'font',
|
||||
[
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'superscript',
|
||||
'subscript',
|
||||
'clear'
|
||||
]
|
||||
],
|
||||
['fontname', ['fontname']],
|
||||
['color', ['color']],
|
||||
['para', ['ul', 'ol', 'paragraph']],
|
||||
['height', ['height']],
|
||||
['table', ['table']],
|
||||
['insert', ['link', 'picture', 'video']],
|
||||
['view', ['fullscreen', 'codeview', 'undo', 'redo', 'help']]
|
||||
],
|
||||
callbacks: {
|
||||
onChange: (content: any, e: any) => {
|
||||
localValue.value = content
|
||||
OnTextChange()
|
||||
emit('update:modelValue', localValue.value)
|
||||
emit('change', e)
|
||||
},
|
||||
onInit: () => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const SetContent = () => {
|
||||
if (props.modelValue) $(editorElement.value).summernote('code', props.modelValue)
|
||||
}
|
||||
|
||||
const OnTextChange = () => {
|
||||
if (props.minlength !== undefined) {
|
||||
if (localValue.value.length < Number(props.minlength)) {
|
||||
InvalidMessageText.value.minlength = `Girdiğiniz bilgi en az ${props.minlength} karakter uzunluğunda olmalı`
|
||||
} else {
|
||||
delete InvalidMessageText.value.minlength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
SetupEditor()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
InvalidMessageText.value.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.value.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style>
|
||||
.summer-label {
|
||||
width: 100%;
|
||||
}
|
||||
.summer-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.note-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.note-editor.note-frame.fullscreen {
|
||||
background-color: #fff;
|
||||
}
|
||||
.note-color.open .note-dropdown-menu {
|
||||
display: flex;
|
||||
}
|
||||
.note-editor .note-toolbar .note-color-palette div .note-color-btn {
|
||||
min-height: inherit;
|
||||
}
|
||||
.note-editable ul {
|
||||
list-style-type: inherit;
|
||||
list-style-image: inherit;
|
||||
margin: inherit;
|
||||
padding: inherit;
|
||||
}
|
||||
.note-editable ul li {
|
||||
list-style-type: inherit;
|
||||
list-style-image: inherit;
|
||||
}
|
||||
.note-editable ol li {
|
||||
list-style-type: inherit;
|
||||
list-style-image: inherit;
|
||||
}
|
||||
</style>
|
||||
133
src/components/global/FormTextarea.vue
Normal file
133
src/components/global/FormTextarea.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div :class="['form-item', half ? 'form-item-half' : '', elclass || '']">
|
||||
<span class="form-item-title" v-if="title !== undefined && title !== ''">
|
||||
{{ title }}
|
||||
</span>
|
||||
<slot name="input">
|
||||
<label>
|
||||
<span v-if="label !== undefined && label !== ''">
|
||||
{{ label }}
|
||||
<i v-if="required" class="form-item-alert">*</i>
|
||||
</span>
|
||||
<textarea
|
||||
:value="modelValue"
|
||||
:rows="rows"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="OnInput"
|
||||
@click="OnClick"
|
||||
@change="OnChange"
|
||||
@focus="OnFocus"
|
||||
@keydown="OnKeyDown"
|
||||
@keyup="OnKeyUp"
|
||||
:class="[
|
||||
invalidText !== undefined && invalidText !== '' ? 'invalid' : '',
|
||||
iclass || ''
|
||||
]"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"></textarea>
|
||||
<span
|
||||
class="form-item-alert"
|
||||
v-if="InvalidMessages.length > 0 && InvalidMessages !== ''">
|
||||
{{ InvalidMessages }}
|
||||
</span>
|
||||
<span
|
||||
class="form-item-description"
|
||||
v-if="description !== undefined && description !== ''">
|
||||
{{ description }}
|
||||
</span>
|
||||
</label>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
export interface Props {
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
modelValue: number | string | null | undefined
|
||||
half?: boolean
|
||||
title?: string
|
||||
rows?: number
|
||||
invalidText?: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
maxlength?: string
|
||||
minlength?: string
|
||||
iclass?: string
|
||||
elclass?: string
|
||||
modelKey?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
half: false,
|
||||
rows: 4,
|
||||
required: false
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'input',
|
||||
'focus',
|
||||
'click',
|
||||
'keydown',
|
||||
'keyup'
|
||||
])
|
||||
interface InvalidMessagesObj {
|
||||
[key: string]: any
|
||||
}
|
||||
const InvalidMessageText = ref<InvalidMessagesObj>({})
|
||||
const InvalidMessages = computed(() => {
|
||||
let text = ''
|
||||
Object.keys(InvalidMessageText.value).forEach((k: string, i: number) => {
|
||||
text += InvalidMessageText.value[k] + ', '
|
||||
})
|
||||
return text
|
||||
})
|
||||
|
||||
const OnChange = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('change', e)
|
||||
}
|
||||
const OnInput = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('input', e)
|
||||
}
|
||||
const OnFocus = (e: Event) => {
|
||||
emit('focus', e)
|
||||
}
|
||||
const OnClick = (e: Event) => {
|
||||
emit('focus', e)
|
||||
}
|
||||
const OnKeyDown = (e: Event) => {
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('keydown', e)
|
||||
}
|
||||
const OnKeyUp = (e: Event) => {
|
||||
if (props.minlength !== undefined) {
|
||||
if ((e.target as HTMLInputElement)?.value.length < Number(props.minlength)) {
|
||||
InvalidMessageText.value.minlength = `Girdiğiniz bilgi en az ${props.minlength} karakter uzunluğunda olmalı`
|
||||
} else {
|
||||
delete InvalidMessageText.value.minlength
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:modelValue', (e.target as HTMLInputElement).value)
|
||||
emit('keyup', e)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.invalidText,
|
||||
() => {
|
||||
if (props.invalidText !== undefined && props.invalidText !== '') {
|
||||
InvalidMessageText.value = {}
|
||||
InvalidMessageText.value.invalid = props.invalidText
|
||||
} else {
|
||||
delete InvalidMessageText.value.invalid
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
42
src/components/global/IconButton.vue
Normal file
42
src/components/global/IconButton.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div :class="[classList]">
|
||||
<template v-if="toRoute === undefined">
|
||||
<div class="ico-clickable" @click="OnClick">
|
||||
<i :class="['ico-c', iconClass]">
|
||||
<svg>
|
||||
<use :href="icourl + '#' + icon" />
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link :to="toRoute">
|
||||
<i :class="['ico-c', iconClass]">
|
||||
<svg>
|
||||
<use :href="icourl + '#' + icon" />
|
||||
</svg>
|
||||
</i>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import icourl from '@/assets/images/icons.svg'
|
||||
|
||||
export interface Props {
|
||||
icon: string
|
||||
iconClass?: string
|
||||
classList?: string
|
||||
toRoute?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
iconClass: '',
|
||||
classList: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const OnClick = (e: Event) => {
|
||||
emit('click', e)
|
||||
}
|
||||
</script>
|
||||
437
src/components/global/ListTableContent.vue
Normal file
437
src/components/global/ListTableContent.vue
Normal file
@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
page === 'list' ? 'section-header' : '',
|
||||
page === 'form' ? 'form-part-title' : ''
|
||||
]">
|
||||
<i class="ico-c ico-section back-grad-title" v-if="icon !== undefined && icon !== ''">
|
||||
<svg height="20">
|
||||
<use :href="icourl + '#' + icon" />
|
||||
</svg>
|
||||
</i>
|
||||
<h1 v-if="title !== undefined && title !== ''">
|
||||
<span>{{ title }}</span>
|
||||
<span class="section-header-count">{{ localTotalRecord }} {{ listText }}</span>
|
||||
</h1>
|
||||
<h4 v-if="formTitle !== undefined && formTitle !== ''">{{ formTitle }}</h4>
|
||||
<div class="section-header-buttons-c collapsable" v-if="!isPreview">
|
||||
<div class="header-extra-buttons-C"><slot name="extraButtons"></slot></div>
|
||||
<div class="list-search-c" v-if="search">
|
||||
<i
|
||||
class="ico-c ico-section ico-section-header-btn btn-list-search menu-have-sub"
|
||||
@click="OpenSearchForm($event)">
|
||||
<svg>
|
||||
<use href="@/assets/images/icons.svg#search" />
|
||||
</svg>
|
||||
</i>
|
||||
<div
|
||||
v-if="searchForm"
|
||||
class="menu-sub menu-sub-top menu-sub-right menu-sub-mobile-fix section-header-search"
|
||||
:style="searchFieldPos">
|
||||
<div id="list-search-form">
|
||||
<input
|
||||
:id="'listsearch' + rnd"
|
||||
type="text"
|
||||
placeholder="Ara"
|
||||
v-model="localQuery"
|
||||
@keyup="SearchInputKeyUp" />
|
||||
<button @click="SearchQuery">
|
||||
<svg>
|
||||
<use href="@/assets/images/icons.svg#search" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-buttons-C">
|
||||
<div class="menu-have-sub section-header-mobile-menu" @click="OpenMobileButtons">
|
||||
<i class="ico-c ico-section ico-section-header-btn">
|
||||
<svg>
|
||||
<use href="@/assets/images/icons.svg#oplist" />
|
||||
</svg>
|
||||
</i>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="section-header-buttons menu-sub menu-sub-right menu-sub-top"
|
||||
v-if="mobileButtons">
|
||||
<icon-button
|
||||
v-if="export"
|
||||
classList="ico-section ico-section-header-btn"
|
||||
@click="exportPanel = !exportPanel"
|
||||
icon="export" />
|
||||
<icon-button
|
||||
v-if="filterable()"
|
||||
classList="ico-section ico-section-header-btn"
|
||||
@click="OpenFilterPanel"
|
||||
icon="filter" />
|
||||
<icon-button
|
||||
v-if="addRoute !== undefined && addRoute !== ''"
|
||||
classList="ico-section ico-section-header-btn"
|
||||
:toRoute="addRoute"
|
||||
icon="plus" />
|
||||
|
||||
<icon-button
|
||||
v-else-if="addAction !== undefined && addAction !== ''"
|
||||
classList="ico-section ico-section-header-btn"
|
||||
@click="localAddAction"
|
||||
icon="plus" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
page === 'list' ? 'section-content section-inner' : '',
|
||||
page === 'form' ? 'form-part-content' : ''
|
||||
]">
|
||||
<div class="list-filter-wrapper" v-if="filters() || searched">
|
||||
<h4>Filtreler ve Arama</h4>
|
||||
<div class="list-filter-content">
|
||||
<template v-if="localQuery !== ''">
|
||||
<div class="list-filter-item" data-filter="durum-onaylanmis">
|
||||
<strong>Arama:</strong>
|
||||
<span>
|
||||
{{ localQuery }}
|
||||
</span>
|
||||
<span class="list-filter-close" @click="RemoveSearch"></span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-for="(filter, k) in filterParams">
|
||||
<div class="list-filter-item" data-filter="durum-onaylanmis">
|
||||
<strong>{{ filter.title }}:</strong>
|
||||
<span>
|
||||
{{ filter.text || filter.val }} {{ filter.op === '=' ? '' : filter.op }}
|
||||
</span>
|
||||
<span class="list-filter-close" @click="RemoveFilterKey(k as string)"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="datatable">
|
||||
<data-table
|
||||
:tableHeader="tableHeader"
|
||||
:tableData="localTableData"
|
||||
:rowAction="rowAction"
|
||||
v-model:sortData="localSort"
|
||||
v-model:pagination="localPagination"
|
||||
:rowNumber="rowNumber"
|
||||
:totalValues="localTotalValues"
|
||||
:isPreview="isPreview" />
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<panel-wrapper v-if="filterPanel" v-model="filterPanel" :panel-title="'Filtreleme'">
|
||||
<template #panelContent>
|
||||
<panel-filter :filterHead="tableHeader" v-model:filterParams="localFilterParams" />
|
||||
</template>
|
||||
<template #footerButton>
|
||||
<div class="button-c button-save" @click="FilterData">Filtrele</div>
|
||||
</template>
|
||||
</panel-wrapper>
|
||||
<panel-wrapper v-if="exportPanel" v-model="exportPanel" :panel-title="'Dışa Aktar'">
|
||||
<template #panelContent>
|
||||
<panel-export v-model:selectedExport="selectedExport" />
|
||||
</template>
|
||||
</panel-wrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onBeforeMount, watch, computed } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
import { useDataStore } from '@/stores/dataStore'
|
||||
const dataStore = useDataStore()
|
||||
|
||||
import icourl from '@/assets/images/icons.svg'
|
||||
import PanelWrapper from '@/components/PanelWrapper.vue'
|
||||
import PanelFilter from './PanelFilter.vue'
|
||||
import PanelExport from './PanelExport.vue'
|
||||
|
||||
interface ITableHead {
|
||||
[key: string]: any
|
||||
title: string
|
||||
name: string
|
||||
compute?: Function
|
||||
computeHtml?: Function
|
||||
sort?: boolean
|
||||
filter?: Record<string, any>
|
||||
}
|
||||
interface IPagination {
|
||||
[key: string]: any
|
||||
pageNumber?: number | string
|
||||
pageSize: number | string
|
||||
totalRecords: number | string
|
||||
}
|
||||
interface ISort {
|
||||
[key: string]: any
|
||||
sortColumn?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
export interface Props {
|
||||
tableHeader: ITableHead[]
|
||||
tableData?: Record<string, any>[]
|
||||
pagination?: IPagination
|
||||
rowAction?: Function | string
|
||||
title?: string
|
||||
formTitle?: string
|
||||
listText?: string
|
||||
icon?: string
|
||||
apiList?: string
|
||||
addRoute?: string
|
||||
addAction?: Function | string
|
||||
filterData?: Record<string, any>
|
||||
sortData?: ISort
|
||||
totalRecord?: string | number
|
||||
query?: string
|
||||
apiText?: string
|
||||
page?: string
|
||||
refresh?: boolean
|
||||
export?: boolean
|
||||
search?: boolean
|
||||
rowNumber?: boolean
|
||||
totalValues?: Record<string, any>
|
||||
isPreview?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
tableData: () => [],
|
||||
totalRecord: 0,
|
||||
apiText: '',
|
||||
page: 'list',
|
||||
refresh: false,
|
||||
export: true,
|
||||
search: true,
|
||||
rowNumber: false,
|
||||
isPreview: false
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'click',
|
||||
'update:tableData',
|
||||
'update:pagination',
|
||||
'update:sortData',
|
||||
'update:query',
|
||||
'update:refresh',
|
||||
'update:totalRecord'
|
||||
])
|
||||
|
||||
const rnd = ref<number>(Number(Math.floor(Math.random() * 1000000000)))
|
||||
const filterPanel = ref<boolean>(false)
|
||||
const exportPanel = ref<boolean>(false)
|
||||
const searchForm = ref<boolean>(false)
|
||||
const mobileButtons = ref<boolean>(false)
|
||||
const selectedExport = ref<number | null>(null)
|
||||
const searched = ref<boolean>(false)
|
||||
|
||||
const localSort = ref<ISort>({} as ISort)
|
||||
const localPagination = ref<IPagination>({} as IPagination)
|
||||
const localTotalValues = reactive<Record<string, any>>({})
|
||||
const searchFieldPos = ref<string>('')
|
||||
|
||||
if (props.totalValues !== undefined) Object.assign(localTotalValues, props.totalValues)
|
||||
|
||||
if (props.pagination !== undefined)
|
||||
Object.assign(localPagination.value, props.pagination)
|
||||
const localTableData = ref<Record<string, any>[]>([])
|
||||
if (props.tableData !== undefined) localTableData.value = props.tableData
|
||||
const localQuery = ref<string>('')
|
||||
|
||||
const localTotalRecord = ref<string | number>(0)
|
||||
if (props.totalRecord !== undefined) localTotalRecord.value = props.totalRecord
|
||||
|
||||
const localFilterParams = ref<Record<string, any>>({})
|
||||
const filterParams = reactive<Record<string, any>>({})
|
||||
|
||||
const OpenFilterPanel = () => {
|
||||
Object.assign(localFilterParams.value, filterParams)
|
||||
filterPanel.value = true
|
||||
}
|
||||
|
||||
const EqualObjects = (source: Record<string, any>, dest: Record<string, any>) => {
|
||||
Object.keys(source).forEach((keys, is) => {
|
||||
if (dest[keys] === undefined) delete source[keys]
|
||||
})
|
||||
}
|
||||
|
||||
const FilterData = async () => {
|
||||
Object.assign(filterParams, localFilterParams.value)
|
||||
EqualObjects(filterParams, localFilterParams.value)
|
||||
setTimeout(async () => {
|
||||
await GetLocalData()
|
||||
filterPanel.value = false
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const localAddAction = () => {
|
||||
if (props.addAction !== undefined && props.addAction !== '')
|
||||
(props.addAction as Function)()
|
||||
}
|
||||
|
||||
const filterable = () => {
|
||||
return props.tableHeader.filter((e) => e.hasOwnProperty('filter')).length > 0
|
||||
}
|
||||
|
||||
const filters = (): boolean => {
|
||||
return Object.keys(filterParams).length > 0
|
||||
}
|
||||
|
||||
const RemoveFilterKey = (k: string) => {
|
||||
delete localFilterParams.value[k]
|
||||
delete filterParams[k]
|
||||
GetLocalData()
|
||||
}
|
||||
const RemoveSearch = () => {
|
||||
localQuery.value = ''
|
||||
searched.value = false
|
||||
GetLocalData()
|
||||
}
|
||||
const GetLocalData = async () => {
|
||||
if (selectedExport.value !== null) {
|
||||
const exportUrl =
|
||||
axios.defaults.baseURL + dataStore.apiBase + props.apiList + '?isPdf=true'
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = exportUrl
|
||||
document.body.appendChild(link)
|
||||
link.dispatchEvent(
|
||||
new MouseEvent('click', { bubbles: true, cancelable: true, view: window })
|
||||
)
|
||||
link.remove()
|
||||
window.URL.revokeObjectURL(link.href)
|
||||
|
||||
selectedExport.value = null
|
||||
exportPanel.value = false
|
||||
} else {
|
||||
let apiData: Record<string, any> = {
|
||||
params: {
|
||||
pageNumber: props.isPreview ? 0 : localPagination.value.pageNumber || 1,
|
||||
pageSize: globalStore.perPage
|
||||
}
|
||||
}
|
||||
|
||||
if (localSort.value.sortColumn !== undefined) {
|
||||
apiData.params.sortColumn = localSort.value.sortColumn
|
||||
if (localSort.value.sortOrder !== undefined && localSort.value.sortOrder !== '')
|
||||
apiData.params.sortOrder = localSort.value.sortOrder
|
||||
}
|
||||
if (localQuery.value !== '') {
|
||||
apiData.params.searchString = localQuery.value
|
||||
} else {
|
||||
delete apiData.params.searchString
|
||||
}
|
||||
|
||||
if (props.apiText !== undefined && props.apiText !== '')
|
||||
apiData.text = props.apiText
|
||||
|
||||
var filterparam: Record<string, any> = {}
|
||||
|
||||
if (filters()) {
|
||||
Object.keys(filterParams).forEach((f, k) => {
|
||||
filterparam['Filters[' + f + ']'] = filterParams[f].op + filterParams[f].val
|
||||
})
|
||||
Object.assign(apiData.params, filterparam)
|
||||
}
|
||||
let dt = await dataStore.dataGet(props.apiList as string, apiData)
|
||||
|
||||
// buraya dikkat cekilis itiraz modulunde liste duzgun calışması için nullable kullanıldı
|
||||
if (dt !== 'errorfalse') localTableData.value = dt.data ?? dt
|
||||
else localTableData.value = []
|
||||
|
||||
localPagination.value.totalRecords = dt.totalRecords
|
||||
localPagination.value.pageSize = dt.pageSize
|
||||
|
||||
localTotalRecord.value = dt.totalRecords || localTableData.value.length
|
||||
|
||||
if (props.pagination !== undefined) emit('update:pagination', localPagination.value)
|
||||
if (props.totalRecord !== undefined)
|
||||
emit('update:totalRecord', localTotalRecord.value)
|
||||
if (props.totalValues !== undefined) {
|
||||
Object.keys(props.totalValues).forEach((key, i) => {
|
||||
localTotalValues[key] = dt[props.totalValues![key]]
|
||||
})
|
||||
}
|
||||
emit('update:refresh', false)
|
||||
}
|
||||
}
|
||||
const OpenMobileButtons = () => {
|
||||
searchForm.value = false
|
||||
mobileButtons.value = !mobileButtons.value
|
||||
}
|
||||
const OpenSearchForm = (e: Event) => {
|
||||
searchFieldPos.value = ''
|
||||
if (globalStore.screenWidth <= globalStore.breakPoints.tabletp) {
|
||||
mobileButtons.value = false
|
||||
let eltRect = (e.target as HTMLElement).getBoundingClientRect() as DOMRect
|
||||
searchFieldPos.value = `top:${eltRect.top + eltRect.height}px;`
|
||||
}
|
||||
|
||||
searchForm.value = !searchForm.value
|
||||
if (searchForm.value) {
|
||||
setTimeout(() => {
|
||||
let el: HTMLInputElement = document.getElementById(
|
||||
'listsearch' + rnd.value
|
||||
) as HTMLInputElement
|
||||
el.focus
|
||||
el.select()
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
const SearchInputKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') SearchQuery()
|
||||
}
|
||||
const SearchQuery = () => {
|
||||
if (props.apiList !== undefined) GetLocalData()
|
||||
else emit('update:query', localQuery.value)
|
||||
searchForm.value = false
|
||||
searched.value = true
|
||||
}
|
||||
const Resize = () => {
|
||||
searchForm.value = false
|
||||
mobileButtons.value = false
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
if (globalStore.screenWidth >= globalStore.breakPoints.tabletp)
|
||||
mobileButtons.value = true
|
||||
|
||||
if (props.apiList !== undefined) GetLocalData()
|
||||
|
||||
window.addEventListener('resize', Resize)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => localSort.value,
|
||||
() => {
|
||||
GetLocalData()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => localPagination.value.pageNumber,
|
||||
() => {
|
||||
GetLocalData()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.refresh,
|
||||
() => {
|
||||
if (props.refresh) GetLocalData()
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => localQuery.value,
|
||||
() => {
|
||||
if (props.apiList !== undefined && localQuery.value.length === 0) GetLocalData()
|
||||
else if (localQuery.value.length === 0) emit('update:query', localQuery.value)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => selectedExport.value,
|
||||
() => {
|
||||
if (selectedExport.value !== null) GetLocalData()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
54
src/components/global/PanelExport.vue
Normal file
54
src/components/global/PanelExport.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="panel-content panel-export-content">
|
||||
<div class="panel-content-item">
|
||||
<div class="button-c button-icon button-export" @click="SelectExport(1)">
|
||||
<i class="ico-c">
|
||||
<svg>
|
||||
<use :href="icourl + '#pdf'"></use>
|
||||
</svg>
|
||||
</i>
|
||||
<span class="panel-date">PDF</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-content-item" v-if="showExport">
|
||||
<div class="button-c button-icon button-export" @click="SelectExport(2)">
|
||||
<i class="ico-c">
|
||||
<svg>
|
||||
<use :href="icourl + '#xls'"></use>
|
||||
</svg>
|
||||
</i>
|
||||
<span class="panel-date">Excell</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-content-item" v-if="showExport">
|
||||
<div class="button-c button-icon button-export" @click="SelectExport(3)">
|
||||
<i class="ico-c">
|
||||
<svg>
|
||||
<use :href="icourl + '#csv'"></use>
|
||||
</svg>
|
||||
</i>
|
||||
<span class="panel-date">CSV</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import icourl from '@/assets/images/icons.svg'
|
||||
|
||||
const props = defineProps<{
|
||||
selectedExport?: number | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:selectedExport'])
|
||||
|
||||
const selected = ref<number>(0)
|
||||
const showExport = ref<boolean>(false)
|
||||
|
||||
const SelectExport = (exp: number) => {
|
||||
selected.value = exp
|
||||
emit('update:selectedExport', selected.value)
|
||||
}
|
||||
</script>
|
||||
123
src/components/global/PanelFilter.vue
Normal file
123
src/components/global/PanelFilter.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<template v-for="(filter, i) in filterHead">
|
||||
<template v-if="filter.filter !== undefined">
|
||||
<template v-if="filter.filter.type === 'datetime-local'">
|
||||
<div class="panel-content-item">
|
||||
<form-date
|
||||
v-model="localFilterData[filter.name]"
|
||||
:label="filter.title"
|
||||
@change="UpdateFilter(filter)" />
|
||||
<form-select
|
||||
v-if="filter.filter.range !== undefined && filter.filter.range"
|
||||
:listData="filterOperator"
|
||||
v-model="localFilterData[filter.name + 'op']"
|
||||
@change="UpdateFilter(filter)" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="filter.filter.type === 'date'">
|
||||
<div class="panel-content-item">
|
||||
<form-date
|
||||
type="date"
|
||||
v-model="localFilterData[filter.name]"
|
||||
:label="filter.title"
|
||||
@change="UpdateFilter(filter)" />
|
||||
<form-select
|
||||
v-if="filter.filter.range !== undefined && filter.filter.range"
|
||||
:listData="filterOperator"
|
||||
v-model="localFilterData[filter.name + 'op']"
|
||||
@change="UpdateFilter(filter)" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="filter.filter.type === 'text'">
|
||||
<div class="panel-content-item">
|
||||
<form-input
|
||||
v-model="localFilterData[filter.name]"
|
||||
:label="filter.title"
|
||||
@change="UpdateFilter(filter)" />
|
||||
<form-select
|
||||
v-if="filter.filter.range !== undefined && filter.filter.range"
|
||||
:listData="filterOperator"
|
||||
v-model="localFilterData[filter.name + 'op']"
|
||||
@change="UpdateFilter(filter)" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="filter.filter.type === 'select'">
|
||||
<div class="panel-content-item">
|
||||
<form-select
|
||||
:listData="filter.filter.data"
|
||||
:listText="filter.filter.listText"
|
||||
:listVal="filter.filter.listVal"
|
||||
:extraData="filter"
|
||||
:label="filter.title"
|
||||
v-model="localFilterData[filter.filter.filterId || filter.name]"
|
||||
@change="UpdateFilterSelect"
|
||||
clearable />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onBeforeMount } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
filterHead: Record<string, any>
|
||||
filterParams: Record<string, any>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:filterParams'])
|
||||
|
||||
const localFilterData = reactive<Record<string, any>>({})
|
||||
const localFilterParams = reactive<Record<string, any>>(
|
||||
Object.assign(props.filterParams)
|
||||
)
|
||||
const filterOperator = ref<string[]>(['=', '<', '>'])
|
||||
|
||||
const createFilterData = () => {
|
||||
props.filterHead.forEach((d: Record<string, any>) => {
|
||||
if (d.filter !== undefined) {
|
||||
let filterKey = d.filter.filterId || d.name
|
||||
localFilterData[filterKey] = ''
|
||||
localFilterData[filterKey + 'op'] = '='
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(localFilterParams).forEach((k, i) => {
|
||||
localFilterData[k] = localFilterParams[k].val
|
||||
localFilterData[k + 'op'] = localFilterParams[k].op
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateFilter = (k: Record<string, any>) => {
|
||||
let filterKey = k.filter.filterId || k.name
|
||||
if (localFilterData[filterKey] !== '') {
|
||||
localFilterParams[filterKey] = {}
|
||||
localFilterParams[filterKey].val = localFilterData[filterKey]
|
||||
localFilterParams[filterKey].op = localFilterData[filterKey + 'op']
|
||||
localFilterParams[filterKey].title = k.title
|
||||
} else delete localFilterParams[filterKey]
|
||||
|
||||
emit('update:filterParams', localFilterParams)
|
||||
}
|
||||
const UpdateFilterSelect = (
|
||||
e: Event,
|
||||
v: string,
|
||||
d: Record<string, any>,
|
||||
ext: Record<string, any>
|
||||
) => {
|
||||
let filterKey = ext.filter.filterId || ext.name
|
||||
if (v !== '' && v !== null && localFilterData[filterKey] !== '') {
|
||||
localFilterParams[filterKey] = {}
|
||||
localFilterParams[filterKey].val = localFilterData[filterKey]
|
||||
localFilterParams[filterKey].op = localFilterData[filterKey + 'op']
|
||||
localFilterParams[filterKey].title = ext.title
|
||||
localFilterParams[filterKey].text = d[ext.filter.listText]
|
||||
} else {
|
||||
delete localFilterParams[filterKey]
|
||||
}
|
||||
emit('update:filterParams', localFilterParams)
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
createFilterData()
|
||||
})
|
||||
</script>
|
||||
109
src/components/global/Tabs.vue
Normal file
109
src/components/global/Tabs.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="tabs-buttons-c" :id="'tabnav' + rnd">
|
||||
<template v-for="(tab, i) in tabList">
|
||||
<div
|
||||
:class="['tab-button', currentTab === i ? 'tab-selected' : '']"
|
||||
@click="ChangeTab(i)"
|
||||
:style="[i === 0 ? tabPosition : '']">
|
||||
<slot :name="'tabname' + i">{{ tab.text }}</slot>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tab-nav-button tab-nav-button-prev" @click="ChangeTab('<')">
|
||||
<i class="ico-c ico-tab-nav">
|
||||
<svg width="8"><use href="/src/assets/images/icons.svg#arrow"></use></svg>
|
||||
</i>
|
||||
</div>
|
||||
<div class="tab-nav-button tab-nav-button-next" @click="ChangeTab('>')">
|
||||
<i class="ico-c ico-tab-nav">
|
||||
<svg width="8"><use href="/src/assets/images/icons.svg#arrow"></use></svg>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs-contents-c" :id="'tabs' + rnd">
|
||||
<template v-for="(tabc, j) in tabList">
|
||||
<div
|
||||
class="tab-content tab-animate"
|
||||
:style="[j === 0 ? tabContentPosition : '', { width: tabWidth + 'px' }]">
|
||||
<template v-if="currentTab === j">
|
||||
<slot :name="tabc.id"></slot>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useGlobalStore } from '@/stores/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
interface TabObj {
|
||||
[key: string]: any
|
||||
text: string
|
||||
id: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
tabList: TabObj[]
|
||||
}>()
|
||||
|
||||
const currentTab = ref<number>(0)
|
||||
const rnd = ref<number>(Math.ceil(Number(Math.random() * 1000000000)))
|
||||
const tabWidth = ref<number>(0)
|
||||
const tabPosition = ref<string>('')
|
||||
|
||||
const tabContentPosition = computed<string>(() => {
|
||||
var tabPos = ''
|
||||
let pos = -(tabWidth.value * currentTab.value)
|
||||
tabPos = `margin-left:${pos}px`
|
||||
return tabPos
|
||||
})
|
||||
|
||||
const CalculateNavPosition = () => {
|
||||
tabPosition.value = ''
|
||||
let screenWidth = document.body.offsetWidth
|
||||
if (screenWidth <= 1024) {
|
||||
let pos = -(tabWidth.value * currentTab.value - 50)
|
||||
tabPosition.value = `margin-left:${pos}px`
|
||||
}
|
||||
}
|
||||
|
||||
const ChangeTab = (d: number | string) => {
|
||||
if (d === '<') {
|
||||
if (currentTab.value !== 0) {
|
||||
currentTab.value--
|
||||
}
|
||||
} else if (d == '>') {
|
||||
if (currentTab.value !== props.tabList.length - 1) {
|
||||
currentTab.value++
|
||||
}
|
||||
} else {
|
||||
currentTab.value = Number(d)
|
||||
}
|
||||
CalculateNavPosition()
|
||||
}
|
||||
const TabWidth = () => {
|
||||
let screenWidth = document.body.offsetWidth
|
||||
let tabel = document.getElementById('tabs' + rnd.value) as HTMLElement
|
||||
if (screenWidth <= globalStore.breakPoints.tablet) {
|
||||
tabWidth.value = screenWidth - 48
|
||||
} else {
|
||||
const menuSideW = document.querySelector('.menu-side-w')
|
||||
let sideW = globalStore.sideMenu ? menuSideW!.getBoundingClientRect().width : 0
|
||||
tabWidth.value = screenWidth - sideW - 48
|
||||
}
|
||||
}
|
||||
const Resize = () => {
|
||||
TabWidth()
|
||||
CalculateNavPosition()
|
||||
}
|
||||
onMounted(() => {
|
||||
TabWidth()
|
||||
window.addEventListener('resize', Resize)
|
||||
})
|
||||
watch(
|
||||
() => globalStore.sideMenu,
|
||||
() => {
|
||||
TabWidth()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
40
src/components/global/ToastItem.vue
Normal file
40
src/components/global/ToastItem.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div :class="['page-toast-item', type !== undefined ? 'toast-item-' + type : '']">
|
||||
<span class="toast-close-btn" @click="CloseToast"></span>
|
||||
<span>{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useToastStore } from './toastStore'
|
||||
const toastStore = useToastStore()
|
||||
|
||||
export interface Props {
|
||||
id: number
|
||||
text: string
|
||||
type: string
|
||||
timeout?: number
|
||||
show?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: '',
|
||||
timeout: 0,
|
||||
show: false
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:show'])
|
||||
const localTimeout = ref<number>(props.timeout)
|
||||
|
||||
const CloseToast = () => {
|
||||
delete toastStore.toasts[props.id]
|
||||
}
|
||||
onMounted(() => {
|
||||
if (localTimeout.value === 0) localTimeout.value = toastStore.toastTimeout
|
||||
|
||||
if (localTimeout.value >= 0) {
|
||||
setTimeout(() => {
|
||||
CloseToast()
|
||||
}, localTimeout.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
20
src/components/global/Toasts.vue
Normal file
20
src/components/global/Toasts.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<template v-if="Object.keys(toastStore.toasts).length > 0">
|
||||
<teleport to="body">
|
||||
<div class="page-toast-w">
|
||||
<template v-for="(toast, i) in toastStore.toasts">
|
||||
<toast-item
|
||||
:type="toast.type"
|
||||
:id="toast.id"
|
||||
:timeout="toast.timeout"
|
||||
:text="toast.text" />
|
||||
</template>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ToastItem from './ToastItem.vue'
|
||||
import { useToastStore } from './toastStore'
|
||||
const toastStore = useToastStore()
|
||||
</script>
|
||||
22
src/components/global/dialogStore.ts
Normal file
22
src/components/global/dialogStore.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
export const useDialogStore = defineStore('dialogStore', () => {
|
||||
const dialogs = reactive<Record<string, any>>({})
|
||||
|
||||
const CreateDialog = (data: Record<string, any>) => {
|
||||
const rnd: number = Number(Math.floor(Math.random() * 10000000000))
|
||||
|
||||
dialogs[rnd] = {} as Record<string, any>
|
||||
dialogs[rnd].id = rnd
|
||||
dialogs[rnd].data = data
|
||||
}
|
||||
const CloseDialog = (id: number) => {
|
||||
delete dialogs[id]
|
||||
}
|
||||
return {
|
||||
dialogs,
|
||||
CreateDialog,
|
||||
CloseDialog
|
||||
}
|
||||
})
|
||||
40
src/components/global/index.ts
Normal file
40
src/components/global/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import DataTable from './DataTable.vue'
|
||||
import DataTablePagination from './DataTablePagination.vue'
|
||||
import ListTableContent from './ListTableContent.vue'
|
||||
import FormInput from './FormInput.vue'
|
||||
import FormDate from './FormDate.vue'
|
||||
import FormFile from './FormFile.vue'
|
||||
import FormTextarea from './FormTextarea.vue'
|
||||
import FormQuill from './FormQuill.vue'
|
||||
import FormSummer from './FormSummer.vue'
|
||||
import FormSelect from './FormSelect.vue'
|
||||
import FormRadio from './FormRadio.vue'
|
||||
import FormCheckbox from './FormCheckbox.vue'
|
||||
import Tabs from './Tabs.vue'
|
||||
import IconButton from './IconButton.vue'
|
||||
import Breadcrumb from './Breadcrumb.vue'
|
||||
import FileListItem from './FileListItem.vue'
|
||||
import FormDisplay from './FormDisplay.vue'
|
||||
import Toasts from './Toasts.vue'
|
||||
import Dialogs from './Dialogs.vue'
|
||||
export {
|
||||
DataTable,
|
||||
DataTablePagination,
|
||||
ListTableContent,
|
||||
FormInput,
|
||||
FormDate,
|
||||
FormFile,
|
||||
FormTextarea,
|
||||
FormQuill,
|
||||
FormSummer,
|
||||
FormSelect,
|
||||
FormRadio,
|
||||
FormCheckbox,
|
||||
Tabs,
|
||||
IconButton,
|
||||
Breadcrumb,
|
||||
FileListItem,
|
||||
FormDisplay,
|
||||
Toasts,
|
||||
Dialogs
|
||||
}
|
||||
23
src/components/global/toastStore.ts
Normal file
23
src/components/global/toastStore.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
export const useToastStore = defineStore('toastStore', () => {
|
||||
const toastTimeout = ref<number>(5000)
|
||||
const toasts = reactive<Record<string, any>>({})
|
||||
|
||||
const AddToast = (toast: string, type: string, timeout?: number) => {
|
||||
const iTimeout: number = timeout || 0
|
||||
const rnd: number = Number(Math.floor(Math.random() * 10000000000))
|
||||
|
||||
toasts[rnd] = {} as Record<string, any>
|
||||
toasts[rnd].text = toast
|
||||
toasts[rnd].timeout = iTimeout
|
||||
toasts[rnd].id = rnd
|
||||
toasts[rnd].type = type
|
||||
}
|
||||
return {
|
||||
toastTimeout,
|
||||
toasts,
|
||||
AddToast
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user