Initial commit

This commit is contained in:
2026-05-29 17:49:25 +09:00
commit 330105cb27
1081 changed files with 148694 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
<template>
<sdl-modal-container />
<sdl-loadingbar />
<sdl-dialog :click-to-close="false" />
<router-view></router-view>
<sdl-scroll-to-top></sdl-scroll-to-top>
</template>
@@ -0,0 +1,859 @@
<!--
Program: ApprovalPath.vue
Author: SDL
Description: Approval path common component
-->
<template>
<div class="ui--approval-container">
<div class="input-group">
<input type="text" class="form-control" :placeholder="$t('sdl.common.label.payer')" aria-label="add User" v-model="searchUserNm" @keyup.esc="searchUserNm = ''" @keyup.enter.stop="openSearchUser()" />
<button class="btn btn-outline-secondary" type="button" @click="openSearchUser">
<i class="bi bi-search"></i>
</button>
</div>
<div class="ui--approval-list">
<!-- file-button -->
<div class="ui--approval-button">
<div class="ui--apv-flex">
<button class="btn ui--btn-apv-outline" type="button" :class="{ on: approvalType === 'APPROVAL' }" @click="changeApprovalType('APPROVAL')">
{{ $t('sdl.approval.label.APPROVE') }}
</button>
<button class="btn ui--btn-apv-outline" type="button" :class="{ on: approvalType === 'AGREEMENT' }" @click="changeApprovalType('AGREEMENT')">
{{ $t('sdl.approval.label.AGREEMENT') }}
</button>
<button class="btn ui--btn-apv-outline" type="button" :class="{ on: approvalType === 'NOTIFICATION' }" @click="changeApprovalType('NOTIFICATION')">
{{ $t('sdl.approval.label.NOTIFY') }}
</button>
</div>
<span class="ui--slide ms-2 me-2">|</span>
<button class="btn ui--btn-xs" type="button" @click="changeApprovalType('EX_POST_APPROVAL')">
{{ $t('sdl.approval.label.SKIP') }}
</button>
<span class="ui--slide ms-2 me-2">|</span>
<button class="btn ui--btn-xs me-1" type="button" @click="setApprovalParallel(true)"><i class="bi bi-view-stacked"></i>{{ $t('sdl.common.label.parallel') }}</button>
<button class="btn ui--btn-xs" type="button" @click="setApprovalParallel(false)"><i class="bi bi-grid"></i>{{ $t('sdl.approval.label.PARALLEL_CANCEL') }}</button>
<span v-if="showRequired" class="ui--slide ms-2 me-2">|</span>
<button v-if="showRequired" class="btn btn-outline-info btn-xs" @click="onRequiredApprListPopup" type="button" style="position: relative">
{{ $t('sdl.approval.label.addRequiredApprovalUser') }}
<div v-if="showRequiredTooltip" class="ui--app-required"><i class="bi bi-info-circle"></i> {{ $t('sdl.approval.message.selectRequiredApprovalUser') }}</div>
</button>
<span v-if="showRequired" class="ui--slide ms-2 me-2">|</span>
<button class="btn ui--btn-icon-sm" :title="$t('sdl.common.label.Top')" type="button" @click="stepTopUp()">
<i class="bi bi-skip-end-fill ui--trans-rotate-270"></i>
</button>
<button class="btn ui--btn-icon-sm" :title="$t('sdl.common.label.moveup')" type="button" @click="stepUp()">
<i class="bi bi-play-fill ui--trans-rotate-270"></i>
</button>
<button class="btn ui--btn-icon-sm" :title="$t('sdl.common.label.movedown')" type="button" @click="stepDown()">
<i class="bi bi-play-fill ui--trans-rotate-90"></i>
</button>
<button class="btn ui--btn-icon-sm" :title="$t('sdl.common.label.bottom')" type="button" @click="stepBottomDown()">
<i class="bi bi-skip-end-fill ui--trans-rotate-90"></i>
</button>
<button class="btn ui--btn-icon-sm" :title="$t('sdl.common.label.delete')" type="button" @click="deleteUserTopBtn">
<i class="bi bi-x"></i>
</button>
<span class="ui--slide ms-2 me-2">|</span>
<span
>{{ $t('sdl.common.label.total') }}<strong class="ms-1 me-2">{{ apprPathList.length }}</strong></span
>
</div>
<!-- // file-button -->
<!-- file-list -->
<ul>
<li v-for="user in apprPathList" :key="`${user.userId}_${user.stepIdx}`" :class="{ selected: user.selected, draft: user.approvalType === 'DRAFTING' }" @click="selectedPath(user)">
<span class="count">{{ user.stepIdx }}</span>
<div class="ui--apv-flex" v-if="user.approvalType === 'DRAFTING'">
<button class="btn ui--btn-apv btn-apv-request" type="button">{{ $t('sdl.approval.label.REQUEST') }}</button>
</div>
<div class="ui--apv-flex" v-else-if="user.approvalType === 'EX_POST_APPROVAL'">
<button class="btn ui--btn-apv btn-apv-after on" type="button">{{ $t('sdl.approval.label.SKIP') }}</button>
</div>
<div class="ui--apv-flex" v-else-if="user.approvalType === 'PARALLEL_APPROVAL'">
<button class="btn ui--btn-apv ui--btn-apv-approve on" type="button">
{{ $t('sdl.approval.label.PARALLEL_APPROVE') }}
</button>
</div>
<div class="ui--apv-flex" v-else-if="user.approvalType === 'PARALLEL_AGREEMENT'">
<button class="btn ui--btn-apv ui--btn-apv-approve on" type="button">
{{ $t('sdl.approval.label.PARALLEL_AGREEMENT') }}
</button>
</div>
<div class="ui--apv-flex" v-else>
<button class="btn ui--btn-apv ui--btn-apv-approve" :class="{ on: user.approvalType === 'APPROVAL' }" @click.stop="changeUserApprovalType(user, 'APPROVAL')" type="button">
{{ $t('sdl.approval.label.APPROVE') }}
</button>
<button class="btn ui--btn-apv ui--btn-apv-agreement" :class="{ on: user.approvalType === 'AGREEMENT' }" @click.stop="changeUserApprovalType(user, 'AGREEMENT')" type="button">
{{ $t('sdl.approval.label.AGREEMENT') }}
</button>
<button class="btn ui--btn-apv ui--btn-apv-notice" :class="{ on: user.approvalType === 'NOTIFICATION' }" @click.stop="changeUserApprovalType(user, 'NOTIFICATION')" type="button">
{{ $t('sdl.approval.label.NOTIFY') }}
</button>
</div>
<span class="name" v-if="!user.required"> {{ user.userName }}/{{ user.deptName }}/{{ user.compName }}</span>
<span class="name" v-if="user.required"
><span class="text-info">{{ user.userName }}/{{ user.deptName }}/{{ user.compName }} ({{ $t('sdl.approval.label.requiredApprovalUser') }})</span></span
>
<div v-if="user.approvalType === 'DRAFTING'" class="ui--apv-right text-black-50">
{{ $t('sdl.common.label.requester') }}
</div>
<div class="ui--apv-right" v-else>
<button class="btn ui--btn-icon-xs" :class="{ on: user.routeModify }" :title="$t('sdl.common.label.pathchange')" @click.stop="approvalRouteModify(user)" type="button">
<i class="bi bi-arrow-down-up"></i>
</button>
<button class="btn ui--btn-icon-xs" :class="{ on: user.bodyModify }" :title="$t('sdl.approval.label.BODYMODIFY')" @click.stop="approvalBodyModify(user)" type="button">
<i class="bi bi-fonts"></i>
</button>
<button class="btn ui--btn-icon-xs" :class="{ on: user.arbitrary }" :title="$t('sdl.approval.label.ARBITRARY')" @click.stop="approvalArbitrary(user)" type="button">
<i class="bi bi-pencil-square"></i>
</button>
<button class="btn ui--btn-icon-sm float-end" :title="$t('sdl.common.label.delete')" @click.stop="deleteUserRightBtn(user)" type="button">
<i class="bi bi-x"></i>
</button>
</div>
</li>
</ul>
<!-- // file-list -->
</div>
</div>
</template>
<script>
import { SDLUtil } from '@/utils';
import RequiredApprListPopup from '@/components/view/admin/approval/popup/RequiredApprListPopup.vue';
export default {
props: {
approvalStepList: {
type: Array,
},
showRequired: {
type: Boolean,
default: false,
},
requiredApprovalUserList: {
type: Array,
default: () => [],
},
internalFlag: {
type: Boolean,
default: false,
},
},
data() {
return {
searchUserNm: '',
apprPathList: [],
approvalType: 'APPROVAL',
selectedDocTypeCode: '',
checkParallesGroup: {},
showRequiredTooltip: true,
approvalPathStatus: [
{ code: 'DRAFTING', label: 'sdl.approval.label.REQUEST' },
{ code: 'APPROVAL', label: 'sdl.approval.label.APPROVE' },
{ code: 'CANCEL', label: 'sdl.approval.label.CANCELLED' },
{ code: 'AGREEMENT', label: 'sdl.approval.label.AGREEMENT' },
{ code: 'NOTIFICATION', label: 'sdl.approval.label.NOTIFY' },
{ code: 'EX_POST_APPROVAL', label: 'sdl.approval.label.SKIP' },
{ code: 'PARALLEL_AGREEMENT', label: 'sdl.approval.label.PARALLEL_AGREEMENT' },
{ code: 'PARALLEL_APPROVAL', label: 'sdl.approval.label.PARALLEL_APPROVE' },
{ code: 'REJECT', label: 'sdl.approval.label.REJECTED' },
],
};
},
methods: {
/**
* param:
* return:
* exception:
* Description : Page Initialization
*/
init() {
// this.apprPathList.splice(0);
this.searchUserNm = '';
if (this.approvalStepList) {
this.setApprovalList();
}
},
/**
* param:
* return:
* exception:
* Description : When receiving approval list as props from outside
*/
setApprovalList() {
this.apprPathList.splice(0);
Object.keys(this.approvalStepList).forEach(idx => {
const apprLine = this.approvalStepList[idx];
this.apprPathList.push({
stepIdx: apprLine.approvalStepSequence,
parallel: apprLine.parallelProcess,
approvalType: apprLine.approvalStepType,
required: false,
hasError: false,
approverChangeAllowed: apprLine.approverChangeAllowed,
bodyModify: apprLine.bodyModify,
routeModify: apprLine.routeModify,
arbitrary: apprLine.arbitrary,
userId: apprLine.approvalPersonId,
userName: apprLine.approvalPersonName,
grdName: apprLine.approvalPersonPositionName,
deptName: apprLine.approvalPersonDeptName,
compName: apprLine.approvalPersonCompanyName,
apprPathFlag: apprLine.apprPathFlag,
docType: apprLine.docType,
});
});
this.resetApprovalSeq();
},
/**
* param:
* return:
* exception:
* Description : When you take the approval list from the inside,
*/
getApprovalList() {
const rtnList = [];
Object.keys(this.apprPathList).forEach(idx => {
const apprLine = this.apprPathList[idx];
rtnList.push({
approvalStepSequence: apprLine.stepIdx,
parallelProcess: apprLine.parallel,
approvalStepType: apprLine.approvalType,
required: false,
hasError: false,
approverChangeAllowed: true,
bodyModify: apprLine.bodyModify,
routeModify: apprLine.routeModify,
arbitrary: apprLine.arbitrary,
approvalPersonId: apprLine.userId,
approvalPersonName: apprLine.userName,
approvalPersonPositionName: apprLine.grdName,
approvalPersonDeptName: apprLine.deptName,
approvalPersonCompanyName: apprLine.compName,
});
});
return rtnList;
},
/**
* param: user -> json
* return:
* exception:
* Description : Body can be modified
*/
approvalBodyModify(user) {
if (user.approvalStatusTypeCode === 'DRAFTING') return; // No change of DRAFTING
user.bodyModify = !user.bodyModify;
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
},
/**
* param: user -> json
* return:
* exception:
* Description : Is the approval path changed
*/
approvalRouteModify(user) {
if (user.approvalStatusTypeCode === 'DRAFTING') return; // No change of DRAFTING
user.routeModify = !user.routeModify;
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
},
/**
* param: user -> json
* return:
* exception:
* Description : Is the final decision for approval
*/
approvalArbitrary(user) {
if (user.approvalStatusTypeCode === 'DRAFTING') return; // No change of DRAFTING
user.arbitrary = !user.arbitrary;
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
},
/**
* param: parallel -> Boolean
* return:
* exception:
* Description : parallel approval setting
*/
setApprovalParallel(parallel) {
if (this.apprPathList[0].selected) return; // No change of DRAFTING
if (this.checkRequired()) {
// Required Approval Check
SDLUtil.alert('sdl.approval.message.cannotChangeRequiredSteps');
return;
}
const selectedRows = [];
for (let i = 1; i < this.apprPathList.length; i += 1) {
if (this.apprPathList[i].selected) {
if (this.apprPathList[i].approvalType === 'NOTIFICATION') {
// Notification does not pay in parallel
SDLUtil.alert('sdl.approval.message.notifyCannotBeParallel');
return;
}
if (this.apprPathList[i].approvalType === 'EX_POST_APPROVAL') {
// No parallel payment for the latter decision
SDLUtil.alert('sdl.approval.message.skipCannotBeParallel');
return;
}
selectedRows.push(this.apprPathList[i]);
}
}
if (selectedRows.length < 2) {
// Select more than one parallel approval
SDLUtil.alert('sdl.approval.message.parallelRequiresTwoOrMore');
return;
}
if (this.checkApprovalStepsParallelPosition(selectedRows, parallel, false)) {
return;
}
Object.keys(selectedRows).forEach(idx => {
const user = selectedRows[idx];
if (user.selected && user.approvalType !== 'NOTIFICATION' && user.approvalType !== 'EX_POST_APPROVAL') {
user.parallel = parallel;
if (parallel) {
if (user.approvalType === 'PARALLEL_APPROVAL' || user.approvalType === 'PARALLEL_AGREEMENT') return;
if (user.approvalType === 'APPROVAL') {
user.approvalType = 'PARALLEL_APPROVAL';
} else {
user.approvalType = 'PARALLEL_AGREEMENT';
}
} else if (user.approvalType === 'PARALLEL_APPROVAL') {
user.approvalType = 'APPROVAL';
} else if (user.approvalType === 'PARALLEL_AGREEMENT') {
user.approvalType = 'AGREEMENT';
}
}
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
});
this.resetApprovalSeq();
},
/**
* param: selectOptions -> array
* param: parallel -> true/false(Boolean)
* param: submit -> true/false(Boolean)
* return:
* exception:
* Description : Check if parallel approval is possible
*/
checkApprovalStepsParallelPosition(selectOptions, parallel, submit) {
const paralles = [];
let retStatus = true;
selectOptions
.filter(opt => opt.parallel)
.forEach(opt => {
if (typeof this.checkParallesGroup[opt.stepIdx] === 'undefined') {
const checkParalles = [];
checkParalles.push(opt);
this.checkParallesGroup[opt.stepIdx] = checkParalles;
} else {
this.checkParallesGroup[opt.stepIdx].push(opt);
}
});
if (submit) {
// Select more than one parallel approval
Object.keys(this.checkParallesGroup).some(index => {
if (this.checkParallesGroup[index].length === 1) {
SDLUtil.alert('sdl.approval.message.parallelRemainTwoOrMore');
return true;
}
return false;
});
retStatus = false;
} else {
selectOptions
.filter(opt => opt.selected && opt.approvalType !== 'NOTIFICATION')
.forEach(opt => {
paralles.push(opt);
});
for (let i = 1; i < paralles.length; i += 1) {
const diff = paralles[i - 1].stepIdx - paralles[i].stepIdx;
if (diff === 1 || diff === -1 || diff === 0) {
if (!parallel && paralles[i - 1].parallel !== paralles[i].parallel) {
SDLUtil.alert('sdl.approval.message.notMattchParallele');
return true;
}
retStatus = false;
} else {
SDLUtil.alert('sdl.approval.message.linked');
return true;
}
}
// When releasing parallel payment, only one parallel approval step must be left.
// If you select 3 parallel payments and replace the two parallel ones with the approval.
if (!parallel && paralles.length > 0) {
const opt = paralles[0];
const opts = this.checkParallesGroup[opt.stepIdx];
if (opts != null && opts.length - paralles.length === 1) {
SDLUtil.alert('sdl.approval.message.parallelRemainTwoOrMore');
return true;
}
}
}
return retStatus;
},
/**
* param:
* return:
* exception:
* Description : Required Approval Check
*/
checkRequired() {
for (let i = 0; i < this.apprPathList.length; i += 1) {
if (this.apprPathList[i].selected) {
if (this.apprPathList[i].required) return true;
}
}
return false;
},
/**
* param:
* return:
* exception:
* Description : open user search popup
*/
openSearchUser() {
if (this.searchUserNm) {
if (this.searchUserNm.length < 2) {
SDLUtil.alert('sdl.common.message.enterMoreThan2CharForSearchTerms');
return;
}
}
SDLUtil.openUserPopup({
searchColumn: 'userName',
searchTxt: this.searchUserNm,
rtnFunc: this.getUserList,
knoxSearch: !this.internalFlag,
});
},
/**
* param: list -> array
* return:
* exception:
* Description : return user search result
*/
getUserList(list) {
const n = this.apprPathList.length;
let dupFlag = false;
// Double-payer check
Object.keys(list).forEach(i => {
const userInfo = list[i];
for (let j = 0; j < this.apprPathList.length; j += 1) {
if (userInfo.userId === this.apprPathList[j].userId) {
dupFlag = true;
return;
}
}
});
if (dupFlag) {
SDLUtil.alert('sdl.approval.message.duplicateApprover');
return;
}
Object.keys(list).forEach(i => {
const userInfo = list[i];
this.apprPathList.push({
stepIdx: parseInt(n, 10) + parseInt(i, 10),
parallel: false,
approvalType: this.approvalType,
required: this.requiredApprovalUserList.some(requiredUser => requiredUser.userId === userInfo.userId),
hasError: false,
approverChangeAllowed: true,
bodyModify: false,
routeModify: false,
arbitrary: false,
departmentName: userInfo.deptName,
positionName: userInfo.grdName,
companyName: userInfo.compName,
positionCode: userInfo.grdCode,
departmentCode: userInfo.deptCode,
companyCode: userInfo.compCode,
requiredUser: userInfo.requiredUser,
userId: userInfo.userId,
userName: userInfo.userName,
grdName: userInfo.grdName,
deptName: userInfo.deptName,
compName: userInfo.compName,
});
});
this.resetApprovalSeq();
this.searchUserNm = '';
},
/**
* param: user -> Object
* return:
* exception:
* Description : select approval path
*/
selectedPath(user) {
user.selected = !user.selected;
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
},
/**
* param:
* return:
* exception:
* Description : search popup RequiredApprListPopup
*/
onRequiredApprListPopup() {
const { docType } = this.requiredApprovalUserList[0];
SDLUtil.show(
RequiredApprListPopup,
{
docType,
rtnFunc: list => {
this.showRequiredTooltip = false;
let dupFlag = false;
// Double-payer check
Object.keys(list).forEach(i => {
const userInfo = list[i];
for (let j = 0; j < this.apprPathList.length; j += 1) {
if (userInfo.userId === this.apprPathList[j].userId) {
dupFlag = true;
return;
}
}
});
if (dupFlag) {
SDLUtil.alert('sdl.approval.message.duplicateApprover');
return;
}
Object.keys(list).forEach(idx => {
const apprLine = list[idx];
this.apprPathList.push({
stepIdx: apprLine.approvalStepSequence,
parallel: false,
approvalType: 'APPROVAL',
required: true,
hasError: false,
approverChangeAllowed: true,
bodyModify: apprLine.bodyModify,
routeModify: apprLine.routeModify,
arbitrary: apprLine.arbitrary,
userId: apprLine.userId,
userName: apprLine.userName,
grdName: apprLine.grdName,
deptName: apprLine.deptName,
compName: apprLine.compName,
apprPathFlag: false,
docType,
});
});
this.resetApprovalSeq();
},
},
{
width: '900px',
height: 'auto',
},
);
},
/**
* param: approvalType -> String
* return:
* exception:
* Description : click top button(APPROVAL, AGREEMENT, NOTIFICATION)
*/
changeApprovalType(approvalType) {
if (this.apprPathList[0].selected) return; // The drafter cannot be changed
// Approval person cannot be modified
for (let i = 1; i < this.apprPathList.length; i += 1) {
if (this.apprPathList[i].selected && !this.apprPathList[i].approverChangeAllowed) {
SDLUtil.alert('sdl.approval.message.approverNotAllowedToChange');
return;
}
}
this.approvalType = approvalType;
// if (!this.chkSelectDocType()) return;
for (let i = 1; i < this.apprPathList.length; i += 1) {
// Except the drafter
if (this.apprPathList[i].selected) {
this.apprPathList[i].approvalType = approvalType;
this.apprPathList.splice(i, 1, this.apprPathList[i]);
}
}
},
/**
* param: user -> Object
* param: approvalType -> String
* return:
* exception:
* Description : click left button(APPROVAL, AGREEMENT, NOTIFICATION)
*/
changeUserApprovalType(user, approvalType) {
if (this.apprPathList[0].selected) return; // The drafter cannot be changed
// The authorized person cannot be modified
if (!user.approverChangeAllowed) {
SDLUtil.alert('sdl.approval.message.approverNotAllowedToChange');
return;
}
user.approvalType = approvalType;
this.apprPathList.splice(this.apprPathList.indexOf(user), 1, user);
},
/**
* param:
* return:
* exception:
* Description : click top delete button
*/
deleteUserTopBtn() {
if (this.apprPathList[0].selected) return; // The drafter cannot be changed
let apprPathFlag = false;
let parallelChkFlag = false;
for (let i = 0; i < this.apprPathList.length; i += 1) {
const opt = this.apprPathList[i];
if (opt.selected && opt.parallel) {
parallelChkFlag = true;
}
if (opt.selected && opt.apprPathFlag) {
apprPathFlag = true;
}
}
if (parallelChkFlag) {
SDLUtil.alert('sdl.common.message.possibleToDeleteAfterParallelPayment');
return;
}
if (apprPathFlag) {
SDLUtil.alert('sdl.approval.message.delApprovalPathMgmt');
return;
}
const selectedPathList = [];
this.apprPathList.forEach(path => {
if (path.selected) selectedPathList.push(path);
});
selectedPathList.forEach(selectedPath => {
this.apprPathList.forEach((apprPath, idx) => {
if (selectedPath === apprPath) this.apprPathList.splice(idx, 1);
});
});
this.resetApprovalSeq();
},
/**
* param: user -> Object
* return:
* exception:
* Description : click right delete button
*/
deleteUserRightBtn(user) {
if (this.apprPathList[0].selected) return; // The drafter cannot be changed
if (user.apprPathFlag) {
SDLUtil.alert('sdl.approval.message.delApprovalPathMgmt');
return;
}
if (user.parallel) {
SDLUtil.alert('sdl.common.message.possibleToDeleteAfterParallelPayment');
return;
}
this.apprPathList.splice(this.apprPathList.indexOf(user), 1);
this.resetApprovalSeq();
},
/**
* param:
* return:
* exception:
* Description : reset approval sequence numbering
*/
resetApprovalSeq() {
let stepIdx = -1;
let prevIsParallel = false;
// let preIndex = -1;
let preSelected = false;
this.showRequiredTooltip = !this.apprPathList.some(appr => appr.required);
for (let i = 0; i < this.apprPathList.length; i += 1) {
const opt = this.apprPathList[i];
if (!prevIsParallel || !opt.parallel) {
stepIdx += 1;
} else if (preSelected !== opt.selected) {
stepIdx += 1;
}
// preIndex = opt.stepIdx;
prevIsParallel = opt.parallel;
opt.stepIdx = stepIdx;
preSelected = opt.selected;
this.apprPathList.splice(i, 1, opt);
}
},
chkSelectDocType() {
if (this.selectedDocTypeCode === '') {
SDLUtil.alert('sdl.approval.message.selectApprovalDocType');
return false;
}
return true;
},
/**
* param:
* return:
* exception:
* Description : Move to Top
*/
stepTopUp() {
while (this.stepUp());
},
/**
* param:
* return:
* exception:
* Description : Move up
*/
stepUp() {
if (this.apprPathList[0].selected) return false; // The drafter cannot be changed
const checkParallesGroup = {};
for (let i = 0; i < this.apprPathList.length; i += 1) {
const opt = this.apprPathList[i];
if (opt.parallel) {
if (typeof checkParallesGroup[opt.stepIdx] === 'undefined') {
const checkParalles = [];
checkParalles.push(opt);
checkParallesGroup[opt.stepIdx] = checkParalles;
} else {
checkParallesGroup[opt.stepIdx].push(opt);
}
}
}
// Check if it is parallel
let paralleChkFlag = false;
Object.keys(checkParallesGroup).forEach(i => {
let n = 0;
const opt = checkParallesGroup[i];
Object.keys(opt).forEach(j => {
if (opt[j].selected) {
n += 1;
}
});
if (n > 0 && n !== opt.length) {
paralleChkFlag = true;
}
});
if (paralleChkFlag) {
SDLUtil.alert('sdl.common.message.parallelPaymentCanOnlyBeTransferred');
return false;
}
let flag = false;
for (let i = 1; i < this.apprPathList.length; i += 1) {
if (this.apprPathList[i].selected) {
if (i === 1) {
return false;
}
const tmpItem = this.apprPathList[i];
const parallelFlag = this.apprPathList[i - 1].parallel;
this.apprPathList.splice(i, 1, this.apprPathList[i - 1]);
this.apprPathList.splice(i - 1, 1, tmpItem);
if (i - 2 >= 1 && this.apprPathList[i - 2].parallel && parallelFlag) {
this.stepUp();
}
flag = true;
}
}
this.resetApprovalSeq();
return flag;
},
/**
* param:
* return:
* exception:
* Description : Move down
*/
stepDown() {
if (this.apprPathList[0].selected) return false; // The drafter cannot be changed
const checkParallesGroup = {};
for (let i = 0; i < this.apprPathList.length; i += 1) {
const opt = this.apprPathList[i];
if (opt.parallel) {
if (typeof checkParallesGroup[opt.stepIdx] === 'undefined') {
const checkParalles = [];
checkParalles.push(opt);
checkParallesGroup[opt.stepIdx] = checkParalles;
} else {
checkParallesGroup[opt.stepIdx].push(opt);
}
}
}
// Check if it is parallel
let paralleChkFlag = false;
Object.keys(checkParallesGroup).forEach(i => {
let n = 0;
const opt = checkParallesGroup[i];
Object.keys(opt).forEach(j => {
if (opt[j].selected) {
n += 1;
}
});
if (n > 0 && n !== opt.length) {
paralleChkFlag = true;
}
});
if (paralleChkFlag) {
SDLUtil.alert('sdl.common.message.parallelPaymentCanOnlyBeTransferred');
return false;
}
let flag = false;
for (let i = this.apprPathList.length - 1; i >= 0; i -= 1) {
if (this.apprPathList[i].selected) {
if (i === this.apprPathList.length - 1) {
return false;
}
const tmpItem = this.apprPathList[i];
const parallelFlag = this.apprPathList[i + 1].parallel;
this.apprPathList.splice(i, 1, this.apprPathList[i + 1]);
this.apprPathList.splice(i + 1, 1, tmpItem);
if (i + 2 < this.apprPathList.length && this.apprPathList[i + 2].parallel && parallelFlag) {
this.stepDown();
}
flag = true;
}
}
this.resetApprovalSeq();
return flag;
},
/**
* param:
* return:
* exception:
* Description : Move to the bottom
*/
stepBottomDown() {
while (this.stepDown());
},
},
created() {
this.init();
},
};
</script>
<style></style>
@@ -0,0 +1,162 @@
<!--
Program: Bread crumb
Author: SDL
Brief: Components that visually show where the current user is
-->
<template>
<div class="ui--content-heading">
<strong>
{{ title }}
<a href="javascript:"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
:data-bs-original-title="tooltipMessage"
class="ui--addfavor"
:class="[ isUserMenu ? 'on': 'off' ]"
@click="isUserMenu ? deleteMyMenu(menu.menuId) : addMyMenu(menu.menuId)"
>
<i class="bi bi-star-fill"></i>
</a>
</strong>
<nav aria-label="breadcrumb">
<ol class="breadcrumb float-end">
<li class="breadcrumb-item" v-if="home">
<router-link :to="{ name: 'mainIndexPage' }">{{ homeText }} </router-link>
</li>
<li
class="breadcrumb-item"
:class="{ active: index === list.length - 1 }"
v-for="(item, index) in list"
:key="item.menuId"
>
<a>{{ getLabel(item) }}</a>
<!-- <a :href="'' !== item.menuUrl && item.menuUrl ? item.menuUrl : 'javascript:'">{{item.localeLabel}}</a> -->
<!-- <router-link v-if="index === list.length - 1" :to="item.menuUrl" tag="span">{{item.localeLabel}}</router-link>
<a :href="'' !== item.menuUrl && item.menuUrl ? item.menuUrl : 'javascript:'" v-else>{{item.localeLabel}}</a> -->
</li>
<!-- <li class="breadcrumb-item"><a href="#">Menu1</a></li>
<li class="breadcrumb-item"><a href="#">Submenu1</a></li>
<li class="breadcrumb-item active" aria-current="page">Submenu1-1</li> -->
</ol>
</nav>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import _ from 'lodash';
import SDLLocale from '@/mixin/SDLLocale';
export default {
name: 'Breadcrumb',
mixins: [SDLLocale],
props: {
home: {
required: false,
type: Boolean,
default: false,
},
homeText: {
required: false,
type: String,
default: 'Home',
},
},
data() {
return {
title: '',
list: [],
isUserMenu: false,
};
},
computed: {
...mapGetters({
menus: 'getMenus',
myMenus: 'getMyMenus',
}),
menu() {
return _.find(this.menus, ['menuId', this.menuId]);
},
menuId() {
return this.$route.meta.menuId;
},
pageId() {
return this.$route.meta.pageId;
},
tooltipMessage() {
return this.isUserMenu ? 'Delete My Menu' : 'Add My Menu';
}
},
watch: {
menus: {
handler() {
this.setTitle();
this.setBreadcrumb();
},
deep: true
},
myMenus: {
handler() {
this.setTitle();
this.setBreadcrumb();
},
deep: true
},
locale: {
handler() {
this.setTitle();
this.setBreadcrumb();
},
deep: true
},
$route: {
handler() {
this.setTitle();
this.setBreadcrumb();
},
deep: true
},
},
methods: {
...mapActions(['addMyMenu', 'deleteMyMenu']),
getParentMenu(parentId) {
return this.menus.find(item => item.menuId === parentId);
},
setTitle() {
if (this.menu) {
this.title = this.getLabel(this.menu);
// this.title = this.menu.localeLabel;
this.isUserMenu = this.myMenus.some(myMenu => myMenu.menuId === this.menu.menuId);
}
},
setBreadcrumb() {
if (this.menu) {
this.list = [this.menu];
for (let i = this.menu.menuLevel - 1; i > 0; i -= 1) {
const parentMenu = this.getParentMenu(this.list[0].parentId);
if (parentMenu) {
this.list = [parentMenu, ...this.list];
}
}
}
},
},
created() {
this.setTitle();
this.setBreadcrumb();
},
mounted() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(
tooltipTriggerEl,
{
trigger: 'hover',
}
));
},
};
</script>
<style scoped></style>
@@ -0,0 +1,3 @@
import SdlBreadcrumb from './Breadcrumb.vue';
export default SdlBreadcrumb;
@@ -0,0 +1,471 @@
<!--
Program: Date Picker
Author: SDL
Brief: Date Picker Component
-->
<template>
<div class="d-inline-flex">
<template v-if="lazyLoad">
<datepicker
v-bind="$attrs"
v-if="!period"
v-model="date"
:ref="refKey"
:bootstrap-styling="true"
:wrapper-class="wrapClass"
:language="currentLang"
:format="customFormatter"
:minimum-view="minimumView"
:disabled-dates="disabledDate"
:disabled="disabled"
@input="updateDate"
>
<template #afterDateInput>
<button class="btn btn-outline-secondary" type="button" :data-ref="refKey" :disabled="disabled" @click="openPicker(refKey)" v-click-outside="outsideClick">
<i class="bi bi-calendar"></i>
</button>
</template>
</datepicker>
<template v-else>
<datepicker
v-model="startDate"
:ref="startRefKey"
:bootstrap-styling="true"
:wrapper-class="wrapClass"
:language="currentLang"
:format="customFormatter"
:minimum-view="minimumView"
:disabled-dates="startDisabledDates"
:disabled="startDisabled || disabled"
:placeholder="startPlaceHolder"
@input="updateStartDate"
>
<template #afterDateInput>
<button class="btn btn-outline-secondary" type="button" :data-ref="startRefKey" :disabled="startDisabled || disabled" @click="openPicker(startRefKey)" v-click-outside="outsideClick">
<i class="bi bi-calendar"></i>
</button>
</template>
</datepicker>
<span class="ms-2 me-2 pt-2"> ~ </span>
<datepicker
v-model="endDate"
:ref="endRefKey"
:bootstrap-styling="true"
:wrapper-class="wrapClass"
:language="currentLang"
:format="customFormatter"
:minimum-view="minimumView"
:disabled-dates="endDisabledDates"
:disabled="endDisabled || disabled"
:placeholder="endPlaceHolder"
@input="updateEndDate"
>
<template #afterDateInput>
<button class="btn btn-outline-secondary" type="button" :data-ref="endRefKey" :disabled="endDisabled || disabled" @click="openPicker(endRefKey)" v-click-outside="outsideClick">
<i class="bi bi-calendar"></i>
</button>
</template>
</datepicker>
</template>
</template>
</div>
</template>
<script>
import Datepicker from './components/Datepicker.vue';
import { mapGetters } from 'vuex';
import moment from 'moment';
import _ from 'lodash';
import SDLUtil from '@/utils/SDLUtil';
export default {
components: {
Datepicker,
},
emits: ['input', 'moment', 'update:modelValue'],
props: {
value: {
type: [String, Object],
},
modelValue: {
type: [String, Object],
},
wrapClass: {
required: false,
type: String,
default: 'wd150',
},
// period information
period: {
required: false,
type: Object,
},
// minimum unit - ex) When setting to month, picker can be selected on a monthly basis
minimumView: {
required: false,
type: String,
default: 'day',
},
default: {
required: false,
type: Boolean,
default: true,
},
disabled: {
required: false,
type: Boolean,
default: false,
},
disabledDate: {
required: false,
type: Object,
default: () => {},
},
startDisabledDate: {
required: false,
type: String,
default: '',
},
endDisabledDate: {
required: false,
type: String,
default: '',
},
},
data() {
return {
refKey: _.uniqueId('dp_'),
startRefKey: _.uniqueId('start_dp_'),
endRefKey: _.uniqueId('end_dp_'),
lazyLoad: false,
date: '',
startDate: '',
endDate: '',
currentLang: null,
lang: {},
};
},
created() {
import('./locale/').then(data => {
this.$i18n.availableLocales.forEach(countryCode => {
const country = countryCode.replace('_', '-').split('-')[0];
this.lang[countryCode] = data[country];
});
// Applying the language currently set
this.currentLang = this.lang[this.$i18n.locale];
// period information initialization
if (this.period) {
this.startDate = _.has(this.modelValue, 'startDate') ? moment(this.modelValue.startDate).format(this.standardFormat) : moment().subtract(1, 'M').format(this.standardFormat);
this.endDate = _.has(this.modelValue, 'endDate') ? moment(this.modelValue.endDate).format(this.standardFormat) : moment().format(this.standardFormat);
this.updatePeriod();
} else {
this.date = this.modelValue ? this.modelValue : moment().format(this.standardFormat);
this.updateDate(this.date);
}
this.lazyLoad = true;
});
},
computed: {
...mapGetters({
locale: 'getLocale',
}),
// Start Date Zone Disabled Settings
startDisabled() {
return this.period.startDisabled;
},
// End Date Zone Disabled Settings
endDisabled() {
return this.period.endDisabled;
},
// Start date range settings.
startDisabledDates() {
return {
to: moment(this.startDisabledDate).toDate(),
from: moment(this.endDate).parseZone().toDate(),
};
},
// End date range settings.
endDisabledDates() {
return {
to: moment(this.startDate).toDate(),
};
},
// Start Date PlaceHolder
startPlaceHolder() {
return this.period.startPlaceHolder || '';
},
// End Date PlaceHolder
endPlaceHolder() {
return this.period.endPlaceHolder || '';
},
// Date Format by Locale
dateFormat() {
return this.minimumView === 'month' ? SDLUtil.getMsgProp('data-format.date.ym') : SDLUtil.getMsgProp('data-format.date.ymd');
},
standardFormat() {
return this.minimumView === 'month' ? 'YYYY-MM' : 'YYYY-MM-DD';
},
},
watch: {
// Change of date unit
minimumView() {
if (this.minimumView === 'day') {
this.startDate = moment().subtract(1, 'M').format(this.standardFormat);
this.endDate = moment().format(this.standardFormat);
}
this.updatePeriod();
},
// locale Change
locale() {
this.currentLang = this.lang[this.locale];
},
modelValue() {
if (this.period) {
this.startDate = _.has(this.modelValue, 'startDate') ? moment(this.modelValue.startDate).format(this.standardFormat) : moment().subtract(1, 'M').format(this.standardFormat);
this.endDate = _.has(this.modelValue, 'endDate') ? moment(this.modelValue.endDate).format(this.standardFormat) : moment().format(this.standardFormat);
} else {
this.date = this.modelValue ? this.modelValue : moment().format(this.standardFormat);
}
},
},
methods: {
/**
* param: param -> Object
* return: void
* exception:
* description : Close when clicking outside the calendar button area
*/
outsideClick(param) {
if (_.find(param.evt.path, ['className', 'vdp-datepicker__calendar'])) {
return;
}
if (
param.evt.target.className.indexOf('prev') > -1 ||
param.evt.target.className.indexOf('next') > -1 ||
param.evt.target.className.indexOf('day__month_btn') > -1 ||
param.evt.target.className.indexOf('month__year_btn') > -1 ||
(param.evt.target.nodeName === 'SPAN' && param.evt.target.parentNode.nodeName === 'HEADER')
) {
return;
}
const calendar = this.$refs[param.source.dataset.ref];
if (calendar.isOpen) calendar.close(true);
},
/**
* param: date -> Date
* return: String
* exception:
* description : Return to set date format String
*/
customFormatter(date = moment()) {
return moment(date).format(this.dateFormat);
},
standardFormatter(date = moment()) {
return moment(date).format(this.standardFormat);
},
/**
* param: refKey -> String
* return: String
* exception:
* description : Activate the calendar by clicking the calendar button
*/
openPicker(refKey) {
this.$refs[refKey].showCalendar();
},
/**
* param: value -> Date
* return: String
* exception:
* description : When changing value updates.
*/
updateDate(value) {
this.$emit('input', moment(value).format(this.dateFormat));
this.$emit('update:modelValue', moment(value).format(this.dateFormat));
this.$emit('moment', moment(value));
},
/**
* param: value -> Date
* return: String
* exception:
* description : Start date change
*/
updateStartDate(value) {
this.startDate = value;
this.updatePeriod();
},
/**
* param: value -> Date
* return: String
* exception:
* description : End date change
*/
updateEndDate(value) {
this.endDate = value;
this.updatePeriod();
},
/**
* param:
* return: String
* exception:
* description : When changing the period updates
*/
updatePeriod() {
this.$emit('input', {
startDate: this.standardFormatter(this.startDate),
endDate: this.standardFormatter(this.endDate),
});
this.$emit('update:modelValue', {
startDate: this.standardFormatter(this.startDate),
endDate: this.standardFormatter(this.endDate),
});
},
},
};
</script>
<style>
.wd150 {
width: 150px !important;
}
.vdp-datepicker__calendar {
z-index: 118 !important;
}
.rtl {
direction: rtl;
}
.vdp-datepicker {
position: relative;
text-align: left;
}
.vdp-datepicker * {
box-sizing: border-box;
}
.vdp-datepicker__calendar {
position: absolute;
z-index: 100;
background: #fff;
width: 300px;
border: 1px solid #ccc;
}
.vdp-datepicker__calendar header {
display: block;
line-height: 40px;
}
.vdp-datepicker__calendar header span {
display: inline-block;
text-align: center;
width: 71.42857142857143%;
float: left;
}
.vdp-datepicker__calendar header .prev,
.vdp-datepicker__calendar header .next {
width: 14.285714285714286%;
float: left;
text-indent: -10000px;
position: relative;
}
.vdp-datepicker__calendar header .prev:after,
.vdp-datepicker__calendar header .next:after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
border: 6px solid transparent;
}
.vdp-datepicker__calendar header .prev:after {
border-right: 10px solid #000;
margin-left: -5px;
}
.vdp-datepicker__calendar header .prev.disabled:after {
border-right: 10px solid #ddd;
}
.vdp-datepicker__calendar header .next:after {
border-left: 10px solid #000;
margin-left: 5px;
}
.vdp-datepicker__calendar header .next.disabled:after {
border-left: 10px solid #ddd;
}
.vdp-datepicker__calendar header .prev:not(.disabled),
.vdp-datepicker__calendar header .next:not(.disabled),
.vdp-datepicker__calendar header .up:not(.disabled) {
cursor: pointer;
}
.vdp-datepicker__calendar header .prev:not(.disabled):hover,
.vdp-datepicker__calendar header .next:not(.disabled):hover,
.vdp-datepicker__calendar header .up:not(.disabled):hover {
background: #eee;
}
.vdp-datepicker__calendar .disabled {
color: #ddd;
cursor: default;
}
.vdp-datepicker__calendar .flex-rtl {
display: flex;
width: inherit;
flex-wrap: wrap;
}
.vdp-datepicker__calendar .cell {
display: inline-block;
padding: 0 5px;
width: 14.285714285714286%;
height: 40px;
line-height: 40px;
text-align: center;
vertical-align: middle;
border: 1px solid transparent;
}
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year {
cursor: pointer;
}
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day:hover,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month:hover,
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year:hover {
border: 1px solid #4bd;
}
.vdp-datepicker__calendar .cell.selected {
background: #4bd;
}
.vdp-datepicker__calendar .cell.selected:hover {
background: #4bd;
}
.vdp-datepicker__calendar .cell.selected.highlighted {
background: #4bd;
}
.vdp-datepicker__calendar .cell.highlighted {
background: #cae5ed;
}
.vdp-datepicker__calendar .cell.highlighted.disabled {
color: #a3a3a3;
}
.vdp-datepicker__calendar .cell.grey {
color: #888;
}
.vdp-datepicker__calendar .cell.grey:hover {
background: inherit;
}
.vdp-datepicker__calendar .cell.day-header {
font-size: 85%;
white-space: nowrap;
cursor: inherit;
}
.vdp-datepicker__calendar .cell.day-header:hover {
background: inherit;
}
.vdp-datepicker__calendar .month,
.vdp-datepicker__calendar .year {
width: 33.333%;
}
.vdp-datepicker__clear-button,
.vdp-datepicker__calendar-button {
cursor: pointer;
font-style: normal;
}
.vdp-datepicker__clear-button.disabled,
.vdp-datepicker__calendar-button.disabled {
color: #999;
cursor: default;
}
</style>
@@ -0,0 +1,158 @@
<template>
<div :class="{'input-group' : bootstrapStyling}">
<!-- Calendar Button -->
<span v-if="calendarButton" class="vdp-datepicker__calendar-button" :class="{'input-group-prepend' : bootstrapStyling}" @click="showCalendar" v-bind:style="{'cursor:not-allowed;' : disabled}">
<span :class="{'input-group-text' : bootstrapStyling}">
<i :class="calendarButtonIcon">
{{ calendarButtonIconContent }}
<span v-if="!calendarButtonIcon">&hellip;</span>
</i>
</span>
</span>
<!-- Input -->
<input
:type="inline ? 'hidden' : 'text'"
:class="computedInputClass"
:name="name"
:ref="refName"
:id="id"
:value="formattedValue"
:open-date="openDate"
:placeholder="placeholder"
:clear-button="clearButton"
:disabled="disabled"
:required="required"
:readonly="!typeable"
@click="showCalendar"
@keyup="parseTypedDate"
@blur="inputBlurred"
autocomplete="off">
<!-- Clear Button -->
<span v-if="clearButton && selectedDate" class="vdp-datepicker__clear-button" :class="{'input-group-append' : bootstrapStyling}" @click="clearDate()">
<span :class="{'input-group-text' : bootstrapStyling}">
<i :class="clearButtonIcon">
<span v-if="!clearButtonIcon">&times;</span>
</i>
</span>
</span>
<slot name="afterDateInput"></slot>
</div>
</template>
<script>
import { makeDateUtils } from '../utils/DateUtils'
export default {
props: {
selectedDate: Date,
resetTypedDate: [Date],
format: [String, Function],
translation: Object,
inline: Boolean,
id: String,
name: String,
refName: String,
openDate: Date,
placeholder: String,
inputClass: [String, Object, Array],
clearButton: {
type: Boolean,
default: () => undefined,
},
clearButtonIcon: String,
calendarButton: Boolean,
calendarButtonIcon: String,
calendarButtonIconContent: String,
disabled: Boolean,
required: Boolean,
typeable: Boolean,
bootstrapStyling: Boolean,
useUtc: Boolean
},
emits: ['showCalendar', 'typedDate', 'closeCalendar', 'clearDate'],
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
input: null,
typedDate: false,
utils: constructedDateUtils
}
},
computed: {
formattedValue () {
if (!this.selectedDate) {
return null
}
if (this.typedDate) {
return this.typedDate
}
return typeof this.format === 'function'
? this.format(this.selectedDate)
: this.utils.formatDate(new Date(this.selectedDate), this.format, this.translation)
},
computedInputClass () {
if (this.bootstrapStyling) {
if (typeof this.inputClass === 'string') {
return [this.inputClass, 'form-control'].join(' ')
}
return {'form-control': true, ...this.inputClass}
}
return this.inputClass
}
},
watch: {
resetTypedDate () {
this.typedDate = false
}
},
methods: {
showCalendar () {
this.$emit('showCalendar')
},
/**
* Attempt to parse a typed date
* @param {Event} event
*/
parseTypedDate (event) {
// close calendar if escape or enter are pressed
if ([
27, // escape
13 // enter
].includes(event.keyCode)) {
this.input.blur()
}
if (this.typeable) {
const typedDate = Date.parse(this.input.value)
if (!isNaN(typedDate)) {
this.typedDate = this.input.value
this.$emit('typedDate', new Date(this.typedDate))
}
}
},
/**
* nullify the typed date to defer to regular formatting
* called once the input is blurred
*/
inputBlurred () {
if (this.typeable && isNaN(Date.parse(this.input.value))) {
this.clearDate()
this.input.value = null
this.typedDate = null
}
this.$emit('closeCalendar')
},
/**
* emit a clearDate event
*/
clearDate () {
this.$emit('clearDate')
}
},
mounted () {
this.input = this.$el.querySelector('input')
}
}
// eslint-disable-next-line
;
</script>
@@ -0,0 +1,479 @@
<template>
<div class="vdp-datepicker" :class="[wrapperClass, isRtl ? 'rtl' : '']">
<date-input
:selectedDate="selectedDate"
:resetTypedDate="resetTypedDate"
:format="format"
:translation="translation"
:inline="inline"
:id="id"
:name="name"
:refName="refName"
:openDate="openDate"
:placeholder="placeholder"
:inputClass="inputClass"
:typeable="typeable"
:clearButton="clearButton"
:clearButtonIcon="clearButtonIcon"
:calendarButton="calendarButton"
:calendarButtonIcon="calendarButtonIcon"
:calendarButtonIconContent="calendarButtonIconContent"
:disabled="disabled"
:required="required"
:bootstrapStyling="bootstrapStyling"
:use-utc="useUtc"
@showCalendar="showCalendar"
@closeCalendar="close"
@typedDate="setTypedDate"
@clearDate="clearDate">
<template #afterDateInput>
<slot name="afterDateInput"></slot>
</template>
</date-input>
<!-- Day View -->
<picker-day
v-if="allowedToShowView('day')"
:pageDate="pageDate"
:selectedDate="selectedDate"
:showDayView="showDayView"
:fullMonthName="fullMonthName"
:allowedToShowView="allowedToShowView"
:disabledDates="disabledDates"
:highlighted="highlighted"
:calendarClass="calendarClass"
:calendarStyle="calendarStyle"
:translation="translation"
:pageTimestamp="pageTimestamp"
:isRtl="isRtl"
:mondayFirst="mondayFirst"
:dayCellContent="dayCellContent"
:use-utc="useUtc"
@changedMonth="handleChangedMonthFromDayPicker"
@selectDate="selectDate"
@showMonthCalendar="showMonthCalendar"
@selectedDisabled="selectDisabledDate">
<template #beforeCalendarHeader>
<slot name="beforeCalendarHeader"></slot>
</template>
</picker-day>
<!-- Month View -->
<picker-month
v-if="allowedToShowView('month')"
:pageDate="pageDate"
:selectedDate="selectedDate"
:showMonthView="showMonthView"
:allowedToShowView="allowedToShowView"
:disabledDates="disabledDates"
:calendarClass="calendarClass"
:calendarStyle="calendarStyle"
:translation="translation"
:isRtl="isRtl"
:use-utc="useUtc"
@selectMonth="selectMonth"
@showYearCalendar="showYearCalendar"
@changedYear="setPageDate">
<template #beforeCalendarHeader>
<slot name="beforeCalendarHeader"></slot>
</template>
</picker-month>
<!-- Year View -->
<picker-year
v-if="allowedToShowView('year')"
:pageDate="pageDate"
:selectedDate="selectedDate"
:showYearView="showYearView"
:allowedToShowView="allowedToShowView"
:disabledDates="disabledDates"
:calendarClass="calendarClass"
:calendarStyle="calendarStyle"
:translation="translation"
:isRtl="isRtl"
:use-utc="useUtc"
@selectYear="selectYear"
@changedDecade="setPageDate">
<template #beforeCalendarHeader>
<slot name="beforeCalendarHeader"></slot>
</template>
</picker-year>
</div>
</template>
<script>
import en from '../locale/translations/en'
import DateInput from './DateInput.vue'
import PickerDay from './PickerDay.vue'
import PickerMonth from './PickerMonth.vue'
import PickerYear from './PickerYear.vue'
import utils, { makeDateUtils } from '../utils/DateUtils'
export default {
components: {
DateInput,
PickerDay,
PickerMonth,
PickerYear
},
emits: ['closed', 'selected', 'input', 'update:modelValue', 'changedMonth', 'cleared', 'selectedDisabled', 'changedYear'],
props: {
value: {
validator: val => utils.validateDateInput(val)
},
modelValue: {
validator: val => utils.validateDateInput(val)
},
name: String,
refName: String,
id: String,
format: {
type: [String, Function],
default: 'dd MMM yyyy'
},
language: {
type: Object,
default: () => en
},
openDate: {
validator: val => utils.validateDateInput(val)
},
dayCellContent: Function,
fullMonthName: Boolean,
disabledDates: Object,
highlighted: Object,
placeholder: String,
inline: Boolean,
calendarClass: [String, Object, Array],
inputClass: [String, Object, Array],
wrapperClass: [String, Object, Array],
mondayFirst: Boolean,
clearButton: {
type: Boolean,
default: () => undefined,
},
clearButtonIcon: String,
calendarButton: Boolean,
calendarButtonIcon: String,
calendarButtonIconContent: String,
bootstrapStyling: Boolean,
initialView: String,
disabled: Boolean,
required: Boolean,
typeable: Boolean,
useUtc: Boolean,
minimumView: {
type: String,
default: 'day'
},
maximumView: {
type: String,
default: 'year'
}
},
data () {
const startDate = this.openDate ? new Date(this.openDate) : new Date()
const constructedDateUtils = makeDateUtils(this.useUtc)
const pageTimestamp = constructedDateUtils.setDate(startDate, 1)
return {
/*
* Vue cannot observe changes to a Date Object so date must be stored as a timestamp
* This represents the first day of the current viewing month
* {Number}
*/
pageTimestamp,
/*
* Selected Date
* {Date}
*/
selectedDate: null,
/*
* Flags to show calendar views
* {Boolean}
*/
showDayView: false,
showMonthView: false,
showYearView: false,
/*
* Positioning
*/
calendarHeight: 0,
resetTypedDate: new Date(),
utils: constructedDateUtils
}
},
watch: {
value (value) {
this.setValue(value)
},
openDate () {
this.setPageDate()
},
initialView () {
this.setInitialView()
},
modelValue(value) {
this.setValue(value);
},
},
computed: {
computedInitialView () {
if (!this.initialView) {
return this.minimumView
}
return this.initialView
},
pageDate () {
return new Date(this.pageTimestamp)
},
translation () {
return this.language
},
calendarStyle () {
return {
position: this.isInline ? 'static' : undefined
}
},
isOpen () {
return this.showDayView || this.showMonthView || this.showYearView
},
isInline () {
return !!this.inline
},
isRtl () {
return this.translation.rtl === true
}
},
methods: {
/**
* Called in the event that the user navigates to date pages and
* closes the picker without selecting a date.
*/
resetDefaultPageDate () {
if (this.selectedDate === null) {
this.setPageDate()
return
}
this.setPageDate(this.selectedDate)
},
/**
* Effectively a toggle to show/hide the calendar
* @return {mixed}
*/
showCalendar () {
if (this.disabled || this.isInline) {
return false
}
if (this.isOpen) {
return this.close(true)
}
this.setInitialView()
},
/**
* Sets the initial picker page view: day, month or year
*/
setInitialView () {
const initialView = this.computedInitialView
if (!this.allowedToShowView(initialView)) {
throw new Error(`initialView '${this.initialView}' cannot be rendered based on minimum '${this.minimumView}' and maximum '${this.maximumView}'`)
}
switch (initialView) {
case 'year':
this.showYearCalendar()
break
case 'month':
this.showMonthCalendar()
break
default:
this.showDayCalendar()
break
}
},
/**
* Are we allowed to show a specific picker view?
* @param {String} view
* @return {Boolean}
*/
allowedToShowView (view) {
const views = ['day', 'month', 'year']
const minimumViewIndex = views.indexOf(this.minimumView)
const maximumViewIndex = views.indexOf(this.maximumView)
const viewIndex = views.indexOf(view)
return viewIndex >= minimumViewIndex && viewIndex <= maximumViewIndex
},
/**
* Show the day picker
* @return {Boolean}
*/
showDayCalendar () {
if (!this.allowedToShowView('day')) {
return false
}
this.close()
this.showDayView = true
return true
},
/**
* Show the month picker
* @return {Boolean}
*/
showMonthCalendar () {
if (!this.allowedToShowView('month')) {
return false
}
this.close()
this.showMonthView = true
return true
},
/**
* Show the year picker
* @return {Boolean}
*/
showYearCalendar () {
if (!this.allowedToShowView('year')) {
return false
}
this.close()
this.showYearView = true
return true
},
/**
* Set the selected date
* @param {Number} timestamp
*/
setDate (timestamp) {
const date = new Date(timestamp)
this.selectedDate = date
this.setPageDate(date)
this.$emit('selected', date)
this.$emit('input', date)
this.$emit('update:modelValue', date)
},
/**
* Clear the selected date
*/
clearDate () {
this.selectedDate = null
this.setPageDate()
this.$emit('selected', null)
this.$emit('input', null)
this.$emit('cleared')
},
/**
* @param {Object} date
*/
selectDate (date) {
this.setDate(date.timestamp)
if (!this.isInline) {
this.close(true)
}
this.resetTypedDate = new Date()
},
/**
* @param {Object} date
*/
selectDisabledDate (date) {
this.$emit('selectedDisabled', date)
},
/**
* @param {Object} month
*/
selectMonth (month) {
const date = new Date(month.timestamp)
if (this.allowedToShowView('day')) {
this.setPageDate(date)
this.$emit('changedMonth', month)
this.showDayCalendar()
} else {
this.selectDate(month)
}
},
/**
* @param {Object} year
*/
selectYear (year) {
const date = new Date(year.timestamp)
if (this.allowedToShowView('month')) {
this.setPageDate(date)
this.$emit('changedYear', year)
this.showMonthCalendar()
} else {
this.selectDate(year)
}
},
/**
* Set the datepicker value
* @param {Date|String|Number|null} date
*/
setValue (date) {
if (typeof date === 'string' || typeof date === 'number') {
let parsed = new Date(date)
date = isNaN(parsed.valueOf()) ? null : parsed
}
if (!date) {
this.setPageDate()
this.selectedDate = null
return
}
this.selectedDate = date
this.setPageDate(date)
},
/**
* Sets the date that the calendar should open on
*/
setPageDate (date) {
if (!date) {
if (this.openDate) {
date = new Date(this.openDate)
} else {
date = new Date()
}
}
this.pageTimestamp = this.utils.setDate(new Date(date), 1)
},
/**
* Handles a month change from the day picker
*/
handleChangedMonthFromDayPicker (date) {
this.setPageDate(date)
this.$emit('changedMonth', date)
},
/**
* Set the date from a typedDate event
*/
setTypedDate (date) {
this.setDate(date.getTime())
},
/**
* Close all calendar layers
* @param {Boolean} emitEvent - emit close event
*/
close (emitEvent) {
this.showDayView = this.showMonthView = this.showYearView = false
if (!this.isInline) {
if (emitEvent) {
this.$emit('closed')
}
document.removeEventListener('click', this.clickOutside, false)
}
},
/**
* Initiate the component
*/
init () {
if (this.modelValue) {
this.setValue(this.modelValue)
}
if (this.isInline) {
this.setInitialView()
}
}
},
mounted () {
this.init();
}
};
</script>
@@ -0,0 +1,376 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showDayView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextMonth() : previousMonth()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span class="day__month_btn" @click="showMonthCalendar" :class="allowedToShowView('month') ? 'up' : ''">{{ isYmd ? currYearName : currMonthName }} {{ isYmd ? currMonthName : currYearName }}</span>
<span
@click="isRtl ? previousMonth() : nextMonth()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<div :class="isRtl ? 'flex-rtl' : ''">
<span class="cell day-header" v-for="d in daysOfWeek" :key="d.timestamp">{{ d }}</span>
<template v-if="blankDays > 0">
<span class="cell day blank" v-for="d in blankDays" :key="d.timestamp"></span>
</template><!--
--><span class="cell day"
v-for="day in days"
:key="day.timestamp"
:class="dayClasses(day)"
v-html="dayCellContent(day)"
@click="selectDate(day)"></span>
</div>
</div>
</template>
<script>
import { makeDateUtils } from '../utils/DateUtils'
export default {
props: {
showDayView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
fullMonthName: Boolean,
allowedToShowView: Function,
dayCellContent: {
type: Function,
default: day => day.date
},
disabledDates: Object,
highlighted: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
mondayFirst: Boolean,
useUtc: Boolean
},
emits: ['changedMonth', 'selectedDisabled', 'selectDate', 'showMonthCalendar'],
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
computed: {
/**
* Returns an array of day names
* @return {String[]}
*/
daysOfWeek () {
if (this.mondayFirst) {
const tempDays = this.translation.days.slice()
tempDays.push(tempDays.shift())
return tempDays
}
return this.translation.days
},
/**
* Returns the day number of the week less one for the first of the current month
* Used to show amount of empty cells before the first in the day calendar layout
* @return {Number}
*/
blankDays () {
const d = this.pageDate
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
if (this.mondayFirst) {
return this.utils.getDay(dObj) > 0 ? this.utils.getDay(dObj) - 1 : 6
}
return this.utils.getDay(dObj)
},
/**
* @return {Object[]}
*/
days () {
const d = this.pageDate
let days = []
// set up a new date object to the beginning of the current 'page'
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
let daysInMonth = this.utils.daysInMonth(this.utils.getFullYear(dObj), this.utils.getMonth(dObj))
for (let i = 0; i < daysInMonth; i++) {
days.push({
date: this.utils.getDate(dObj),
timestamp: dObj.getTime(),
isSelected: this.isSelectedDate(dObj),
isDisabled: this.isDisabledDate(dObj),
isHighlighted: this.isHighlightedDate(dObj),
isHighlightStart: this.isHighlightStart(dObj),
isHighlightEnd: this.isHighlightEnd(dObj),
isToday: this.utils.compareDates(dObj, new Date()),
isWeekend: this.utils.getDay(dObj) === 0 || this.utils.getDay(dObj) === 6,
isSaturday: this.utils.getDay(dObj) === 6,
isSunday: this.utils.getDay(dObj) === 0
})
this.utils.setDate(dObj, this.utils.getDate(dObj) + 1)
}
return days
},
/**
* Gets the name of the month the current page is on
* @return {String}
*/
currMonthName () {
const monthName = this.fullMonthName ? this.translation.months : this.translation.monthsAbbr
return this.utils.getMonthNameAbbr(this.utils.getMonth(this.pageDate), monthName)
},
/**
* Gets the name of the year that current page is on
* @return {Number}
*/
currYearName () {
const yearSuffix = this.translation.yearSuffix
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
},
/**
* Is this translation using year/month/day format?
* @return {Boolean}
*/
isYmd () {
return this.translation.ymd && this.translation.ymd === true
},
/**
* Is the left hand navigation button disabled?
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextMonthDisabled(this.pageTimestamp)
: this.isPreviousMonthDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation button disabled?
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousMonthDisabled(this.pageTimestamp)
: this.isNextMonthDisabled(this.pageTimestamp)
}
},
methods: {
selectDate (date) {
if (date.isDisabled) {
this.$emit('selectedDisabled', date)
return false
}
this.$emit('selectDate', date)
},
/**
* @return {Number}
*/
getPageMonth () {
return this.utils.getMonth(this.pageDate)
},
/**
* Emit an event to show the month picker
*/
showMonthCalendar () {
this.$emit('showMonthCalendar')
},
/**
* Change the page month
* @param {Number} incrementBy
*/
changeMonth (incrementBy) {
let date = this.pageDate
this.utils.setMonth(date, this.utils.getMonth(date) + incrementBy)
this.$emit('changedMonth', date)
},
/**
* Decrement the page month
*/
previousMonth () {
if (!this.isPreviousMonthDisabled()) {
this.changeMonth(-1)
}
},
/**
* Is the previous month disabled?
* @return {Boolean}
*/
isPreviousMonthDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
let d = this.pageDate
return this.utils.getMonth(this.disabledDates.to) >= this.utils.getMonth(d) &&
this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(d)
},
/**
* Increment the current page month
*/
nextMonth () {
if (!this.isNextMonthDisabled()) {
this.changeMonth(+1)
}
},
/**
* Is the next month disabled?
* @return {Boolean}
*/
isNextMonthDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
let d = this.pageDate
return this.utils.getMonth(this.disabledDates.from) <= this.utils.getMonth(d) &&
this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(d)
},
/**
* Whether a day is selected
* @param {Date}
* @return {Boolean}
*/
isSelectedDate (dObj) {
return this.selectedDate && this.utils.compareDates(this.selectedDate, dObj)
},
/**
* Whether a day is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledDate (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined') {
return false
}
if (typeof this.disabledDates.dates !== 'undefined') {
this.disabledDates.dates.forEach((d) => {
if (this.utils.compareDates(date, d)) {
disabledDates = true
return true
}
})
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to && date < this.disabledDates.to) {
disabledDates = true
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from && date > this.disabledDates.from) {
disabledDates = true
}
if (typeof this.disabledDates.ranges !== 'undefined') {
this.disabledDates.ranges.forEach((range) => {
if (typeof range.from !== 'undefined' && range.from && typeof range.to !== 'undefined' && range.to) {
if (date < range.to && date > range.from) {
disabledDates = true
return true
}
}
})
}
if (typeof this.disabledDates.days !== 'undefined' && this.disabledDates.days.indexOf(this.utils.getDay(date)) !== -1) {
disabledDates = true
}
if (typeof this.disabledDates.daysOfMonth !== 'undefined' && this.disabledDates.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
disabledDates = true
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
},
/**
* Whether a day is highlighted (only if it is not disabled already except when highlighted.includeDisabled is true)
* @param {Date}
* @return {Boolean}
*/
isHighlightedDate (date) {
if (!(this.highlighted && this.highlighted.includeDisabled) && this.isDisabledDate(date)) {
return false
}
let highlighted = false
if (typeof this.highlighted === 'undefined') {
return false
}
if (typeof this.highlighted.dates !== 'undefined') {
this.highlighted.dates.forEach((d) => {
if (this.utils.compareDates(date, d)) {
highlighted = true
return true
}
})
}
if (this.isDefined(this.highlighted.from) && this.isDefined(this.highlighted.to)) {
highlighted = date >= this.highlighted.from && date <= this.highlighted.to
}
if (typeof this.highlighted.days !== 'undefined' && this.highlighted.days.indexOf(this.utils.getDay(date)) !== -1) {
highlighted = true
}
if (typeof this.highlighted.daysOfMonth !== 'undefined' && this.highlighted.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
highlighted = true
}
if (typeof this.highlighted.customPredictor === 'function' && this.highlighted.customPredictor(date)) {
highlighted = true
}
return highlighted
},
dayClasses (day) {
return {
'selected': day.isSelected,
'disabled': day.isDisabled,
'highlighted': day.isHighlighted,
'today': day.isToday,
'weekend': day.isWeekend,
'sat': day.isSaturday,
'sun': day.isSunday,
'highlight-start': day.isHighlightStart,
'highlight-end': day.isHighlightEnd
}
},
/**
* Whether a day is highlighted and it is the first date
* in the highlighted range of dates
* @param {Date}
* @return {Boolean}
*/
isHighlightStart (date) {
return this.isHighlightedDate(date) &&
(this.highlighted.from instanceof Date) &&
(this.utils.getFullYear(this.highlighted.from) === this.utils.getFullYear(date)) &&
(this.utils.getMonth(this.highlighted.from) === this.utils.getMonth(date)) &&
(this.utils.getDate(this.highlighted.from) === this.utils.getDate(date))
},
/**
* Whether a day is highlighted and it is the first date
* in the highlighted range of dates
* @param {Date}
* @return {Boolean}
*/
isHighlightEnd (date) {
return this.isHighlightedDate(date) &&
(this.highlighted.to instanceof Date) &&
(this.utils.getFullYear(this.highlighted.to) === this.utils.getFullYear(date)) &&
(this.utils.getMonth(this.highlighted.to) === this.utils.getMonth(date)) &&
(this.utils.getDate(this.highlighted.to) === this.utils.getDate(date))
},
/**
* Helper
* @param {mixed} prop
* @return {Boolean}
*/
isDefined (prop) {
return typeof prop !== 'undefined' && prop
}
}
}
// eslint-disable-next-line
;
</script>
@@ -0,0 +1,201 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showMonthView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextYear() : previousYear()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span class="month__year_btn" @click="showYearCalendar" :class="allowedToShowView('year') ? 'up' : ''">{{ pageYearName }}</span>
<span
@click="isRtl ? previousYear() : nextYear()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<span class="cell month"
v-for="month in months"
:key="month.timestamp"
:class="{'selected': month.isSelected, 'disabled': month.isDisabled}"
@click.stop="selectMonth(month)">{{ month.month }}</span>
</div>
</template>
<script>
import { makeDateUtils } from '../utils/DateUtils'
export default {
props: {
showMonthView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
disabledDates: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
allowedToShowView: Function,
useUtc: Boolean
},
emits: ['selectMonth', 'changedYear', 'showYearCalendar'],
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
computed: {
months () {
const d = this.pageDate
let months = []
// set up a new date object to the beginning of the current 'page'
let dObj = this.useUtc
? new Date(Date.UTC(d.getUTCFullYear(), 0, d.getUTCDate()))
: new Date(d.getFullYear(), 0, d.getDate(), d.getHours(), d.getMinutes())
for (let i = 0; i < 12; i++) {
months.push({
month: this.utils.getMonthName(i, this.translation.months),
timestamp: dObj.getTime(),
isSelected: this.isSelectedMonth(dObj),
isDisabled: this.isDisabledMonth(dObj)
})
this.utils.setMonth(dObj, this.utils.getMonth(dObj) + 1)
}
return months
},
/**
* Get year name on current page.
* @return {String}
*/
pageYearName () {
const yearSuffix = this.translation.yearSuffix
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
},
/**
* Is the left hand navigation disabled
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextYearDisabled(this.pageTimestamp)
: this.isPreviousYearDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation disabled
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousYearDisabled(this.pageTimestamp)
: this.isNextYearDisabled(this.pageTimestamp)
}
},
methods: {
/**
* Emits a selectMonth event
* @param {Object} month
*/
selectMonth (month) {
if (month.isDisabled) {
return false
}
this.$emit('selectMonth', month)
},
/**
* Changes the year up or down
* @param {Number} incrementBy
*/
changeYear (incrementBy) {
let date = this.pageDate
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
this.$emit('changedYear', date)
},
/**
* Decrements the year
*/
previousYear () {
if (!this.isPreviousYearDisabled()) {
this.changeYear(-1)
}
},
/**
* Checks if the previous year is disabled or not
* @return {Boolean}
*/
isPreviousYearDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
return this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(this.pageDate)
},
/**
* Increments the year
*/
nextYear () {
if (!this.isNextYearDisabled()) {
this.changeYear(1)
}
},
/**
* Checks if the next year is disabled or not
* @return {Boolean}
*/
isNextYearDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
return this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(this.pageDate)
},
/**
* Emits an event that shows the year calendar
*/
showYearCalendar () {
this.$emit('showYearCalendar')
},
/**
* Whether the selected date is in this month
* @param {Date}
* @return {Boolean}
*/
isSelectedMonth (date) {
return (this.selectedDate &&
this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date) &&
this.utils.getMonth(this.selectedDate) === this.utils.getMonth(date))
},
/**
* Whether a month is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledMonth (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined') {
return false
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
if (
(this.utils.getMonth(date) < this.utils.getMonth(this.disabledDates.to) && this.utils.getFullYear(date) <= this.utils.getFullYear(this.disabledDates.to)) ||
this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)
) {
disabledDates = true
}
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
if (
(this.utils.getMonth(date) > this.utils.getMonth(this.disabledDates.from) && this.utils.getFullYear(date) >= this.utils.getFullYear(this.disabledDates.from)) ||
this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)
) {
disabledDates = true
}
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
}
}
}
// eslint-disable-next-line
;
</script>
@@ -0,0 +1,175 @@
<template>
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showYearView" :style="calendarStyle" @mousedown.prevent>
<slot name="beforeCalendarHeader"></slot>
<header>
<span
@click="isRtl ? nextDecade() : previousDecade()"
class="prev"
:class="{'disabled': isLeftNavDisabled}">&lt;</span>
<span>{{ getPageDecade }}</span>
<span
@click="isRtl ? previousDecade() : nextDecade()"
class="next"
:class="{'disabled': isRightNavDisabled}">&gt;</span>
</header>
<span
class="cell year"
v-for="year in years"
:key="year.timestamp"
:class="{ 'selected': year.isSelected, 'disabled': year.isDisabled }"
@click.stop="selectYear(year)">{{ year.year }}</span>
</div>
</template>
<script>
import { makeDateUtils } from '../utils/DateUtils'
export default {
props: {
showYearView: Boolean,
selectedDate: Date,
pageDate: Date,
pageTimestamp: Number,
disabledDates: Object,
highlighted: Object,
calendarClass: [String, Object, Array],
calendarStyle: Object,
translation: Object,
isRtl: Boolean,
allowedToShowView: Function,
useUtc: Boolean
},
emits: ['selectYear', 'changedDecade'],
computed: {
years () {
const d = this.pageDate
let years = []
// set up a new date object to the beginning of the current 'page'7
let dObj = this.useUtc
? new Date(Date.UTC(Math.floor(d.getUTCFullYear() / 10) * 10, d.getUTCMonth(), d.getUTCDate()))
: new Date(Math.floor(d.getFullYear() / 10) * 10, d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
for (let i = 0; i < 10; i++) {
years.push({
year: this.utils.getFullYear(dObj),
timestamp: dObj.getTime(),
isSelected: this.isSelectedYear(dObj),
isDisabled: this.isDisabledYear(dObj)
})
this.utils.setFullYear(dObj, this.utils.getFullYear(dObj) + 1)
}
return years
},
/**
* @return {String}
*/
getPageDecade () {
const decadeStart = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10
const decadeEnd = decadeStart + 9
const yearSuffix = this.translation.yearSuffix
return `${decadeStart} - ${decadeEnd}${yearSuffix}`
},
/**
* Is the left hand navigation button disabled?
* @return {Boolean}
*/
isLeftNavDisabled () {
return this.isRtl
? this.isNextDecadeDisabled(this.pageTimestamp)
: this.isPreviousDecadeDisabled(this.pageTimestamp)
},
/**
* Is the right hand navigation button disabled?
* @return {Boolean}
*/
isRightNavDisabled () {
return this.isRtl
? this.isPreviousDecadeDisabled(this.pageTimestamp)
: this.isNextDecadeDisabled(this.pageTimestamp)
}
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc)
return {
utils: constructedDateUtils
}
},
methods: {
selectYear (year) {
if (year.isDisabled) {
return false
}
this.$emit('selectYear', year)
},
changeYear (incrementBy) {
let date = this.pageDate
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
this.$emit('changedDecade', date)
},
previousDecade () {
if (this.isPreviousDecadeDisabled()) {
return false
}
this.changeYear(-10)
},
isPreviousDecadeDisabled () {
if (!this.disabledDates || !this.disabledDates.to) {
return false
}
const disabledYear = this.utils.getFullYear(this.disabledDates.to)
const lastYearInPreviousPage = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10 - 1
return disabledYear > lastYearInPreviousPage
},
nextDecade () {
if (this.isNextDecadeDisabled()) {
return false
}
this.changeYear(10)
},
isNextDecadeDisabled () {
if (!this.disabledDates || !this.disabledDates.from) {
return false
}
const disabledYear = this.utils.getFullYear(this.disabledDates.from)
const firstYearInNextPage = Math.ceil(this.utils.getFullYear(this.pageDate) / 10) * 10
return disabledYear < firstYearInNextPage
},
/**
* Whether the selected date is in this year
* @param {Date}
* @return {Boolean}
*/
isSelectedYear (date) {
return this.selectedDate && this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date)
},
/**
* Whether a year is disabled
* @param {Date}
* @return {Boolean}
*/
isDisabledYear (date) {
let disabledDates = false
if (typeof this.disabledDates === 'undefined' || !this.disabledDates) {
return false
}
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
if (this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)) {
disabledDates = true
}
}
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
if (this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)) {
disabledDates = true
}
}
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
disabledDates = true
}
return disabledDates
}
}
}
// eslint-disable-next-line
;
</script>
@@ -0,0 +1,3 @@
import SdlDatePicker from './SdlDatePicker.vue';
export default SdlDatePicker;
@@ -0,0 +1,57 @@
export default class Language {
constructor (language, months, monthsAbbr, days) {
this.language = language
this.months = months
this.monthsAbbr = monthsAbbr
this.days = days
this.rtl = false
this.ymd = false
this.yearSuffix = ''
}
get language () {
return this._language
}
set language (language) {
if (typeof language !== 'string') {
throw new TypeError('Language must be a string')
}
this._language = language
}
get months () {
return this._months
}
set months (months) {
if (months.length !== 12) {
throw new RangeError(`There must be 12 months for ${this.language} language`)
}
this._months = months
}
get monthsAbbr () {
return this._monthsAbbr
}
set monthsAbbr (monthsAbbr) {
if (monthsAbbr.length !== 12) {
throw new RangeError(`There must be 12 abbreviated months for ${this.language} language`)
}
this._monthsAbbr = monthsAbbr
}
get days () {
return this._days
}
set days (days) {
if (days.length !== 7) {
throw new RangeError(`There must be 7 days for ${this.language} language`)
}
this._days = days
}
}
// eslint-disable-next-line
;
@@ -0,0 +1,105 @@
import af from './translations/af'
import ar from './translations/ar'
import bg from './translations/bg'
import bs from './translations/bs'
import ca from './translations/ca'
import cs from './translations/cs'
import da from './translations/da'
import de from './translations/de'
import ee from './translations/ee'
import el from './translations/el'
import en from './translations/en'
import es from './translations/es'
import fa from './translations/fa'
import fi from './translations/fi'
import fo from './translations/fo'
import fr from './translations/fr'
import ge from './translations/ge'
import gl from './translations/gl'
import he from './translations/he'
import hr from './translations/hr'
import hu from './translations/hu'
import id from './translations/id'
import is from './translations/is'
import it from './translations/it'
import ja from './translations/ja'
import kk from './translations/kk'
import ko from './translations/ko'
import lb from './translations/lb'
import lt from './translations/lt'
import lv from './translations/lv'
import mk from './translations/mk'
import mn from './translations/mn'
import nbNO from './translations/nb-NO'
import nl from './translations/nl'
import pl from './translations/pl'
import ptBR from './translations/pt-BR'
import ro from './translations/ro'
import ru from './translations/ru'
import sk from './translations/sk'
import slSI from './translations/sl-SI'
import srCYRL from './translations/sr-CYRL'
import sr from './translations/sr'
import sv from './translations/sv'
import th from './translations/th'
import tr from './translations/tr'
import uk from './translations/uk'
import ur from './translations/ur'
import vi from './translations/vi'
import zh from './translations/zh'
import zhHK from './translations/zh-HK'
export {
af,
ar,
bg,
bs,
ca,
cs,
da,
de,
ee,
el,
en,
es,
fa,
fi,
fo,
fr,
ge,
gl,
he,
hr,
hu,
id,
is,
it,
ja,
kk,
ko,
lb,
lt,
lv,
mk,
mn,
nbNO,
nl,
pl,
ptBR,
ro,
ru,
sk,
slSI,
srCYRL,
sr,
sv,
th,
tr,
uk,
ur,
vi,
zh,
zhHK
}
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Afrikaans',
['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Arabic',
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت']
)
language.rtl = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Bulgarian',
['Януари', 'Февруари', 'Март', 'Април', 'Май', 'Юни', 'Юли', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
['Ян', 'Фев', 'Мар', 'Апр', 'Май', 'Юни', 'Юли', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Bosnian',
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Juni', 'Juli', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Catalan',
['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Oct', 'Nov', 'Des'],
['Diu', 'Dil', 'Dmr', 'Dmc', 'Dij', 'Div', 'Dis']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Czech',
['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'],
['led', 'úno', 'bře', 'dub', 'kvě', 'čer', 'čec', 'srp', 'zář', 'říj', 'lis', 'pro'],
['ne', 'po', 'út', 'st', 'čt', 'pá', 'so']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Danish',
['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'German',
['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Estonian',
['Jaanuar', 'Veebruar', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'August', 'September', 'Oktoober', 'November', 'Detsember'],
['Jaan', 'Veebr', 'Märts', 'Apr', 'Mai', 'Juuni', 'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'],
['P', 'E', 'T', 'K', 'N', 'R', 'L']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Greek',
['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάϊος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'],
['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαι', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'English',
['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Spanish',
['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Persian',
['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'],
['فرو', 'ارد', 'خرد', 'تیر', 'مرد', 'شهر', 'مهر', 'آبا', 'آذر', 'دی', 'بهم', 'اسف'],
['یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Finnish',
['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'],
['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Faroese',
['Januar', 'Februar', 'Mars', 'Apríl', 'Mai', 'Juni', 'Juli', 'August', 'Septembur', 'Oktobur', 'Novembur', 'Desembur'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['Sun', 'Mán', 'Týs', 'Mik', 'Hós', 'Frí', 'Ley']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'French',
['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc'],
['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Georgia',
['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'],
['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'],
['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Galician',
['Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro'],
['Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xuñ', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec'],
['Dom', 'Lun', 'Mar', 'Mér', 'Xov', 'Ven', 'Sáb']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Hebrew',
['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'],
['ינו', 'פבר', 'מרץ', 'אפר', 'מאי', 'יונ', 'יול', 'אוג', 'ספט', 'אוק', 'נוב', 'דצמ'],
['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש']
)
language.rtl = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Croatian',
['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac'],
['Sij', 'Velj', 'Ožu', 'Tra', 'Svi', 'Lip', 'Srp', 'Kol', 'Ruj', 'Lis', 'Stu', 'Pro'],
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Hungarian',
['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
['Jan', 'Febr', 'Márc', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec'],
['Vas', 'Hét', 'Ke', 'Sze', 'Csü', 'Pén', 'Szo']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Indonesian',
['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'],
['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Icelandic',
['Janúar', 'Febrúar', 'Mars', 'Apríl', 'Maí', 'Júní', 'Júlí', 'Ágúst', 'September', 'Október', 'Nóvember', 'Desember'],
['Jan', 'Feb', 'Mars', 'Apr', 'Maí', 'Jún', 'Júl', 'Ágú', 'Sep', 'Okt', 'Nóv', 'Des'],
['Sun', 'Mán', 'Þri', 'Mið', 'Fim', 'Fös', 'Lau']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Italian',
['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
)
// eslint-disable-next-line
;
@@ -0,0 +1,15 @@
import Language from '../Language'
const language = new Language(
'Japanese',
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
['日', '月', '火', '水', '木', '金', '土']
)
language.yearSuffix = '年'
language.ymd = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Kazakh',
['Қаңтар', 'Ақпан', 'Наурыз', 'Сәуір', 'Мамыр', 'Маусым', 'Шілде', 'Тамыз', 'Қыркүйек', 'Қазан', 'Қараша', 'Желтоқсан'],
['Қаң', 'Ақп', 'Нау', 'Сәу', 'Мам', 'Мау', 'Шіл', 'Там', 'Қыр', 'Қаз', 'Қар', 'Жел'],
['Жк', 'Дй', 'Сй', 'Ср', 'Бй', 'Жм', 'Сн']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Korean',
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
['일', '월', '화', '수', '목', '금', '토']
)
language.yearSuffix = '년'
language.ymd = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Luxembourgish',
['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Lithuanian',
['Sausis', 'Vasaris', 'Kovas', 'Balandis', 'Gegužė', 'Birželis', 'Liepa', 'Rugpjūtis', 'Rugsėjis', 'Spalis', 'Lapkritis', 'Gruodis'],
['Sau', 'Vas', 'Kov', 'Bal', 'Geg', 'Bir', 'Lie', 'Rugp', 'Rugs', 'Spa', 'Lap', 'Gru'],
['Sek', 'Pir', 'Ant', 'Tre', 'Ket', 'Pen', 'Šeš']
)
language.ymd = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Latvian',
['Janvāris', 'Februāris', 'Marts', 'Aprīlis', 'Maijs', 'Jūnijs', 'Jūlijs', 'Augusts', 'Septembris', 'Oktobris', 'Novembris', 'Decembris'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jūn', 'Jūl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sv', 'Pr', 'Ot', 'Tr', 'Ce', 'Pk', 'Se']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Macedonian',
['Јануари', 'Февруари', 'Март', 'Април', 'Мај', 'Јуни', 'Јули', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
['Јан', 'Фев', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
['Нед', 'Пон', 'Вто', 'Сре', 'Чет', 'Пет', 'Саб']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Mongolia',
['1 дүгээр сар', '2 дугаар сар', '3 дугаар сар', '4 дүгээр сар', '5 дугаар сар', '6 дугаар сар', '7 дугаар сар', '8 дугаар сар', '9 дүгээр сар', '10 дугаар сар', '11 дүгээр сар', '12 дугаар сар'],
['1-р сар', '2-р сар', '3-р сар', '4-р сар', '5-р сар', '6-р сар', '7-р сар', '8-р сар', '9-р сар', '10-р сар', '11-р сар', '12-р сар'],
['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя']
)
language.ymd = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Norwegian Bokmål',
['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Dutch',
['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Polish',
['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Brazilian',
['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Romanian',
['Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'],
['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Noi', 'Dec'],
['D', 'L', 'Ma', 'Mi', 'J', 'V', 'S']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Russian',
['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
['Янв', 'Февр', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сент', 'Окт', 'Нояб', 'Дек'],
['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Slovakian',
['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'],
['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'],
['ne', 'po', 'ut', 'st', 'št', 'pi', 'so']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Sloveian',
['Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', 'Avgust', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Serbian in Cyrillic script',
['Јануар', 'Фебруар', 'Март', 'Април', 'Мај', 'Јун', 'Јул', 'Август', 'Септембар', 'Октобар', 'Новембар', 'Децембар'],
['Јан', 'Феб', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Нов', 'Дец'],
['Нед', 'Пон', 'Уто', 'Сре', 'Чет', 'Пет', 'Суб']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Serbian',
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Swedish',
['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Thai',
['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'],
['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'],
['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Turkish',
['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt']
)
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Ukraine',
['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Чер', 'Лип', 'Серп', 'Вер', 'Жовт', 'Лист', 'Груд'],
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
)
// eslint-disable-next-line
;
@@ -0,0 +1,14 @@
import Language from '../Language'
const language = new Language(
'Urdu',
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'ہفتہ']
)
language.rtl = true
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,10 @@
import Language from '../Language'
export default new Language(
'Vietnamese',
['Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6', 'Tháng 7', 'Tháng 8', 'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12'],
['T 01', 'T 02', 'T 03', 'T 04', 'T 05', 'T 06', 'T 07', 'T 08', 'T 09', 'T 10', 'T 11', 'T 12'],
['CN', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7']
)
// eslint-disable-next-line
;
@@ -0,0 +1,11 @@
import Language from '../Language'
const language = new Language(
'Chinese_HK',
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
['日', '壹', '贰', '叁', '肆', '伍', '陆']
)
language.yearSuffix = '年'
export default language
@@ -0,0 +1,13 @@
import Language from '../Language'
const language = new Language(
'Chinese',
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
['日', '一', '二', '三', '四', '五', '六']
)
language.yearSuffix = '年'
export default language
// eslint-disable-next-line
;
@@ -0,0 +1,252 @@
import en from '../locale/translations/en'
const utils = {
/**
* @type {Boolean}
*/
useUtc: false,
/**
* Returns the full year, using UTC or not
* @param {Date} date
*/
getFullYear (date) {
return this.useUtc ? date.getUTCFullYear() : date.getFullYear()
},
/**
* Returns the month, using UTC or not
* @param {Date} date
*/
getMonth (date) {
return this.useUtc ? date.getUTCMonth() : date.getMonth()
},
/**
* Returns the date, using UTC or not
* @param {Date} date
*/
getDate (date) {
return this.useUtc ? date.getUTCDate() : date.getDate()
},
/**
* Returns the day, using UTC or not
* @param {Date} date
*/
getDay (date) {
return this.useUtc ? date.getUTCDay() : date.getDay()
},
/**
* Returns the hours, using UTC or not
* @param {Date} date
*/
getHours (date) {
return this.useUtc ? date.getUTCHours() : date.getHours()
},
/**
* Returns the minutes, using UTC or not
* @param {Date} date
*/
getMinutes (date) {
return this.useUtc ? date.getUTCMinutes() : date.getMinutes()
},
/**
* Sets the full year, using UTC or not
* @param {Date} date
*/
setFullYear (date, value, useUtc) {
return this.useUtc ? date.setUTCFullYear(value) : date.setFullYear(value)
},
/**
* Sets the month, using UTC or not
* @param {Date} date
*/
setMonth (date, value, useUtc) {
return this.useUtc ? date.setUTCMonth(value) : date.setMonth(value)
},
/**
* Sets the date, using UTC or not
* @param {Date} date
* @param {Number} value
*/
setDate (date, value, useUtc) {
return this.useUtc ? date.setUTCDate(value) : date.setDate(value)
},
/**
* Check if date1 is equivalent to date2, without comparing the time
* @see https://stackoverflow.com/a/6202196/4455925
* @param {Date} date1
* @param {Date} date2
*/
compareDates (date1, date2) {
const d1 = new Date(date1.getTime())
const d2 = new Date(date2.getTime())
if (this.useUtc) {
d1.setUTCHours(0, 0, 0, 0)
d2.setUTCHours(0, 0, 0, 0)
} else {
d1.setHours(0, 0, 0, 0)
d2.setHours(0, 0, 0, 0)
}
return d1.getTime() === d2.getTime()
},
/**
* Validates a date object
* @param {Date} date - an object instantiated with the new Date constructor
* @return {Boolean}
*/
isValidDate (date) {
if (Object.prototype.toString.call(date) !== '[object Date]') {
return false
}
return !isNaN(date.getTime())
},
/**
* Return abbreviated week day name
* @param {Date}
* @param {Array}
* @return {String}
*/
getDayNameAbbr (date, days) {
if (typeof date !== 'object') {
throw TypeError('Invalid Type')
}
return days[this.getDay(date)]
},
/**
* Return name of the month
* @param {Number|Date}
* @param {Array}
* @return {String}
*/
getMonthName (month, months) {
if (!months) {
throw Error('missing 2nd parameter Months array')
}
if (typeof month === 'object') {
return months[this.getMonth(month)]
}
if (typeof month === 'number') {
return months[month]
}
throw TypeError('Invalid type')
},
/**
* Return an abbreviated version of the month
* @param {Number|Date}
* @return {String}
*/
getMonthNameAbbr (month, monthsAbbr) {
if (!monthsAbbr) {
throw Error('missing 2nd paramter Months array')
}
if (typeof month === 'object') {
return monthsAbbr[this.getMonth(month)]
}
if (typeof month === 'number') {
return monthsAbbr[month]
}
throw TypeError('Invalid type')
},
/**
* Alternative get total number of days in month
* @param {Number} year
* @param {Number} m
* @return {Number}
*/
daysInMonth (year, month) {
return /8|3|5|10/.test(month) ? 30 : month === 1 ? (!(year % 4) && year % 100) || !(year % 400) ? 29 : 28 : 31
},
/**
* Get nth suffix for date
* @param {Number} day
* @return {String}
*/
getNthSuffix (day) {
switch (day) {
case 1:
case 21:
case 31:
return 'st'
case 2:
case 22:
return 'nd'
case 3:
case 23:
return 'rd'
default:
return 'th'
}
},
/**
* Formats date object
* @param {Date}
* @param {String}
* @param {Object}
* @return {String}
*/
formatDate (date, format, translation) {
translation = (!translation) ? en : translation
let year = this.getFullYear(date)
let month = this.getMonth(date) + 1
let day = this.getDate(date)
let str = format
.replace(/dd/, ('0' + day).slice(-2))
.replace(/d/, day)
.replace(/yyyy/, year)
.replace(/yy/, String(year).slice(2))
.replace(/MMMM/, this.getMonthName(this.getMonth(date), translation.months))
.replace(/MMM/, this.getMonthNameAbbr(this.getMonth(date), translation.monthsAbbr))
.replace(/MM/, ('0' + month).slice(-2))
.replace(/M(?!a|ä|e)/, month)
.replace(/su/, this.getNthSuffix(this.getDate(date)))
.replace(/D(?!e|é|i)/, this.getDayNameAbbr(date, translation.days))
return str
},
/**
* Creates an array of dates for each day in between two dates.
* @param {Date} start
* @param {Date} end
* @return {Array}
*/
createDateArray (start, end) {
let dates = []
while (start <= end) {
dates.push(new Date(start))
start = this.setDate(new Date(start), this.getDate(new Date(start)) + 1)
}
return dates
},
/**
* method used as a prop validator for input values
* @param {*} val
* @return {Boolean}
*/
validateDateInput (val) {
return val === null || val instanceof Date || typeof val === 'string' || typeof val === 'number'
}
}
export const makeDateUtils = useUtc => ({...utils, useUtc})
export default {
...utils
}
// eslint-disable-next-line
;
@@ -0,0 +1,127 @@
<!--
Program: Editor
Author: SDL
Brief: Editor using Cafenote
-->
<template>
<div :id="editorId" :ref="editorId"></div>
</template>
<script>
import { mapGetters } from 'vuex';
// import $ from 'jquery';
import SDLUtil from '@/utils/SDLUtil';
export default {
inheritAttrs: true,
emits: ['update:modelValue'],
props: {
modelValue: {
required: true,
type: String,
default: '',
},
format: {
required: false,
type: String,
default: 'html',
},
},
data() {
return {
cafe: {},
editorId: 'editor',
config: {
language: this.getLocale(this.$i18n.locale),
height: 250,
plugins: 'autolink dragdrop expaste emoticon findreplace image lineheight link lists listsstyle lock media mhtmlloader pagebreak paragraphformat superscript subscript table typo2kr',
default_font_size: '11',
default_font_family: "SamsungOneKorean, '맑은 고딕', Gulim, Arial",
default_line_height: '1.5',
font_formats: ['SamsungOneKorean', '굴림', '굴림체', '궁서', '궁서체', '돋움', '돋움체', '맑은 고딕', '바탕', '바탕체', 'Arial', 'Courier', 'sans-serif', 'Verdana'],
font_size_style_values: ['8pt', '9pt', '10pt', '11pt', '12pt', '14pt', '16pt', '20pt', '36pt', '48pt'],
line_heights: ['0.5', '1', '1.2', '1.5', '1.9', '2', '3', '4', '5'],
invalid_elements: 'script',
codeview_indent: true,
},
};
},
computed: {
...mapGetters({
locale: 'getLocale',
}),
},
methods: {
setContent() {
if (this.modelValue && this.modelValue !== this.getContent()) {
this.cafe.setContent(this.editorId, this.modelValue, { format: this.format });
}
},
getContent() {
if (this.format === 'html') {
const html = this.cafe.getContent(this.editorId, { format: this.format });
// return $($.parseHTML(html)[5]).html();
}
return this.cafe.getContent(this.editorId, { format: this.format });
},
loadComplete() {
this.setContent();
// rich textarea
// $('iframe')
// .contents()
// .find('#cafe-note-contents')
// .bind('blur', () => {
// this.$emit('update:modelValue', this.getContent());
// });
// html textarea
// $(`#${this.editorId}`)
// .next()
// .find('textarea')
// .bind('blur', () => {
// this.$emit('update:modelValue', this.getContent());
// });
SDLUtil.showLoadingBar(false);
},
getLocale(lang) {
return lang === 'ko_KR' ? lang : 'en_US';
},
},
watch: {
modelValue() {
if (this.cafe.setContent) {
this.setContent();
}
},
locale(lang) {
const content = this.getContent();
this.cafe.destroy(this.editorId);
this.config.language = this.getLocale(lang);
this.cafe.create(this.editorId, this.config, () => {
this.loadComplete();
this.$emit('update:modelValue', content);
});
},
},
created() {
this.editorId = `editor_${new Date().getTime()}${Math.floor(Math.random() * 100) + 1}`;
},
mounted() {
this.$nextTick(() => {
SDLUtil.showLoadingBar(true);
SDLUtil.importJS(`${this.$CONTEXT}/static/libs/cafe/2.3.25/cafe_2.3.25.min.js`)
.then(window => {
this.cafe = window.cafe;
this.cafe.create(this.editorId, this.config, this.loadComplete);
})
.catch(err => {
console.error(err);
});
});
},
beforeUnmount() {
this.cafe.destroy(this.editorId);
},
};
</script>
<style scoped></style>
@@ -0,0 +1,3 @@
import Editor from './Editor.vue';
export default Editor;
@@ -0,0 +1,129 @@
<!--
Program: Excel Download
Author: SDL
Description: Excel Download Common Component
-->
<template>
<button type="button" :class="btnClass" @click="excelDownload()">{{ $t(btnLabel) }}</button>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
props: {
btnLabel: {
required: false,
type: String,
default: 'sdl.common.label.excelDownload',
},
btnClass: {
type: String,
default: 'btn btn-secondary',
},
excelInfo: {
required: false,
type: Object,
},
beforeClick: {
type: Function,
default: () => true,
},
},
data() {
return {};
},
methods: {
/**
* param:
* return:
* exception:
* Description : click excel down
*/
excelDownload() {
if (!this.beforeClick()) return;
SDLUtil.confirm({
msg: 'sdl.common.message.doExcelDownload',
// title: 'confirm title',
// okLabel: 'ok label',
// cancelLabel: 'Cancel',
onOkEvt: () => this.download(),
});
},
/**
* param:
* return:
* exception:
* Description : execute download
*/
download() {
const getFileName = contentDisposition => {
const fileName = contentDisposition
.split(';')
.filter(ele => ele.indexOf('filename') > -1)
.map(ele => ele.replace(/"/g, '').split('=')[1]);
return fileName[0] ? fileName[0] : null;
};
SDLUtil.showLoadingBar(true);
const param = [];
const encodeList = ['columnInfoFile', 'fileName', 'sheetName', 'param'];
Object.keys(this.excelInfo).forEach(val => {
if (encodeList.includes(val)) {
if (val === 'param') {
param.push(`${val}=${encodeURIComponent(JSON.stringify(this.excelInfo[val]))}`);
} else {
param.push(`${val}=${encodeURIComponent(this.excelInfo[val])}`);
}
} else {
param.push(`${val}=${this.excelInfo[val]}`);
}
});
axios({
url: `${SDLUtil.API_URL}/excel/excel-download?${param.join('&')}`,
responseType: 'arraybuffer',
method: 'get',
})
.then(response => {
try {
const blob = new Blob([response.data], { type: response.headers['content-type'] });
let fileName = '';
if (response.headers['content-disposition']) {
fileName = getFileName(response.headers['content-disposition']);
} else {
// eslint-disable-next-line prefer-destructuring
fileName = this.excelInfo.fileName;
}
fileName = decodeURI(fileName); // File name decoding (option of use according to project)
if (window.navigator.msSaveOrOpenBlob) {
// IE 10+
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
// not IE
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.target = '_self';
if (fileName) link.download = fileName;
link.click();
}
SDLUtil.showLoadingBar(false);
// return response.data;
} catch (e) {
SDLUtil.showLoadingBar(false);
console.error(e);
}
})
.catch(response => {
SDLUtil.showLoadingBar(false);
SDLUtil.alert(response.data);
});
},
},
};
</script>
<style></style>
@@ -0,0 +1,119 @@
<!--
Program: Excel Upload
Author: SDL
Description: Excel Upload Common Component
-->
<template>
<div class="input-group">ExcelUpload.vue
<input type="file" class="form-control" @change="onChange" />
<button type="button" :class="btnClass" @click="onBeforeUpload">{{ $t(btnLabel) }}</button>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
props: {
btnLabel: {
required: false,
type: String,
default: 'sdl.common.label.excelUpload',
},
btnClass: {
type: String,
default: 'btn btn-secondary',
},
excelInfo: {
required: false,
type: Object,
},
beforeClick: {
type: Function,
default: () => true,
},
},
data() {
return {
files: [],
};
},
methods: {
/**
* param: event
* return:
* exception:
* Description : Select attachment
*/
onChange(e) {
this.files = e.target.files || e.dataTransfer.files;
const getFileExt = fileName => fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length).toLowerCase();
const extList = 'xls|xlsx'.split('|');
Object.keys(this.files).forEach(x => {
if (!extList.includes(getFileExt(this.files[x].name))) {
SDLUtil.alert(
SDLUtil.getMsgProp('sdl.common.message.extensionCannotBeAttached', [`${getFileExt(this.files[x].name)}`]),
);
this.files = [];
e.target.value = '';
}
});
},
/**
* param:
* return:
* exception:
* Description : click upload button
*/
onBeforeUpload() {
if (this.files.length === 0) {
SDLUtil.alert('sdl.common.message.selectFile');
return;
}
if (!this.beforeClick()) return;
SDLUtil.confirm({
msg: 'sdl.common.message.wouldYouLikeUploadExcel',
// title: 'confirm title',
// okLabel: 'ok label',
// cancelLabel: 'Cancel',
onOkEvt: () => this.upload(),
});
},
/**
* param:
* return:
* exception:
* Description : upload attachment
*/
upload() {
this.excelInfo.columnInfoFile = encodeURIComponent(this.excelInfo.columnInfoFile);
const formData = new FormData();
formData.append('excelFile', this.files[0]);
Object.keys(this.excelInfo).forEach(val => {
formData.append(val, this.excelInfo[val]);
});
SDLUtil.showLoadingBar(true);
axios
.post(`${SDLUtil.API_URL}/excel/excel-upload`, formData)
.then(response => {
// Loading bar hidden
SDLUtil.showLoadingBar(false);
// if (response.data !== '') {
// this.$router.push(response.data);
// }
this.$emit('after-upload', response.data);
})
.catch(error => {
SDLUtil.showLoadingBar(false);
console.log(error);
});
},
},
};
</script>
<style></style>
@@ -0,0 +1,4 @@
import SdlExceldownload from './ExcelDownload.vue';
import SdlExcelupload from './ExcelUpload.vue';
export { SdlExceldownload, SdlExcelupload };
@@ -0,0 +1,806 @@
<!--
Program: Multi File Uploader
Author: SDL
Description: Multi File Common Component
-->
<template>
<div class="ui--file-attachment" :class="{ active: modifyFlag && !itemsAdded, drag: modifyFlag && itemsAdded && dragging }">
<p class="message"><i class="bi bi-cloud-upload"></i><span v-html="initMsg"></span></p>
<input
type="file"
:id="`fileAttach${downloadType}`"
name="items[]"
multiple
@change="onChange"
class="addfile"
@dragover="ignoreDrag"
@dragenter="ignoreDrag"
@drop="onChange"
@dragend="dragging = false"
@dragleave="dragging = false"
:style="(modifyFlag && itemsAdded && dragging) || (modifyFlag && !itemsAdded) ? 'z-index:1001;position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%;' : 'width:0%;height:0%;z-index:-1;'"
/>
<div class="ui--file-list" :class="{ loaderArea: isLoaderVisible }" @dragenter="dragging = true">
<!-- file-button -->
<div class="ui--file-button" v-if="modifyFlag">
<input type="checkbox" @click.stop v-model="selectAll" @change="toggleSelectAll" />
<button type="button" class="btn ui--btn-icon-sm" @click="moveUp">
<span class="ui--blind">{{ $t('sdl.common.label.moveup') }}</span
><i class="bi bi-caret-up-fill"></i>
</button>
<button type="button" class="btn ui--btn-icon-sm" @click="moveDown">
<span class="ui--blind">{{ $t('sdl.common.label.movedown') }}</span
><i class="bi bi-caret-down-fill"></i>
</button>
<button type="button" class="btn ui--btn-icon-sm" @click="selectRemoveItem">
<span class="ui--blind">{{ $t('sdl.common.label.delete') }}</span
><i class="bi bi-x"></i>
</button>
<span class="ms-2 me-2 ui--slide">|</span>
<span
>{{ $t('sdl.common.label.total') }}<strong class="ms-1 me-2">{{ itemsAdded }}</strong
>{{ itemsTotalSize }} / {{ getItemsMaxTotalSize }}</span
>
<label class="btn btn-outline-secondary ms-3 ui--btn-xs" style="margin-bottom: 0px" :for="`fileAttach${downloadType}`">
{{ $t('sdl.common.label.fileAddition') }}
</label>
</div>
<div class="ui--file-button" v-else>
<input type="checkbox" @click.stop v-model="selectAll" @change="toggleSelectAll" />
<span
>{{ $t('sdl.common.label.total') }}<strong class="ms-1 me-2">{{ itemsAdded }}</strong
>({{ itemsTotalSize }})</span
>
<!-- If you want to download compressed into a zip file -->
<!-- <button type="button" @click="checkedDownload" class="btn btn-outline-secondary ms-3 ui&#45;&#45;btn-xs">-->
<button type="button" @click="checkedDownloadNonZip" class="btn btn-outline-secondary ms-3 ui--btn-xs">
{{ $t('sdl.common.label.alldownload') }}
</button>
</div>
<!-- // file-button -->
<!-- file-list -->
<ul>
<li v-for="item in itemsNames" :key="item.fileName">
<div class="form-check">
<input type="checkbox" class="form-check-input" v-model="item.selected" @change="selectRow(item)" />
<label class="form-check-label" @click="download(item)" :style="!modifyFlag ? 'cursor:pointer;' : ''">
{{ item.fileName }}
<small>
<small>({{ bytesToSize(item.fileSize) }})</small>
</small>
</label>
</div>
<button type="button" class="btn ui--btn-icon-sm" v-show="modifyFlag" @click="removeItem(item)">
<span class="ui--blind">{{ $t('sdl.common.label.delete') }}</span
><i class="bi bi-x"></i>
</button>
</li>
</ul>
<div class="loader" :class="{ loaderArea: isLoaderVisible }" v-show="false">
<div class="loaderImg"></div>
</div>
<!-- // file-list -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
props: {
modifyFlag: {
type: Boolean,
default: true,
},
uploadURL: {
type: String,
required: false,
default: `${SDLUtil.API_URL}/resource/attachments/multifile-upload`,
},
multiFileNm: {
type: String,
default: '',
},
downloadType: {
type: String,
required: true,
},
fileList: {
type: Array,
},
useExtList: {
type: String,
default: 'zip|xlsx|xls|ppt|pptx',
},
maxItems: {
type: Number,
default: 10,
},
method: {
type: String,
default: 'post',
},
// postMeta: {
// type: [String, Array, Object],
// default: '',
// },
// postData: {
// type: [Object],
// default: () => {},
// },
postHeader: {
type: [Object],
default: () => {},
},
httpMethodErrorMessage: {
type: String,
default: "This HTTP method is not allowed. Please use either 'put' or 'post' methods.",
},
maxTotalSize: {
type: Number,
default: 1073741824,
},
},
/*
* The component's data.
*/
data() {
return {
dragging: false,
items: [],
itemsAdded: '',
itemsNames: [],
itemsTotalSize: 0,
itemsMaxTotalSize: 0,
formData: '',
successMsg: '',
errorMsg: '',
isLoaderVisible: false,
fileSizes: 0,
selectAll: false,
docId: '',
};
},
computed: {
/**
* param:
* return:
* exception:
* Description : get maximum attachment capacity
*/
getItemsMaxTotalSize() {
return this.bytesToSize(this.maxTotalSize);
},
initMsg() {
return `${this.$t('sdl.common.message.uploadTheAttachedFileHere')}(${this.getItemsMaxTotalSize})`;
},
},
created() {
this.init();
},
methods: {
// http://scratch99.com/web-development/javascript/convert-bytes-to-mb-kb/
/**
* param: bytes -> String
* return: String
* exception:
* Description : convert bytes to size
*/
bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0.00KB';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
if (i === 0) return `${bytes} ${sizes[i]}`;
// eslint-disable-next-line no-restricted-properties
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
},
/**
* param:
* return:
* exception:
* Description : select attch file
*/
selectRow(item) {
if (!item.selected) {
if (this.selectAll) {
this.selectAll = false;
}
}
},
/**
* param:
* return:
* exception:
* Description : If you select all
*/
toggleSelectAll() {
if (this.selectAll) {
for (let i = 0; i < this.itemsNames.length; i += 1) {
const selectItem = this.itemsNames[i];
selectItem.selected = true;
this.itemsNames.splice(i, 1, selectItem);
}
} else {
for (let i = 0; i < this.itemsNames.length; i += 1) {
const selectItem = this.itemsNames[i];
selectItem.selected = false;
this.itemsNames.splice(i, 1, selectItem);
}
}
},
/**
* param:
* return:
* exception:
* Description : move up
*/
moveUp() {
for (let i = 0; i < this.itemsNames.length; i += 1) {
if (this.itemsNames[i].selected) {
if (i === 0) {
return;
}
const tmpItem = this.itemsNames[i];
this.itemsNames.splice(i, 1, this.itemsNames[i - 1]);
this.itemsNames.splice(i - 1, 1, tmpItem);
}
}
},
/**
* param:
* return:
* exception:
* Description : move down
*/
moveDown() {
for (let i = this.itemsNames.length - 1; i >= 0; i -= 1) {
if (this.itemsNames[i].selected) {
if (i === this.itemsNames.length - 1) {
return;
}
const tmpItem = this.itemsNames[i];
this.itemsNames.splice(i, 1, this.itemsNames[i + 1]);
this.itemsNames.splice(i + 1, 1, tmpItem);
}
}
},
/**
* param:
* return:
* exception:
* Description : Delete selected files
*/
selectRemoveItem() {
let flag = true;
const selectedItemList = [];
this.itemsNames.forEach(item => {
if (item.selected) selectedItemList.push(item);
});
selectedItemList.forEach(selectedItem => {
this.removeItem(selectedItem);
flag = false;
});
if (flag) {
SDLUtil.alert('sdl.common.message.selectRow');
}
},
/**
* param:
* return:
* exception:
* Description : Download selected files
*/
checkedDownload() {
let flag = true;
const items = [];
let fileName = '';
for (let i = 0; i < this.itemsNames.length; i += 1) {
if (this.itemsNames[i].selected) {
if (fileName === '') {
fileName = this.itemsNames[i].fileName.substring(fileName.lastIndexOf('.'));
}
items.push(this.itemsNames[i].fileId);
flag = false;
}
}
if (flag) {
SDLUtil.alert('sdl.common.message.selectTheFileToDownload');
} else if (items.length > 1) {
this.download({ fileIds: items, fileName });
} else {
this.download({ fileId: items[0], fileName });
}
},
async checkedDownloadNonZip() {
let flag = true;
const items = [];
let fileName = '';
for (let i = 0; i < this.itemsNames.length; i += 1) {
if (this.itemsNames[i].selected) {
if (fileName === '') {
fileName = this.itemsNames[i].fileName.substring(fileName.lastIndexOf('.'));
}
items.push(this.itemsNames[i].fileId);
flag = false;
}
}
if (flag) {
SDLUtil.alert('sdl.common.message.selectTheFileToDownload');
} else if (items.length > 1) {
for (let id of items) {
await this.asyncDownload({ fileId: id });
}
} else {
await this.asyncDownload({ fileId: items[0], fileName });
}
},
/**
* param:
* return:
* exception:
* Description : download files
*/
download(params) {
if (this.modifyFlag) return;
let path = '';
if (typeof params.fileIds !== 'undefined') {
if (this.multiFileNm !== '') {
params.fileName = this.multiFileNm;
}
if (this.docId) {
// case approval
path = `${SDLUtil.API_URL}/approval/approval-documents/${this.docId}/attachments?fileIds=${params.fileIds}&zipFileName=${params.fileName}`;
} else {
path = `${SDLUtil.API_URL}/resource/attachments/multifile-download?fileIds=${params.fileIds}&downloadType=${this.downloadType}&zipFileName=${params.fileName}`;
}
} else if (this.docId) {
// case approval
path = `${SDLUtil.API_URL}/approval/approval-documents/${this.docId}/attachments/${params.fileId}`;
} else {
path = `${SDLUtil.API_URL}/resource/attachments/file-download/${params.fileId}?downloadType=${this.downloadType}`;
}
const getFileName = contentDisposition => {
const fileName = contentDisposition
.split(';')
.filter(ele => ele.indexOf('filename') > -1)
.map(ele => ele.replace(/"/g, '').split('=')[1]);
return fileName[0] ? fileName[0] : null;
};
SDLUtil.showLoadingBar(true);
axios({
url: path,
responseType: 'arraybuffer',
method: 'get',
})
.then(response => {
try {
if (response.headers['content-type'] === 'application/json') {
response.data = JSON.parse(String.fromCharCode.apply(null, Array.from(new Uint8Array(response.data))));
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(response);
return;
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
let fileName = '';
if (response.headers['content-disposition']) {
fileName = getFileName(response.headers['content-disposition']);
} else {
// eslint-disable-next-line prefer-destructuring
fileName = params.fileName;
}
fileName = decodeURI(fileName);
if (window.navigator.msSaveOrOpenBlob) {
// IE 10+
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
// not IE
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.target = '_self';
if (fileName) link.download = fileName;
link.click();
}
SDLUtil.showLoadingBar(false);
// return response.data;
} catch (e) {
SDLUtil.showLoadingBar(false);
console.error(e);
}
})
.catch(response => {
SDLUtil.showLoadingBar(false);
SDLUtil.alert(response.data);
});
},
async asyncDownload(params) {
if (this.modifyFlag) return;
let path = '';
if (typeof params.fileIds !== 'undefined') {
if (this.multiFileNm !== '') {
params.fileName = this.multiFileNm;
}
if (this.docId) {
// case approval
path = `${SDLUtil.API_URL}/approval/approval-documents/${this.docId}/attachments?fileIds=${params.fileIds}&zipFileName=${params.fileName}`;
} else {
path = `${SDLUtil.API_URL}/resource/attachments/multifile-download?fileIds=${params.fileIds}&downloadType=${this.downloadType}&zipFileName=${params.fileName}`;
}
} else if (this.docId) {
// case approval
path = `${SDLUtil.API_URL}/approval/approval-documents/${this.docId}/attachments/${params.fileId}`;
} else {
path = `${SDLUtil.API_URL}/resource/attachments/file-download/${params.fileId}?downloadType=${this.downloadType}`;
}
const getFileName = contentDisposition => {
const fileName = contentDisposition
.split(';')
.filter(ele => ele.indexOf('filename') > -1)
.map(ele => ele.replace(/"/g, '').split('=')[1]);
return fileName[0] ? fileName[0] : null;
};
SDLUtil.showLoadingBar(true);
await axios({
url: path,
responseType: 'arraybuffer',
method: 'get',
})
.then(response => {
try {
if (response.headers['content-type'] === 'application/json') {
response.data = JSON.parse(String.fromCharCode.apply(null, Array.from(new Uint8Array(response.data))));
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(response);
return;
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
let fileName = '';
if (response.headers['content-disposition']) {
fileName = getFileName(response.headers['content-disposition']);
} else {
// eslint-disable-next-line prefer-destructuring
fileName = params.fileName;
}
fileName = decodeURI(fileName);
if (window.navigator.msSaveOrOpenBlob) {
// IE 10+
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
// not IE
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.target = '_self';
if (fileName) link.download = fileName;
link.click();
}
SDLUtil.showLoadingBar(false);
// return response.data;
} catch (e) {
SDLUtil.showLoadingBar(false);
console.error(e);
}
})
.catch(response => {
SDLUtil.showLoadingBar(false);
SDLUtil.alert(response.data);
});
},
/**
* param:
* return:
* exception:
* Description : ignore drag event
*/
ignoreDrag(e) {
this.dragging = true;
e.stopPropagation();
e.preventDefault();
},
/**
* param:
* return:
* exception:
* Description : if you select a file
*/
onChange(e) {
e.stopPropagation();
e.preventDefault();
// e.dataTransfer.dropEffect = 'copy';
this.successMsg = '';
this.errorMsg = '';
// e.dataTransfer.effectAllowed = 'copy';
let files = e.target.files;
if (files.length === 0) {
files = e.dataTransfer.files || [];
}
const agent = window.navigator.userAgent.toLowerCase();
if ((window.navigator.appName === 'Netscape' && agent.indexOf('trident') !== -1) || agent.indexOf('msie') !== -1) {
try {
if (files.length === 0) {
files = e.dataTransfer.files || [];
}
} catch (err) {
return;
}
}
this.dragging = false;
let tmpTotalFileSize = this.fileSizes;
let tmpTotalFileCnt = this.itemsAdded;
const getFileExt = fileName => fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length).toLowerCase();
const extList = this.useExtList.split('|');
let dupChk = false;
let extChkTxt = '';
Object.keys(files).forEach(x => {
tmpTotalFileSize += files[x].size;
tmpTotalFileCnt += 1;
if (this.itemsNames.filter(item => item.fileName === files[x].name).length > 0) {
dupChk = true;
return;
}
if (!extList.includes(getFileExt(files[x].name))) {
extChkTxt = getFileExt(files[x].name);
}
});
if (dupChk) {
document.querySelector(`#fileAttach${this.downloadType}`).value = '';
SDLUtil.alert('sdl.common.message.duplicated');
return;
}
if (extChkTxt) {
document.querySelector(`#fileAttach${this.downloadType}`).value = '';
SDLUtil.alert('sdl.common.message.extensionCannotBeAttached', [`${extChkTxt}`]);
return;
}
if (this.maxTotalSize < tmpTotalFileSize) {
document.querySelector(`#fileAttach${this.downloadType}`).value = '';
SDLUtil.alert('sdl.common.message.attachedFileExceededMaxCapacity');
return;
}
if (this.maxItems < tmpTotalFileCnt) {
document.querySelector(`#fileAttach${this.downloadType}`).value = '';
SDLUtil.alert('sdl.common.message.attachedFileExceededMaxNumber');
return;
}
Object.keys(files).forEach(x => {
this.items = files[x] || files[x];
const item = {
fileName: files[x].name,
fileSize: files[x].size,
file: this.items,
// selected: false,
};
this.itemsNames.push(item);
});
document.querySelector(`#fileAttach${this.downloadType}`).value = '';
this.calViewVariable();
},
/**
* param: docId -> String
* return:
* exception:
* Description : component initialization
*/
init(docId = '') {
this.docId = docId;
this.items = '';
this.itemsAdded = '';
this.itemsNames = [];
this.itemsTotalSize = 0;
this.dragging = false;
if (typeof this.fileList !== 'undefined') {
this.fileList.forEach(file => {
this.itemsNames.push(file);
});
this.calViewVariable();
}
},
/**
* param:
* return:
* exception:
* Description : capacity calculation
*/
calViewVariable() {
this.fileSizes = 0;
for (let i = 0; i < this.itemsNames.length; i += 1) {
this.fileSizes += this.itemsNames[i].fileSize;
}
this.itemsAdded = this.itemsNames.length;
this.itemsTotalSize = this.bytesToSize(this.fileSizes);
},
/**
* param:
* return:
* exception:
* Description : delete selected file
*/
removeItem(item) {
this.itemsNames.splice(this.itemsNames.indexOf(item), 1);
this.calViewVariable();
},
/**
* param:
* return:
* exception:
* Description : upload selected files
*/
onUpload(docId) {
let { uploadURL } = this;
if (docId) {
// In case of approval, receive the approval ID and upload it.
uploadURL = `${SDLUtil.API_URL}/approval/approval-documents/${docId}/attachments`;
}
this.isLoaderVisible = true;
this.formData = new FormData();
this.formData.append('downloadType', this.downloadType);
let addFileCnt = 0;
for (let i = 0; i < this.itemsNames.length; i += 1) {
if (typeof this.itemsNames[i].file !== 'undefined') {
this.formData.append('files', this.itemsNames[i].file);
addFileCnt += 1;
// this.itemsNames.splice(i);
}
}
if (addFileCnt > 0) {
if (this.method === 'put' || this.method === 'post') {
axios({
method: this.method,
url: uploadURL,
data: this.formData,
})
.then(response => {
this.isLoaderVisible = false;
// Show success message
const fileList = response.data;
for (let i = 0; i < fileList.length; i += 1) {
const obj = this.itemsNames.filter(item => item.fileName === fileList[i].fileName)[0];
this.itemsNames.splice(this.itemsNames.indexOf(obj), 1, Object.assign(obj, fileList[i]));
}
this.isLoaderVisible = false;
this.reIndexOrderIdx();
this.$emit('complete', this.downloadType, this.itemsNames);
})
.catch(error => {
this.isLoaderVisible = false;
if (error.isAxiosError) SDLUtil.alert('sdl.common.message.attachedFileChanged');
else SDLUtil.errorAlert(error);
SDLUtil.showLoadingBar(false);
});
} else {
if (this.showHttpMessages) this.errorMsg = this.httpMethodErrorMessage;
this.isLoaderVisible = false;
SDLUtil.alert({ msg: this.httpMethodErrorMessage });
}
} else {
this.isLoaderVisible = false;
this.reIndexOrderIdx();
this.$emit('complete', this.downloadType, this.itemsNames);
}
},
/**
* param:
* return:
* exception:
* Description : reindex upload file ordering
*/
reIndexOrderIdx() {
for (let i = 0; i < this.itemsNames.length; i += 1) {
this.itemsNames[i].orderIdx = i;
this.itemsNames.splice(i, 1, this.itemsNames[i]);
}
},
/**
* param:
* return:
* exception:
* Description : watch drop event(IE)
*/
watchDrop() {
// console.log('watchDrop');
const agent = window.navigator.userAgent.toLowerCase();
if ((window.navigator.appName === 'Netscape' && agent.indexOf('trident') !== -1) || agent.indexOf('msie') !== -1) {
let el = `#fileAttach${this.downloadType}`;
el = document.querySelector(el) || this.$root.$el.querySelector(el);
this.dropElement = el;
if (this.dropElement) {
document.addEventListener('dragenter', this.ignoreDrag, false);
document.addEventListener('dragover', this.ignoreDrag, false);
document.addEventListener('drop', this.ignoreDrag, false);
this.dropElement.addEventListener('dragover', this.ignoreDrag, false);
this.dropElement.addEventListener('drop', this.onChange, false);
}
}
},
},
watch: {
dragging: {
handler() {
this.watchDrop();
},
deep: true,
},
fileList: {
handler() {
this.init(this.docId);
},
deep: true,
},
},
};
</script>
<style>
.addfile {
position: absolute;
cursor: pointer;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
/* Loader */
.loaderArea {
min-height: 150px;
}
.ui--file-list .loader {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
opacity: 0.9;
}
.ui--file-list .loaderImg {
border: 9px solid #eee;
border-top: 9px solid #00adce;
border-radius: 50%;
width: 70px;
height: 70px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* End Loader */
</style>
@@ -0,0 +1,43 @@
/**
* Program: SDL Common Component
* Author: SDL
* Description: SDL Common Component
*/
import SdlBreadcrumb from './breadcrumb';
import SdlLoadingbar from './loadingbar/LoadingBar.vue';
import SdlModal from './modal';
import SdlFileuploader from './fileuploader/MultipleFileUploader.vue';
import SdlDatePicker from './datepicker';
import SdlRegexInput from './input/RegexInput.vue';
import sdlPagination from './pagination/Pagination.vue';
import { SdlExceldownload, SdlExcelupload } from './excel';
import SdlTree from './tree';
import SdlApprovalpath from './approvalpath/ApprovalPath.vue';
import SdlScrollToTop from './scrollToTop/ScrollToTop.vue';
const SdlControl = {
// eslint-disable-next-line no-unused-vars
install(Vue, options) {
Vue.use(SdlModal, {
dialog: true,
dynamic: true,
dynamicDefaults: { clickToClose: false },
injectModalsContainer: true,
});
Vue.component('sdl-breadcrumb', SdlBreadcrumb);
Vue.component('sdl-loadingbar', SdlLoadingbar);
Vue.component('sdl-fileuploader', SdlFileuploader);
Vue.component('sdl-datepicker', SdlDatePicker);
Vue.component('sdl-pagination', sdlPagination);
Vue.component('sdl-exceldownloadbtn', SdlExceldownload);
Vue.component('sdl-exceluploadbtn', SdlExcelupload);
Vue.component('sdl-tree', SdlTree);
Vue.component('sdl-approvalpath', SdlApprovalpath);
Vue.component('sdl-regex-input', SdlRegexInput);
// Exclude Editor from SDL common component related to license
// Vue.component('sdl-editor', SdlEditor);
Vue.component('sdl-scroll-to-top', SdlScrollToTop);
},
};
export default SdlControl;
@@ -0,0 +1,70 @@
<!--
Program: RegexInput
Author: SDL
Brief: Input using regex
-->
<template>
<input v-bind="$attrs" @input="updateValue($event.target.value)" @change="emitChange" v-model="val" ref="input" />
</template>
<script>
import _ from 'lodash';
export default {
name: 'sdl-regex-input',
props: {
value: {
required: false,
type: [Number, String],
},
modelValue: {
required: false,
type: [Number, String],
},
regExp: {
type: RegExp,
default: null,
},
},
data() {
return {
val: '',
oldVal: '',
};
},
methods: {
isValid(val) {
return _.isEmpty(val) || val.toString().match(this.regExp);
},
// update the value of input
updateValue(val) {
const newValue = this.isValid(val) ? val : this.oldVal;
this.val = newValue;
this.oldVal = newValue;
this.emitInput(newValue);
},
// emit input event
emitInput(val) {
this.$emit('input', val);
this.$emit('update:modelValue', val);
},
// emit change event
emitChange() {
this.$emit('change', this.val);
this.$emit('update:modelValue', this.val);
},
},
watch: {
// watch value prop
value(val) {
this.updateValue(val);
},
modelValue(val) {
this.updateValue(val);
},
},
mounted() {
this.updateValue(this.modelValue);
},
};
</script>
@@ -0,0 +1,54 @@
<!--
Program: LoadingBar
Author: SDL
Description: Loadingbar Common Component
-->
<template>
<modal name="loadingbar" :scrollable="false" :click-to-close="false" classes="modal-sm" :width="270" :height="400" @before-open="beforeOpened">
<div class="flex-loading">
<div class="spinner-border spinner-border-lg" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div v-if="params.msg" class="loadingBarMessage">
<p style="text-align: center; font-weight: bold" v-html="$t(params.msg)"></p>
</div>
</div>
</modal>
</template>
<script>
export default {
name: 'loadingbar',
data() {
return {
params: {},
};
},
methods: {
beforeOpened(event) {
this.params = event.params || {};
},
},
};
</script>
<style scoped>
.flex-loading {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.loadingBarMessage {
margin-top: 10px;
padding-top: 17px;
padding-bottom: 1px;
background-color: white;
border-radius: 20px;
opacity: 0.7;
}
.spinner-border.spinner-border-lg {
margin-left: 100px;
}
</style>
@@ -0,0 +1,210 @@
<!--
Program: Modal Dialog
Author: SDL
Description: Dialog Common Component
-->
<template>
<modal
name="dialog"
height="auto"
:classes="['v--modal', 'vue-dialog', this.params.class]"
:width="width"
:pivot-y="0.3"
:adaptive="true"
:clickToClose="clickToClose"
:transition="transition"
@before-open="beforeOpened"
@before-close="beforeClosed"
@opened="$emit('opened', $event)"
@closed="$emit('closed', $event)"
>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title h4" v-if="params.title" v-html="params.title || ''"></h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="closeClick($event)"> </button>
</div>
<component v-if="params.component" v-bind="params.props" :is="params.component"> </component>
<div class="modal-body" style="word-break: break-all" v-else v-html="params.text || ''"></div>
<div class="modal-footer" v-if="buttons">
<button v-for="(button, i) in buttons" :class="button.default ? 'btn btn-secondary' : 'btn btn-primary'" type="button" :key="i" v-html="button.title" @click.stop="click(i, $event)"></button>
</div>
<div v-else class="vue-dialog-buttons-none"></div>
</div>
</modal>
</template>
<script>
export default {
name: 'sdl-dialog',
props: {
width: {
type: [Number, String],
default: 400,
},
clickToClose: {
type: Boolean,
default: true,
},
transition: {
type: String,
default: 'fade',
},
},
data() {
return {
params: {},
defaultButtons: [{ title: 'CLOSE' }],
};
},
computed: {
buttons() {
return this.params.buttons || this.defaultButtons;
},
/**
* Returns FLEX style with correct width for arbitrary number of
* buttons.
*/
buttonStyle() {
return {
flex: `1 1 ${100 / this.buttons.length}%`,
};
},
},
methods: {
/**
* param: event
* return:
* exception:
* Description : Run Event before Modal opening
*/
beforeOpened(event) {
window.addEventListener('keyup', this.onKeyUp);
this.params = event.params || {};
this.$emit('before-opened', event);
},
/**
* param: event
* return:
* exception:
* Description : Run Event before Modal closing
*/
beforeClosed(event) {
window.removeEventListener('keyup', this.onKeyUp);
this.params = {};
this.$emit('before-closed', event);
},
/**
* param: i -> button index
* param: event
* param: source -> String
* return:
* exception:
* Description : Run Event Button Click
*/
click(i, event, source = 'click') {
const button = this.buttons[i];
if (button && typeof button.handler === 'function') {
button.handler(i, event, { source });
} else {
this.$modal.hide('dialog');
}
},
/**
* param: event
* param: source -> String
* return:
* exception:
* Description : Run Event Close Button Click
*/
closeClick(event, source = 'click') {
const button = this.buttons[0];
if (button && typeof button.handler === 'function') {
button.handler(0, event, { source });
}
this.$modal.hide('dialog');
},
/**
* param: event
* return:
* exception:
* Description : Run Event Key Up
*/
onKeyUp(event) {
if (event.which === 13 && this.buttons.length > 0) {
const buttonIndex = this.buttons.length === 1 ? 0 : this.buttons.findIndex(button => button.default);
if (buttonIndex !== -1) {
this.click(buttonIndex, event, 'keypress');
}
}
},
},
};
</script>
<style>
button span {
pointer-events: none;
}
.vue-dialog div {
box-sizing: border-box;
}
.vue-dialog .dialog-flex {
width: 100%;
height: 100%;
}
.vue-dialog .dialog-content {
flex: 1 0 auto;
width: 100%;
padding: 15px;
font-size: 14px;
}
.vue-dialog .dialog-c-title {
font-weight: 600;
padding-bottom: 15px;
}
.vue-dialog .vue-dialog-buttons {
display: flex;
flex: 0 1 auto;
width: 100%;
border-top: 1px solid #eee;
}
.vue-dialog .vue-dialog-buttons-none {
width: 100%;
padding-bottom: 15px;
}
.vue-dialog-button {
font-size: 12px !important;
background: transparent;
padding: 0;
margin: 0;
border: 0;
cursor: pointer;
box-sizing: border-box;
line-height: 40px;
height: 40px;
color: inherit;
font: inherit;
outline: none;
}
.vue-dialog-button:hover {
background: rgba(0, 0, 0, 0.01);
}
.vue-dialog-button:active {
background: rgba(0, 0, 0, 0.025);
}
.vue-dialog-button:not(:first-of-type) {
border-left: 1px solid #eee;
}
</style>
@@ -0,0 +1,734 @@
<!--
Program: Modal
Author: SDL
Description: Modal Common Component
-->
<template>
<transition :name="overlayTransition">
<div v-if="visibility.overlay" ref="overlay" :class="overlayClass" :aria-expanded="visibility.overlay.toString()" :data-modal="name">
<div class="v--modal-background-click" :style="scrollable ? 'overflow: hidden;' : ''" @mousedown.self="handleBackgroundClick" @touchstart.self.passive="handleBackgroundClick">
<div class="v--modal-top-right">
<slot name="top-right" />
</div>
<transition :name="transition" @before-enter="beforeTransitionEnter" @after-enter="afterTransitionEnter" @after-leave="afterTransitionLeave">
<div v-if="visibility.modal" ref="modal" :class="modalClass" :style="modalStyle" style="display: block;">
<slot />
<resizer v-if="resizable && !isAutoHeight" :min-width="minWidth" :min-height="minHeight" @resize="handleModalResize" />
</div>
</transition>
</div>
</div>
</transition>
</template>
<script>
/* eslint-disable no-underscore-dangle */
// eslint-disable-next-line indent
// eslint-disable-next-line comma-dangle
import Modal from './index';
import Resizer from './Resizer.vue';
import { inRange, createModalEvent, getMutationObserver, blurActiveElement } from './utils';
import { parseNumber, validateNumber } from './parser';
export default {
name: 'Modal',
props: {
name: {
required: true,
type: String,
},
delay: {
type: Number,
default: 0,
},
resizable: {
type: Boolean,
default: false,
},
adaptive: {
type: Boolean,
default: false,
},
draggable: {
type: [Boolean, String],
default: false,
},
scrollable: {
type: Boolean,
default: true,
},
reset: {
type: Boolean,
default: false,
},
overlayTransition: {
type: String,
default: 'overlay-fade',
},
transition: {
type: String,
},
clickToClose: {
type: Boolean,
default: true,
},
classes: {
type: [String, Array],
default: 'v--modal',
},
minWidth: {
type: Number,
default: 0,
validator(value) {
return value >= 0;
},
},
minHeight: {
type: Number,
default: 0,
validator(value) {
return value >= 0;
},
},
maxWidth: {
type: Number,
default: Infinity,
},
maxHeight: {
type: Number,
default: Infinity,
},
width: {
type: [Number, String],
default: 600,
validator: validateNumber,
},
height: {
type: [Number, String],
default: 300,
validator(value) {
return value === 'auto' || validateNumber(value);
},
},
pivotX: {
type: Number,
default: 0.5,
validator(value) {
return value >= 0 && value <= 1;
},
},
pivotY: {
type: Number,
default: 0.5,
validator(value) {
return value >= 0 && value <= 1;
},
},
},
components: {
Resizer,
},
data() {
return {
visible: false,
visibility: {
modal: false,
overlay: false,
},
shift: {
left: 0,
top: 0,
},
modal: {
width: 0,
widthType: 'px',
height: 0,
heightType: 'px',
renderedHeight: 0,
},
window: {
width: 0,
height: 0,
},
mutationObserver: null,
};
},
created() {
this.setInitialSize();
},
/**
* Sets global listeners
*/
beforeMount() {
Modal.event.$on('toggle', this.handleToggleEvent);
window.addEventListener('resize', this.handleWindowResize);
this.handleWindowResize();
/**
* Making sure that autoHeight is enabled when using "scrollable"
*/
if (this.scrollable && !this.isAutoHeight) {
console.warn(`Modal "${this.name}" has scrollable flag set to true but height is not "auto" (${this.height})`);
}
/**
* Only observe when using height: 'auto'
* The callback will be called when modal DOM changes,
* this is for updating the `top` attribute for height 'auto' modals.
*/
if (this.isAutoHeight) {
/**
* MutationObserver feature detection:
*
* Detects if MutationObserver is available, return false if not.
* No polyfill is provided here, so height 'auto' recalculation will
* simply stay at its initial height (won't crash).
* (Provide polyfill to support IE < 11)
*
* https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
*
* For the sake of SSR, MutationObserver cannot be initialized
* before component creation >_<
*/
const MutationObserver = getMutationObserver();
if (MutationObserver) {
this.mutationObserver = new MutationObserver(() => {
this.updateRenderedHeight();
});
}
}
if (this.clickToClose) {
window.addEventListener('keyup', this.handleEscapeKeyUp);
}
},
/**
* Removes global listeners
*/
beforeUnmount() {
Modal.event.$off('toggle', this.handleToggleEvent);
window.removeEventListener('resize', this.handleWindowResize);
if (this.clickToClose) {
window.removeEventListener('keyup', this.handleEscapeKeyUp);
}
/**
* Removes blocked scroll
*/
if (this.scrollable) {
if (this.$root._dynamicContainer.modals.length === 0 || typeof this.$root._dynamicContainer.modals === 'undefined') {
document.body.classList.remove('v--modal-block-scroll');
}
}
},
computed: {
/**
* Returns true if height is set to "auto"
*/
isAutoHeight() {
return this.modal.heightType === 'auto';
},
/**
* Calculates and returns modal position based on the
* pivots, window size and modal size
*/
position() {
// eslint-disable-next-line indent
// eslint-disable-next-line object-curly-newline
const { window, shift, pivotX, pivotY, trueModalWidth, trueModalHeight } = this;
const maxLeft = window.width - trueModalWidth;
const maxTop = window.height - trueModalHeight;
const left = shift.left + pivotX * maxLeft;
const top = shift.top + pivotY * maxTop;
return {
left: parseInt(inRange(0, maxLeft, left), 10),
top: parseInt(inRange(0, maxTop, top), 10),
};
},
/**
* Returns pixel width (if set with %) and makes sure that modal size
* fits the window
*/
trueModalWidth() {
// eslint-disable-next-line indent
// eslint-disable-next-line object-curly-newline
const { window, modal, adaptive, minWidth, maxWidth } = this;
const value = modal.widthType === '%' ? (window.width / 100) * modal.width : modal.width;
const max = Math.min(window.width, maxWidth);
return adaptive ? inRange(minWidth, max, value) : value;
},
/**
* Returns pixel height (if set with %) and makes sure that modal size
* fits the window.
*
* Returns modal.renderedHeight if height set as "auto"
*/
trueModalHeight() {
// eslint-disable-next-line indent
// eslint-disable-next-line object-curly-newline
const { window, modal, isAutoHeight, adaptive, maxHeight } = this;
const value = modal.heightType === '%' ? (window.height / 100) * modal.height : modal.height;
if (isAutoHeight) {
// use renderedHeight when height 'auto'
return this.modal.renderedHeight;
}
const max = Math.min(window.height, maxHeight);
return adaptive ? inRange(this.minHeight, max, value) : value;
},
/**
* Returns class list for screen overlay (modal background)
*/
overlayClass() {
return {
'v--modal-overlay': true,
scrollable: this.scrollable && this.isAutoHeight,
};
},
/**
* Returns class list for modal itself
*/
modalClass() {
return ['v--modal-box', 'modal', this.classes];
},
/**
* CSS styles for position and size of the modal
*/
modalStyle() {
return {
top: `${this.position.top}px`,
left: `${this.position.left}px`,
width: `${this.trueModalWidth}px`,
height: this.isAutoHeight ? 'auto' : `${this.trueModalHeight}px`,
};
},
},
watch: {
/**
* Sets the visibility of overlay and modal.
* Events 'opened' and 'closed' is called here
* inside `setTimeout` and `$nextTick`, after the DOM changes.
* This fixes `$refs.modal` `undefined` bug (fixes #15)
*/
visible(value) {
if (value) {
this.visibility.overlay = true;
setTimeout(() => {
this.visibility.modal = true;
this.$nextTick(() => {
this.addDraggableListeners();
this.callAfterEvent(true);
});
}, this.delay);
} else {
this.visibility.modal = false;
setTimeout(() => {
this.visibility.overlay = false;
this.$nextTick(() => {
this.removeDraggableListeners();
this.callAfterEvent(false);
});
}, this.delay);
}
},
// 페이지 변경시 자동 close처리
$route() {
this.$modal.hide(this.name);
},
},
methods: {
handleToggleEvent(name, state, params) {
if (this.name === name) {
const nextState = typeof state === 'undefined' ? !this.visible : state;
this.toggle(nextState, params);
}
},
/**
* Initializes modal's size & position,
* if "reset" flag is set to true - this function will be called
* every time "beforeOpen" is triggered
*/
setInitialSize() {
const { modal } = this;
const width = parseNumber(this.width);
const height = parseNumber(this.height);
modal.width = width.value;
modal.widthType = width.type;
modal.height = height.value;
modal.heightType = height.type;
},
handleEscapeKeyUp(event) {
if (event.which === 27 && this.visible) {
this.$modal.hide(this.name);
}
},
handleWindowResize() {
this.window.width = window.innerWidth;
this.window.height = window.innerHeight;
this.ensureShiftInWindowBounds();
},
/**
* Generates event object
*/
createModalEvent(args = {}) {
return createModalEvent({
name: this.name,
ref: this.$refs.modal,
...args,
});
},
/**
* Event handler which is triggered on modal resize
*/
handleModalResize(event) {
this.modal.widthType = 'px';
this.modal.width = event.size.width;
this.modal.heightType = 'px';
this.modal.height = event.size.height;
const { size } = this.modal;
this.$emit('resize', this.createModalEvent({ size }));
},
/**
* Event handler which is triggered on $modal.show and $modal.hide
* BeforeEvents: ('before-close' and 'before-open') are `$emit`ed here,
* but AfterEvents ('opened' and 'closed') are moved to `watch.visible`.
*/
toggle(nextState, params) {
const { reset, scrollable, visible } = this;
if (visible === nextState) {
return;
}
const beforeEventName = visible ? 'before-close' : 'before-open';
if (beforeEventName === 'before-open') {
/**
* Need to unfocus previously focused element, otherwise
* all keypress events (ESC press, for example) will trigger on that element.
*/
blurActiveElement();
if (reset) {
this.setInitialSize();
this.shift.left = 0;
this.shift.top = 0;
}
if (scrollable) {
document.body.classList.add('v--modal-block-scroll');
}
} else if (scrollable) {
if (this.$root._dynamicContainer.modals.length === 0 || typeof this.$root._dynamicContainer.modals === 'undefined') {
document.body.classList.remove('v--modal-block-scroll');
}
}
let stopEventExecution = false;
const stop = () => {
stopEventExecution = true;
};
const beforeEvent = this.createModalEvent({
stop,
state: nextState,
params,
});
this.$emit(beforeEventName, beforeEvent);
if (!stopEventExecution) {
this.visible = nextState;
}
},
getDraggableElement() {
const selector = typeof this.draggable !== 'string' ? '.v--modal-box' : this.draggable;
return selector ? this.$refs.overlay.querySelector(selector) : null;
},
/**
* Event handler that is triggered when background overlay is clicked
*/
handleBackgroundClick() {
if (this.clickToClose) {
this.toggle(false);
}
},
/**
*'opened' and 'closed' events are `$emit`ed here.
* This is called in watch.visible.
* Because modal DOM updates are async,
* wrapping afterEvents in `$nextTick` fixes `$refs.modal` undefined bug.
* (fixes #15)
*/
callAfterEvent(state) {
if (state) {
this.connectObserver();
} else {
this.disconnectObserver();
}
const eventName = state ? 'opened' : 'closed';
const event = this.createModalEvent({ state });
this.$emit(eventName, event);
},
addDraggableListeners() {
if (!this.draggable) {
return;
}
const dragger = this.getDraggableElement();
if (dragger) {
let startX = 0;
let startY = 0;
let cachedShiftX = 0;
let cachedShiftY = 0;
const getPosition = event => (event.touches && event.touches.length > 0 ? event.touches[0] : event);
const handleDraggableMousemove = event => {
const { clientX, clientY } = getPosition(event);
this.shift.left = cachedShiftX + clientX - startX;
this.shift.top = cachedShiftY + clientY - startY;
event.preventDefault();
};
const handleDraggableMouseup = event => {
this.ensureShiftInWindowBounds();
document.removeEventListener('mousemove', handleDraggableMousemove);
document.removeEventListener('touchmove', handleDraggableMousemove);
document.removeEventListener('mouseup', handleDraggableMouseup);
document.removeEventListener('touchend', handleDraggableMouseup);
event.preventDefault();
};
const handleDraggableMousedown = event => {
const { target } = event;
if (target && target.nodeName === 'INPUT') {
return;
}
const { clientX, clientY } = getPosition(event);
document.addEventListener('mousemove', handleDraggableMousemove);
document.addEventListener('touchmove', handleDraggableMousemove);
document.addEventListener('mouseup', handleDraggableMouseup);
document.addEventListener('touchend', handleDraggableMouseup);
startX = clientX;
startY = clientY;
cachedShiftX = this.shift.left;
cachedShiftY = this.shift.top;
};
dragger.addEventListener('mousedown', handleDraggableMousedown);
dragger.addEventListener('touchstart', handleDraggableMousedown, { passive: true });
}
},
removeDraggableListeners() {
/**
* Ideally this is not needed because "dragger" will be unmounted anyway.
*/
},
/**
* Update $data.modal.renderedHeight using getBoundingClientRect.
* This method is called when:
* 1. modal opened
* 2. MutationObserver's observe callback
*/
updateRenderedHeight() {
if (this.$refs.modal) {
this.modal.renderedHeight = this.$refs.modal.getBoundingClientRect().height;
}
},
/**
* Start observing modal's DOM, if childList or subtree changes,
* the callback (registered in beforeMount) will be called.
*/
connectObserver() {
if (this.mutationObserver) {
this.mutationObserver.observe(this.$refs.overlay, {
childList: true,
attributes: true,
subtree: true,
});
}
},
/**
* Disconnects MutationObserver
*/
disconnectObserver() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
}
},
beforeTransitionEnter() {
this.connectObserver();
},
afterTransitionEnter() {
// console.log('after transition enter')
},
afterTransitionLeave() {
// console.log('after transition leave')
},
ensureShiftInWindowBounds() {
// eslint-disable-next-line indent
// eslint-disable-next-line object-curly-newline
const { window, shift, pivotX, pivotY, trueModalWidth, trueModalHeight } = this;
const maxLeft = window.width - trueModalWidth;
const maxTop = window.height - trueModalHeight;
const left = shift.left + pivotX * maxLeft;
const top = shift.top + pivotY * maxTop;
this.shift.left -= left - inRange(0, maxLeft, left);
this.shift.top -= top - inRange(0, maxTop, top);
},
},
};
</script>
<style>
.v--modal-block-scroll {
overflow: hidden;
width: 100vw;
}
.modal-dialog {
margin: 0 !important;
--bs-modal-margin: 0.5rem !important;
}
</style>
<style scoped>
.v--modal-block-scroll {
overflow: hidden;
width: 100vw;
}
.v--modal-overlay {
position: fixed;
box-sizing: border-box;
left: 0;
top: 0;
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.2);
z-index: 2000;
opacity: 1;
}
.v--modal-overlay.scrollable {
height: 100%;
min-height: 100vh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.v--modal-overlay .v--modal-background-click {
width: 100%;
min-height: 100%;
height: auto;
}
.v--modal-overlay .v--modal-box {
position: relative;
overflow: hidden;
box-sizing: border-box;
}
.v--modal-overlay.scrollable .v--modal-box {
margin-bottom: 2px;
}
.v--modal {
background-color: white;
text-align: left;
border-radius: 3px;
box-shadow: 0 20px 60px -2px rgba(27, 33, 58, 0.4);
padding: 0;
}
.v--modal.v--modal-fullscreen {
width: 100vw;
height: 100vh;
margin: 0;
left: 0;
top: 0;
}
.modal-dialog {
margin: 0 !important;
}
.modal-lg {
max-width: 1200px;
}
.v--modal-top-right {
display: block;
position: absolute;
right: 0;
top: 0;
}
.overlay-fade-enter-active,
.overlay-fade-leave-active {
transition: all 0.2s;
}
.overlay-fade-enter,
.overlay-fade-leave-active {
opacity: 0;
}
.nice-modal-fade-enter-active,
.nice-modal-fade-leave-active {
transition: all 0.4s;
}
.nice-modal-fade-enter,
.nice-modal-fade-leave-active {
opacity: 0;
transform: translateY(-20px);
}
</style>
@@ -0,0 +1,66 @@
<!--
Program: Modal Container
Author: SDL
Description: Modal Container Common Component
-->
<template>
<div id="modals-container">
<modal
v-bind="modal.modalAttrs"
v-for="modal in modals"
:key="modal.id"
v-on="modal.modalListeners"
@closed="remove(modal.id)"
>
<component
v-bind="modal.componentAttrs"
:is="modal.component"
@close="$modal.hide(modal.modalAttrs.name)"
/>
</modal>
</div>
</template>
<script>
import { generateId } from './utils';
import { shallowRef } from 'vue';
const PREFIX = '_dynamic_modal_';
export default {
data() {
return {
modals: [],
};
},
created() {
// eslint-disable-next-line no-underscore-dangle
this.$root._dynamicContainer = this;
},
methods: {
add(component, componentAttrs = {}, modalAttrs = {}, modalListeners = {}) {
const id = generateId();
const name = modalAttrs.name || PREFIX + id;
const shallowComponent = shallowRef(component);
this.modals.push({
id,
modalAttrs: { ...modalAttrs, name },
modalListeners,
component: shallowComponent,
componentAttrs,
});
this.$nextTick(() => {
this.$modal.show(name);
});
},
remove(id) {
const index = this.modals.findIndex(v => v.id === id);
if (index !== -1) {
this.modals.splice(index, 1);
}
},
},
};
</script>
@@ -0,0 +1,115 @@
<!--
Program: Modal Resizer
Author: SDL
Description: Modal Resizer Common Component
-->
<template>
<div :class="className"></div>
</template>
<script>
import { inRange } from './utils';
export default {
name: 'VueJsModalResizer',
props: {
minHeight: {
type: Number,
default: 0,
},
minWidth: {
type: Number,
default: 0,
},
},
data() {
return {
clicked: false,
size: {},
};
},
mounted() {
this.$el.addEventListener('mousedown', this.start, false);
},
computed: {
className() {
return { 'vue-modal-resizer': true, clicked: this.clicked };
},
},
methods: {
start(event) {
this.clicked = true;
window.addEventListener('mousemove', this.mousemove, false);
window.addEventListener('mouseup', this.stop, false);
event.stopPropagation();
event.preventDefault();
},
stop() {
this.clicked = false;
window.removeEventListener('mousemove', this.mousemove, false);
window.removeEventListener('mouseup', this.stop, false);
this.$emit('resize-stop', {
element: this.$el.parentElement,
size: this.size,
});
},
mousemove(event) {
this.resize(event);
},
resize(event) {
const el = this.$el.parentElement;
if (el) {
let width = event.clientX - el.offsetLeft;
let height = event.clientY - el.offsetTop;
width = inRange(this.minWidth, window.innerWidth, width);
height = inRange(this.minHeight, window.innerHeight, height);
this.size = { width, height };
el.style.width = `${width}px`;
el.style.height = `${height}px`;
this.$emit('resize', {
element: el,
size: this.size,
});
}
},
},
};
</script>
<style>
.vue-modal-resizer {
display: block;
overflow: hidden;
position: absolute;
width: 12px;
height: 12px;
right: 0;
bottom: 0;
z-index: 9999999;
background: transparent;
cursor: se-resize;
}
.vue-modal-resizer::after {
display: block;
position: absolute;
content: '';
background: transparent;
left: 0;
top: 0;
width: 0;
height: 0;
border-bottom: 10px solid #ddd;
border-left: 10px solid transparent;
}
.vue-modal-resizer.clicked::after {
border-bottom: 10px solid #369be9;
}
</style>
@@ -0,0 +1,121 @@
/**
* Program: Modal Plugin
* Author: SDL
* Description: Modal Plugin Common Component
*/
import emitter from '@/event/event';
import Modal from './Modal.vue';
import Dialog from './Dialog.vue';
import ModalsContainer from './ModalsContainer.vue';
import { createDivInBody } from './utils';
const DEFAULT_COMPONENT_NAME = 'Modal';
const UNMOUNTED_ROOT_ERROR_MESSAGE = '[vue-js-modal] ' + 'In order to render dynamic modals, a <modals-container> ' + 'component must be present on the page.';
const DYNAMIC_MODAL_DISABLED_ERROR = '[vue-js-modal] ' + '$modal() received object as a first argument, but dynamic modals are ' + 'switched off. https://github.com/euvl/vue-js-modal/#dynamic-modals';
const UNSUPPORTED_ARGUMENT_ERROR = '[vue-js-modal] $modal() received an unsupported argument as a first argument.';
export const getModalsContainer = (Vue, options, root) => {
// eslint-disable-next-line no-underscore-dangle
if (!root._dynamicContainer && options.injectModalsContainer) {
// const container = createDivInBody();
// createApp({
// parent: root,
// render: h => h(ModalsContainer),
// }).mount(container);
}
// eslint-disable-next-line no-underscore-dangle
return root._dynamicContainer;
};
const Plugin = {
install(Vue, options = {}) {
/**
* Makes sure that plugin can be installed only once
*/
if (this.installed) {
return;
}
this.installed = true;
this.app = Vue;
this.event = emitter;
this.rootInstance = null;
const componentName = options.componentName || DEFAULT_COMPONENT_NAME;
const dynamicDefaults = options.dynamicDefaults || {};
/**
* Plugin API
*/
const showStaticModal = (modal, params) => {
emitter.$emit('toggle', modal, true, params);
};
const showDynamicModal = (modal, props, params, events) => {
/**
* Get root for dynamic modal
*/
const root = params && params.root ? params.root : Plugin.rootInstance;
const container = getModalsContainer(Vue, options, root);
/**
* Show dynamic modal
*/
if (container) {
container.add(modal, props, { ...dynamicDefaults, ...params }, events);
return;
}
console.warn(UNMOUNTED_ROOT_ERROR_MESSAGE);
};
Vue.config.globalProperties.$modal = {
// eslint-disable-next-line consistent-return
show(modal, ...args) {
switch (typeof modal) {
case 'string': {
return showStaticModal(modal, ...args);
}
case 'object':
case 'function': {
return options.dynamic ? showDynamicModal(modal, ...args) : console.warn(DYNAMIC_MODAL_DISABLED_ERROR);
}
default: {
console.warn(UNSUPPORTED_ARGUMENT_ERROR, modal);
return false;
}
}
},
hide(name, params) {
emitter.$emit('toggle', name, false, params);
},
toggle(name, params) {
emitter.$emit('toggle', name, undefined, params);
},
};
/**
* Sets custom component name (if provided)
*/
Vue.component(componentName, Modal);
/**
* Registration of <Dialog/> component
*/
if (options.dialog) {
Vue.component('sdl-dialog', Dialog);
}
/**
* Registration of <ModalsContainer/> component
*/
if (options.dynamic) {
Vue.component('sdl-modal-container', ModalsContainer);
Vue.mixin({
beforeMount() {
if (Plugin.rootInstance === null) {
Plugin.rootInstance = this.$root;
}
},
});
}
},
};
export default Plugin;
@@ -0,0 +1,70 @@
/**
* Program: Modal parser
* Author: SDL
* Description: Modal parser util
*/
const floatRegexp = '[-+]?[0-9]*.?[0-9]+';
const types = [
{
name: 'px',
regexp: new RegExp(`^${floatRegexp}px$`),
},
{
name: '%',
regexp: new RegExp(`^${floatRegexp}%$`),
},
/**
* Fallback optopn
* If no suffix specified, assigning "px"
*/
{
name: 'px',
regexp: new RegExp(`^${floatRegexp}$`),
},
];
const getType = value => {
if (value === 'auto') {
return {
type: value,
value: 0,
};
}
for (let i = 0; i < types.length; i += 1) {
const type = types[i];
if (type.regexp.test(value)) {
return {
type: type.name,
value: parseFloat(value),
};
}
}
return {
type: '',
value,
};
};
export const parseNumber = value => {
switch (typeof value) {
case 'number':
return { type: 'px', value };
case 'string':
return getType(value);
default:
return { type: '', value };
}
};
export const validateNumber = value => {
if (typeof value === 'string') {
const value2 = parseNumber(value);
return (value2.type === '%' || value2.type === 'px') && value2.value > 0;
}
return value >= 0;
};
@@ -0,0 +1,60 @@
export const generateId = ((index = 0) => () => {
index += 1;
return index.toString();
})();
/**
*
* @param {Number} from Lower limit
* @param {Number} to Upper limit
* @param {Number} value Checked number value
*
* @return {Number} Either source value itself or limit value if range limits
* are exceeded
*/
// eslint-disable-next-line no-nested-ternary
export const inRange = (from, to, value) => (value < from ? from : value > to ? to : value);
export const createModalEvent = (args = {}) => ({
id: generateId(),
timestamp: Date.now(),
canceled: false,
...args,
});
export const getMutationObserver = () => {
if (typeof window !== 'undefined') {
const prefixes = ['', 'WebKit', 'Moz', 'O', 'Ms'];
for (let i = 0; i < prefixes.length; i += 1) {
const name = `${prefixes[i]}MutationObserver`;
if (name in window) {
return window[name];
}
}
}
return false;
};
export const createDivInBody = () => {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
};
export const blurActiveElement = () => {
if (
typeof document !== 'undefined'
&& document.activeElement
&& document.activeElement.tagName !== 'BODY'
&& document.activeElement.blur
) {
document.activeElement.blur();
return true;
}
return false;
};
@@ -0,0 +1,113 @@
<!--
Program: Pagination
Author: SDL
Description: Pagination Common Component
-->
<template>
<ul class="pagination b-pagination pagination-md justify-content-center">
<li class="page-item" :class="[{ disabled: currentPage === 1 }]">
<a href="#" class="page-link" @click.prevent="changePage(1)"></a>
</li>
<li class="page-item" :class="[{ disabled: currentPage <= 1 }]">
<a href="#" class="page-link" @click.prevent="changePage(1 >= currentPage - limit ? 1 : currentPage - limit)">
{{ config.prevText }}
</a>
</li>
<li v-for="num in pages" class="page-item" :class="[{ active: num === currentPage }]" :key="num">
<a href="#" @click.prevent="changePage(num)" class="page-link">
{{ num }}
</a>
</li>
<li class="page-item" :class="[{ disabled: currentPage >= lastPage }]">
<a
href="#"
class="page-link"
@click.prevent="changePage(currentPage > lastPage - limit ? lastPage : currentPage + limit)"
>{{ config.nextText }}</a
>
</li>
<li class="page-item" :class="[{ disabled: currentPage === lastPage || lastPage === 0 }]">
<a href="#" class="page-link" @click.prevent="changePage(lastPage)"></a>
</li>
</ul>
</template>
<script>
export default {
props: {
currentPage: {
required: false,
type: Number,
},
totalRows: {
required: false,
type: Number,
},
perPage: {
required: false,
type: Number,
},
limit: {
type: Number,
default: 10,
},
},
data() {
return {
config: { prevText: '<', nextText: '>' },
curPage: 1,
};
},
created() {
this.curPage = this.currentPage;
},
computed: {
/**
* param:
* return:
* exception:
* Description : last page calculation
*/
lastPage() {
const total = this.totalRows / this.perPage;
if (total < 1) return 1;
if (total % 1 !== 0) return parseInt(total + 1, 10);
return total;
},
/**
* param:
* return:
* exception:
* Description : page calculation to be shown
*/
pages() {
let from = Math.floor((this.currentPage - 1) / this.limit) * this.limit + 1;
let to = from + (this.limit - 1);
if (to >= this.lastPage) to = this.lastPage;
const arr = [];
while (from <= to) {
arr.push(from);
from += 1;
}
if (this.totalRows === null) {
arr.splice(0);
arr.push(1);
}
return arr;
},
},
methods: {
/**
* param:
* return:
* exception:
* Description : Parents' change event call
*/
changePage(page) {
this.$emit('change', page);
},
},
};
</script>
<style></style>
@@ -0,0 +1,39 @@
<template>
<button type="button" id="goTop" @click="scrollToTop" class="btn btn-secondary shadow ui--float-position ui--btn-float" :style="{ display: btnShow }">
<i class="bi bi-arrow-up"></i>
</button>
</template>
<script>
import debounce from 'lodash/debounce';
export default {
name: 'ScrollToTop',
data() {
return {
btnShow: '',
};
},
mounted() {
document.addEventListener(
'scroll',
debounce(() => {
this.btnShow = window.scrollY > 0 ? 'block' : 'none';
}, 50),
);
},
methods: {
scrollToTop() {
try {
window.scroll({
top: 0,
left: 0,
behavior: 'smooth',
});
} catch {
window.scrollTo(0, 0);
}
},
},
};
</script>
@@ -0,0 +1,333 @@
<!--
Program: Tree
Author: SDL
Description: Tree Common Component
-->
<template>
<div :class="classes" role="tree">
<ul :class="containerClasses" role="group" style="padding-bottom: 6px;">
<tree-item
v-for="(child, index) in data"
ref="children"
:key="index"
:data="child"
:text-field-name="textFieldName"
:before-item-click="beforeItemClick"
:value-field-name="valueFieldName"
:children-field-name="childrenFieldName"
:item-events="itemEvents"
:whole-row="wholeRow"
:show-checkbox="showCheckbox"
:allow-transition="allowTransition"
:height="sizeHeight"
:parent-item="data"
:draggable="draggable"
:drag-over-background-color="dragOverBackgroundColor"
:on-item-click="onItemClick"
:on-item-toggle="onItemToggle"
:on-item-drag-start="onItemDragStart"
:on-item-drag-end="onItemDragEnd"
:on-item-drop="onItemDrop"
:klass="index === data.length - 1 ? 'tree-last' : ''"
>
<template v-slot="_">
<slot :vm="_.vm" :model="_.model">
<i :class="_.vm.themeIconClasses" role="presentation" v-if="!_.model.loading"></i>
<span v-text="_.model[textFieldName]"></span>
</slot>
</template>
</tree-item>
</ul>
</div>
</template>
<script>
import TreeItem from './TreeItem.vue';
let ITEM_ID = 0;
const ITEM_HEIGHT_SMALL = 18;
const ITEM_HEIGHT_DEFAULT = 24;
const ITEM_HEIGHT_LARGE = 32;
export default {
name: 'Tree',
props: {
data: { type: Array },
size: { type: String, validator: value => ['large', 'small'].indexOf(value) > -1 },
showCheckbox: { type: Boolean, default: false },
wholeRow: { type: Boolean, default: false },
noDots: { type: Boolean, default: false },
collapse: { type: Boolean, default: false },
multiple: { type: Boolean, default: false },
allowBatch: { type: Boolean, default: false },
allowTransition: { type: Boolean, default: true },
textFieldName: { type: String, default: 'text' },
valueFieldName: { type: String, default: 'value' },
childrenFieldName: { type: String, default: 'children' },
itemEvents: {
type: Object,
default() {
return {};
},
},
async: { type: Function },
beforeItemClick: { type: Function },
loadingText: { type: String, default: 'Loading...' },
draggable: { type: Boolean, default: false },
dragOverBackgroundColor: { type: String, default: '#C9FDC9' },
klass: String,
},
data() {
return {
draggedItem: undefined,
draggedElm: undefined,
};
},
computed: {
classes() {
return [{ tree: true }, { 'tree-default': !this.size }, { [`tree-default-${this.size}`]: !!this.size }, { 'tree-checkbox-selection': !!this.showCheckbox }, { [this.klass]: !!this.klass }];
},
containerClasses() {
return [{ 'tree-container-ul': true }, { 'tree-children': true }, { 'tree-wholerow-ul': !!this.wholeRow }, { 'tree-no-dots': !!this.noDots }];
},
sizeHeight() {
switch (this.size) {
case 'large':
return ITEM_HEIGHT_LARGE;
case 'small':
return ITEM_HEIGHT_SMALL;
default:
return ITEM_HEIGHT_DEFAULT;
}
},
},
methods: {
initializeData(items) {
if (items && items.length > 0) {
for (let i of Object.keys(items)) {
items[i] = this.initializeDataItem(items[i]);
this.initializeData(items[i][this.childrenFieldName]);
}
}
},
initializeDataItem(item) {
function Model(itemObj, textFieldName, valueFieldName, childrenFieldName, collapse) {
ITEM_ID += 1;
this.id = itemObj.id || ITEM_ID;
this[textFieldName] = itemObj[textFieldName] || '';
this[valueFieldName] = itemObj[valueFieldName] || itemObj[textFieldName];
this.icon = itemObj.icon || '';
this.opened = itemObj.opened || collapse;
this.selected = itemObj.selected || false;
this.disabled = itemObj.disabled || false;
this.loading = itemObj.loading || false;
this.isFolder = itemObj.isFolder || false;
this[childrenFieldName] = itemObj[childrenFieldName] || [];
}
const node = Object.assign(new Model(item, this.textFieldName, this.valueFieldName, this.childrenFieldName, this.collapse), item);
const self = this;
node.addBefore = (data, selectedNode) => {
const newItem = self.initializeDataItem(data);
const index = selectedNode.parentItem.findIndex(t => t.id === node.id);
selectedNode.parentItem.splice(index, 0, newItem);
};
node.addAfter = (data, selectedNode) => {
const newItem = self.initializeDataItem(data);
const index = selectedNode.parentItem.findIndex(t => t.id === node.id) + 1;
selectedNode.parentItem.splice(index, 0, newItem);
};
node.addChild = data => {
const newItem = self.initializeDataItem(data);
node.opened = true;
node[self.childrenFieldName].push(newItem);
};
node.openChildren = () => {
node.opened = true;
self.handleRecursionNodeChildren(node, nodeObj => {
nodeObj.opened = true;
});
};
node.closeChildren = () => {
node.opened = false;
self.handleRecursionNodeChildren(node, nodeObj => {
nodeObj.opened = false;
});
};
return node;
},
initializeLoading() {
const item = {};
item[this.textFieldName] = this.loadingText;
item.disabled = true;
item.loading = true;
return this.initializeDataItem(item);
},
handleRecursionNodeChilds(node, func) {
if (func(node) !== false) {
if (node.$refs.children && node.$refs.children.length > 0) {
Object.keys(node.$refs.children).forEach(i => {
if (!node.$refs.children[i].disabled) {
this.handleRecursionNodeChilds(node.$refs.children[i], func);
}
});
}
}
},
handleRecursionNodeChildren(node, func) {
if (func(node) !== false) {
if (node[this.childrenFieldName] && node[this.childrenFieldName].length > 0) {
const nodeList = node[this.childrenFieldName];
Object.keys(nodeList).forEach(i => {
this.handleRecursionNodeChildren(nodeList[i], func);
});
}
}
},
onItemClick(oriNode, oriItem, e) {
if (this.multiple) {
if (this.allowBatch) {
this.handleBatchSelectItems(oriNode, oriItem);
}
} else {
this.handleSingleSelectItems(oriNode, oriItem);
}
this.$emit('item-click', oriNode, oriItem, e);
},
handleSingleSelectItems(oriNode) {
this.handleRecursionNodeChilds(this, node => {
if (node.model) node.model.selected = false;
});
oriNode.model.selected = true;
},
handleBatchSelectItems(oriNode) {
this.handleRecursionNodeChilds(oriNode, node => {
if (node.model.disabled) return;
node.model.selected = oriNode.model.selected;
});
},
onItemToggle(oriNode, oriItem, e) {
if (oriNode.model.opened) {
this.handleAsyncLoad(oriNode.model[this.childrenFieldName], oriNode, oriItem);
}
this.$emit('item-toggle', oriNode, oriItem, e);
},
handleAsyncLoad(oriParent, oriNode) {
const self = this;
if (this.async) {
if (oriParent[0].loading) {
this.async(oriNode, data => {
if (data.length > 0) {
Object.keys(data).forEach(i => {
if (!data[i].isLeaf) {
if (typeof data[i][self.childrenFieldName] !== 'object') {
data[i][self.childrenFieldName] = [self.initializeLoading()];
}
}
const dataItem = self.initializeDataItem(data[i]);
oriParent.splice(i, 1, dataItem);
});
} else {
oriNode.model[self.childrenFieldName] = [];
}
});
}
}
},
onItemDragStart(e, oriNode, oriItem) {
if (!this.draggable || oriItem.dragDisabled) return false;
e.dataTransfer.effectAllowed = 'move';
const agent = window.navigator.userAgent.toLowerCase();
if (!(window.navigator.appName === 'Netscape' && agent.indexOf('trident') !== -1) || agent.indexOf('msie') !== -1) {
e.dataTransfer.setData('text', null);
}
this.draggedElm = e.target;
this.draggedItem = {
item: oriItem,
parentItem: oriNode.parentItem,
index: oriNode.parentItem.findIndex(t => t.id === oriItem.id),
};
this.$emit('item-drag-start', oriNode, oriItem, e);
return true;
},
onItemDragEnd(e, oriNode, oriItem) {
this.draggedItem = undefined;
this.draggedElm = undefined;
this.$emit('item-drag-end', oriNode, oriItem, e);
},
onItemDrop(e, oriNode, oriItem, parentItem, parent) {
if (!this.draggable || !!oriItem.dropDisabled) return false;
this.$emit('item-drop-before', oriNode, oriItem, !this.draggedItem ? undefined : this.draggedItem.item, e);
if (!this.draggedElm || this.draggedElm === e.target || this.draggedElm.contains(e.target)) {
return true;
}
if (this.draggedItem) {
if (
this.draggedItem.parentItem === oriItem[this.childrenFieldName] ||
this.draggedItem.item === oriItem ||
(oriItem[this.childrenFieldName] && oriItem[this.childrenFieldName].findIndex(t => t.id === this.draggedItem.item.id) !== -1)
) {
return true;
}
let delFlag = false;
if (oriItem[this.childrenFieldName]) {
// console.log('e.target:', e.target.nodeName);
if (e.target.nodeName === 'SPAN') {
oriItem[this.childrenFieldName].push(this.draggedItem.item);
} else {
// console.log('11:', this.draggedItem);
if (parentItem === this.draggedItem.parentItem) {
parentItem.splice(
parentItem.findIndex(t => t.id === this.draggedItem.item.id),
1,
);
delFlag = true;
}
// console.log('parentItem:', e.target.name);
// if (parentItem.findIndex(t => t.id === oriItem.id) + 1 === parentItem.length) {
// parentItem.push(this.draggedItem.item);
// } else {
parentItem.splice(parentItem.findIndex(t => t.id === oriItem.id) + 1, 0, this.draggedItem.item);
// }
}
} else {
oriItem[this.childrenFieldName] = [this.draggedItem.item];
}
if (e.target.nodeName === 'SPAN') {
oriItem.opened = true;
}
const { draggedItem } = this;
this.$nextTick(() => {
if (!delFlag) {
draggedItem.parentItem.splice(draggedItem.index, 1);
}
});
// console.log('111:', oriNode);
// console.log('111:', oriItem, parentItem);
// console.log('111:', parent);
if (e.target.nodeName === 'SPAN') {
this.$emit('item-drop', oriNode, oriItem, this.draggedItem.item, e);
} else {
this.$emit('item-drop', oriNode, parent, this.draggedItem.item, e);
}
}
return true;
},
},
created() {
this.initializeData(this.data);
},
mounted() {
if (this.async) {
this.data.splice(0, 1, this.initializeLoading());
this.handleAsyncLoad(this.data, this);
}
},
components: {
TreeItem,
},
};
</script>
<style lang="less">
@import './less/style';
</style>
@@ -0,0 +1,313 @@
<!--
Program: Tree Item
Author: SDL
Description: Tree Item Common Component
-->
<template>
<li
role="treeitem"
:class="classes"
:draggable="draggable"
@dragstart.stop="onItemDragStart($event, self, self.model)"
@dragend.stop.prevent="onItemDragEnd($event, self, self.model)"
@dragover.stop.prevent="isDragEnter = true"
@dragenter.stop.prevent="isDragEnter = true"
@dragleave.stop.prevent="isDragEnter = false"
@drop.stop.prevent="handleItemDrop($event, self, self.model)"
>
<div role="presentation" :class="wholeRowClasses" v-if="isWholeRow">&nbsp;</div>
<i class="tree-icon tree-ocl" role="presentation" @click="handleItemToggle"></i>
<div :class="anchorClasses" v-on="events">
<i class="tree-icon tree-checkbox" role="presentation" v-if="showCheckbox && !model.loading"></i>
<slot :vm="this" :model="model">
<i :class="themeIconClasses" role="presentation" v-if="!model.loading"></i>
<span v-text="model[textFieldName]"></span>
</slot>
</div>
<div
name="middleArea"
style="width:100%;height:5px;position:absolute;z-index:1;"
:class="{ 'tree-after-middle-area': isAfterMiddleArea }"
@dragend.stop.prevent="onItemDragEnd($event, self, self.model)"
@dragover.stop.prevent="isAfterMiddleArea = true"
@dragenter.stop.prevent="isAfterMiddleArea = true"
@dragleave.stop.prevent="isAfterMiddleArea = false"
@drop.stop.prevent="handleItemDrop($event, self, self.model, parentItem, parent, 'after')"
>
<div class="arrow-top" v-show="isAfterMiddleArea" />
<div class="arrow-bottom" v-show="isAfterMiddleArea" />
</div>
<ul role="group" ref="group" class="tree-children" v-if="isFolder" :style="groupStyle">
<tree-item
v-for="(child, index) in model[childrenFieldName]"
ref="children"
:before-item-click="beforeItemClick"
:key="index"
:data="child"
:parent="self.model"
:text-field-name="textFieldName"
:value-field-name="valueFieldName"
:children-field-name="childrenFieldName"
:item-events="itemEvents"
:whole-row="wholeRow"
:show-checkbox="showCheckbox"
:allow-transition="allowTransition"
:height="height"
:parent-item="model[childrenFieldName]"
:draggable="draggable"
:drag-over-background-color="dragOverBackgroundColor"
:on-item-click="onItemClick"
:on-item-toggle="onItemToggle"
:on-item-drag-start="onItemDragStart"
:on-item-drag-end="onItemDragEnd"
:on-item-drop="onItemDrop"
:klass="index === model[childrenFieldName].length - 1 ? 'tree-last' : ''"
>
<template v-slot="_">
<slot :vm="_.vm" :model="_.model">
<i :class="_.vm.themeIconClasses" role="presentation" v-if="!_.model.loading"></i>
<span v-text="_.model[textFieldName]"></span>
</slot>
</template>
</tree-item>
</ul>
</li>
</template>
<script>
export default {
name: 'TreeItem',
props: {
data: { type: Object, required: true },
textFieldName: { type: String },
valueFieldName: { type: String },
childrenFieldName: { type: String },
itemEvents: { type: Object },
wholeRow: { type: Boolean, default: false },
showCheckbox: { type: Boolean, default: false },
allowTransition: { type: Boolean, default: true },
height: { type: Number, default: 24 },
parentItem: { type: Array },
parent: { type: Object },
draggable: { type: Boolean, default: false },
dragOverBackgroundColor: { type: String },
beforeItemClick: {
type: Function,
default: () => true,
},
onItemClick: {
type: Function,
default: () => false,
},
onItemToggle: {
type: Function,
default: () => false,
},
onItemDragStart: {
type: Function,
default: () => false,
},
onItemDragEnd: {
type: Function,
default: () => false,
},
onItemDrop: {
type: Function,
default: () => false,
},
klass: String,
},
data() {
return {
isHover: false,
isDragEnter: false,
model: this.data,
maxHeight: 0,
events: {},
isBeforeMiddleArea: false,
isAfterMiddleArea: false,
self: null,
};
},
watch: {
isDragEnter(newValue) {
if (newValue) {
this.$el.style.backgroundColor = this.dragOverBackgroundColor;
} else {
this.$el.style.backgroundColor = 'inherit';
}
},
data(newValue) {
this.model = newValue;
},
'model.opened': {
// handler(val, oldVal) {
handler() {
this.onItemToggle(this, this.model);
this.handleGroupMaxHeight();
},
deep: true,
},
},
computed: {
isFolder() {
return (this.model[this.childrenFieldName] && this.model[this.childrenFieldName].length) || this.model.isFolder;
},
classes() {
return [
{ 'tree-node': true },
{ 'tree-open': this.model.opened },
{ 'tree-closed': !this.model.opened },
{ 'tree-leaf': !this.isFolder },
{ 'tree-loading': !!this.model.loading },
{ 'tree-drag-enter': this.isDragEnter },
{ [this.klass]: !!this.klass },
];
},
anchorClasses() {
return [
{ 'tree-anchor': true },
{ 'tree-disabled': this.model.disabled },
{ 'tree-selected': this.model.selected },
{ 'tree-hovered': this.isHover },
];
},
wholeRowClasses() {
return [
{ 'tree-wholerow': true },
{ 'tree-wholerow-clicked': this.model.selected },
{ 'tree-wholerow-hovered': this.isHover },
];
},
themeIconClasses() {
return [
{ 'tree-icon': true },
{ 'tree-themeicon': true },
{ [this.model.icon]: !!this.model.icon },
{ 'tree-themeicon-custom': !!this.model.icon },
{ 'tree-file': !this.isFolder },
];
},
isWholeRow() {
if (this.wholeRow) {
if (this.$parent.model === undefined) {
return true;
}
return this.$parent.model.opened === true;
}
return false;
},
groupStyle() {
return {
position: this.model.opened ? '' : 'relative',
'max-height': this.allowTransition ? `${this.maxHeight}px` : '',
'transition-duration': this.allowTransition
? `${Math.ceil(this.model[this.childrenFieldName].length / 100) * 300}ms`
: '',
'transition-property': this.allowTransition ? 'max-height' : '',
// display: this.allowTransition ? 'block' : this.model.opened ? 'block' : 'none',
display: this.model.opened ? 'block' : 'none',
};
},
},
methods: {
handleItemToggle() {
if (this.isFolder) {
this.model.opened = !this.model.opened;
this.onItemToggle(this, this.model);
}
},
handleGroupMaxHeight() {
if (this.allowTransition) {
let length = 0;
let childHeight = 0;
if (this.model.opened && this.$refs.children) {
// eslint-disable-next-line prefer-destructuring
length = this.$refs.children.length;
this.$refs.children.forEach((child) => {
childHeight += child.maxHeight;
});
}
this.maxHeight = length * this.height + childHeight;
// eslint-disable-next-line no-underscore-dangle
if (this.$parent.$options._componentTag === 'tree-item' || this.$parent.$options.name === 'TreeItem') {
this.$parent.handleGroupMaxHeight();
}
}
},
handleItemClick(e) {
if (this.model.disabled) return;
if (!this.beforeItemClick(this.model)) return;
this.model.selected = !this.model.selected;
this.onItemClick(this, this.model, e);
},
handleItemMouseOver() {
this.isHover = true;
},
handleItemMouseOut() {
this.isHover = false;
},
handleItemDrop(e, oriNode, oriItem, parentItem, parent) {
this.$el.style.backgroundColor = 'inherit';
this.isBeforeMiddleArea = false;
this.isAfterMiddleArea = false;
this.onItemDrop(e, oriNode, oriItem, parentItem, parent);
},
},
created() {
this.self = this;
const events = {
click: this.handleItemClick,
mouseover: this.handleItemMouseOver,
mouseout: this.handleItemMouseOut,
};
// for (const itemEvent in this.itemEvents) {
Object.keys(this.itemEvents).forEach(itemEvent => {
const itemEventCallback = this.itemEvents[itemEvent];
if (Object.prototype.hasOwnProperty.call(events, itemEvent)) {
const eventCallback = events[itemEvent];
events[itemEvent] = event => {
eventCallback(this.self, this.self.model, event);
itemEventCallback(this.self, this.self.model, event);
};
} else {
events[itemEvent] = event => {
itemEventCallback(this.self, this.self.model, event);
};
}
});
this.events = events;
if (this.$parent.model !== undefined && this.model.opened === true) {
this.$parent.model.opened = true;
}
},
mounted() {
this.handleGroupMaxHeight();
},
};
</script>
<style scoped>
.tree-after-middle-area {
border-top: 1px solid #868e96;
position: relative;
}
.tree-after-middle-area > .arrow-top {
position: absolute;
top: -6px;
width: 0;
height: 0;
border-style: solid;
border-width: 5px 0 0 5px;
border-color: transparent transparent transparent #868e96;
}
.tree-after-middle-area > .arrow-bottom {
width: 0;
height: 0;
border-style: solid;
border-width: 5px 5px 0 0;
border-color: #868e96 transparent transparent transparent;
}
</style>
@@ -0,0 +1,3 @@
import Tree from './Tree.vue';
export default Tree;
@@ -0,0 +1,33 @@
// base tree
.tree-node, .tree-children, .tree-container-ul { display:block; margin:0; padding:0; list-style-type:none; list-style-image:none; }
.tree-children {overflow: hidden; }
.tree-node { white-space:nowrap; }
.tree-anchor { display:inline-block; color:black; white-space:nowrap; padding:0 4px 0 1px; margin:0; vertical-align:top; font-size: 14px; cursor: pointer;}
.tree-anchor:focus { outline:0; }
.tree-anchor, .tree-anchor:link, .tree-anchor:visited, .tree-anchor:hover, .tree-anchor:active { text-decoration:none; color:inherit; }
.tree-icon { display:inline-block; text-decoration:none; margin:0; padding:0; vertical-align:top; text-align:center; }
.tree-icon:empty { display:inline-block; text-decoration:none; margin:0; padding:0; vertical-align:top; text-align:center; }
.tree-ocl { cursor:pointer; }
.tree-leaf > .tree-ocl { cursor:default; }
.tree-anchor > .tree-themeicon { margin-right:2px; }
.tree-no-icons .tree-themeicon,
.tree-anchor > .tree-themeicon-hidden { display:none; }
.tree-hidden, .tree-node.tree-hidden { display:none; }
// base tree rtl
.tree-rtl {
.tree-anchor { padding:0 1px 0 4px; }
.tree-anchor > .tree-themeicon { margin-left:2px; margin-right:0; }
.tree-node { margin-left:0; }
.tree-container-ul > .tree-node { margin-right:0; }
}
// base tree wholerow
.tree-wholerow-ul {
position:relative;
display:inline-block;
min-width:100%;
.tree-leaf > .tree-ocl { cursor:pointer; }
.tree-anchor, .tree-icon { position:relative; }
.tree-wholerow { width:100%; cursor:pointer; z-index: -1; position:absolute; left:0; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none; }
}
@@ -0,0 +1,60 @@
.tree {
text-align: left;
}
.tree-@{theme-name} {
.tree-node,
.tree-icon { background-repeat:no-repeat; background-color:transparent; }
.tree-anchor,
.tree-animated,
.tree-wholerow { transition:background-color 0.15s, box-shadow 0.15s; }
.tree-hovered { background:@hovered-bg-color; border: 0px; box-shadow:none; }
.tree-context { background:@hovered-bg-color; border: 0px; box-shadow:none; }
.tree-selected { background:@clicked-bg-color; border: 0px; box-shadow:none; }
.tree-no-icons .tree-anchor > .tree-themeicon { display:none; }
.tree-disabled {
background:transparent; color:@disabled-color;
&.tree-hovered { background:transparent; box-shadow:none; }
&.tree-selected { background:@disabled-bg-color; }
> .tree-icon { opacity:0.8; filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'tree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#tree-grayscale"); /* Firefox 10+ */ filter: gray; /* IE6-9 */ -webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */ }
}
// search
.tree-search { font-style:italic; color:@search-result-color; font-weight:bold; }
// checkboxes
.tree-no-checkboxes .tree-checkbox { display:none !important; }
&.tree-checkbox-no-clicked {
.tree-selected {
background:transparent;
box-shadow:none;
&.tree-hovered { background:@hovered-bg-color; }
}
> .tree-wholerow-ul .tree-wholerow-clicked {
background:transparent;
&.tree-wholerow-hovered { background:@hovered-bg-color; }
}
}
// stripes
> .tree-striped { min-width:100%; display:inline-block; background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat; }
// wholerow
> .tree-wholerow-ul .tree-hovered,
> .tree-wholerow-ul .tree-selected { background:transparent; box-shadow:none; border-radius:0; }
.tree-wholerow { -moz-box-sizing:border-box; -webkit-box-sizing:border-box; box-sizing:border-box; }
.tree-wholerow-hovered { background:@hovered-bg-color; }
.tree-wholerow-clicked { background:@clicked-bg-color; }
}
// theme variants
.tree-@{theme-name} {
.tree-theme(24px, "data:image/png;base64,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", 32px);
&.tree-rtl .tree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); }
&.tree-rtl .tree-last { background:transparent; }
}
.tree-@{theme-name}-small {
.tree-theme(18px, "data:image/png;base64,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", 32px);
&.tree-rtl .tree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg=="); }
&.tree-rtl .tree-last { background:transparent; }
}
.tree-@{theme-name}-large {
.tree-theme(32px, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAABgCAYAAABsS6soAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACbBJREFUeNrsnX+IHFcBx9/szaZerrU/rmpCcmmLAVuFinjFhFLECmf+EEVrWlNThNo/iiBEaJSribW1EjSSNEY9/6iKxBZK8Q+10nSxAckfScmJtZAm1fzRJA2pUm1yOe9wd3bH9+Zuzunc3GV3frw3O+/zCY+5mbvL25l9893Pe/NuxvF9X4AQjuP0/DsjI2v8s2fPOaZecxXrpz2Wu82XCdX+zpx5I9NOODS4ajSGqkB7pM3rbC81mkG2TyDqhyp9+OgsZWh/GCAGiAHS5o0c+zzOOQwQA8MAgfMPA8QAMUDAADFADAwDBM4/DBADxAABA8QAMTAMEDj/MEAMsB8NsNFopG6kY2Njmd/YqtaPAQrhdvuDo6OjPdc0OTmZW6oUXX+aA7lu3drMM9E59l2fyGmCg/ozcupP96yXi+/L8vnYt34ny/j6jz/zqmEDzNTO3KIadZqTpuz1x9ERfpF97yWwKlc/GONpWW5L2P4ZWW6U5cOmXlgef4bp8v6mR5cBQnnoxqrS2FqJuW2Z790qDXGTtMCDVhjgUqaVd3enXzAdfsq0ejEzqD7j4+Pndu3atUZjlc/LEIwHkSfL72V5UIbjP8tsgFwFzmiAHAUoUfj9Wy7WGKjaSRCrz6kA1GCABKCtBggQCb9pubiuZC/r0SL/c2NjgPELDNF1Hd3h5S5w6OyOh2OAaZdpu71Lrce7wzrqjx37wusn6MaDpezmRrfNysWV8e1Vx9gYYDRk5sPISToBiiIp5EyMh4Uncdplyn3vep+Lrj/r8YFsQajCTi6bcnXQtvDLywDpAgP0ETHzU4sVNoZfxADNB6CyAtNXI7kaCjaGoK3hVxoDtHUKTBkg9AlBW8MvLwNkIjRAD5RpkrPN4ZeXAfYUgDr+vKzM9Rved6vrB2O8IcvalL+7Q4MB6rkKbLqra3NXm2Of740FqL8n7pflJ7IMX+bnrp1fvj2/VH8e97OyGyC3wwKwlH6/HRbPBQYAawMwj9fMPEAA6Eu4IzQAYIAYIABggBggAGg0MNNggACAAWKAAIABYoBaMX1XaNvrBwwQAwQADBADxMAwQMAAMUAAwAAxQAwMAwQMEAMEAAwQA8TAMEDAADFAAAwQAwQMDAMEDBCglDQaDU/M3fo8LJcVg7CMjY25OdSf+iSR9TtlrT8vA3zoG9/yRz9yqzj599fFqdPnxfobVgfbjx9/RTz7zFOFambW/Oq6caR5Hkeet1I3Xf9yBqTjQd+WH39H1t9LbyUISll/J8f9Fyn2X1Sl/uVY//4R8dwfjwTB981tD4jHHp97WNMVV75HhwHqeSZIrw26iAcYma4/CR3hl6ZBF/EQI4P1O5p/DxLY+pVtiefUi4cOz3+1emFbGH7qd3798ycKeR+0PxUOzBmg5ZQmALv5EKjyE/QOPLl30ba254kLF6fEj3/6pDh16uqFn7v7i1sLtcA8DDDzRRCbH1Wp2wBtO9mgdyYmJgqvY+Bjv31HWXH7H8Tw8HXa9zUPA+QqcA4GyFGAMrB//34xPT1tzf7mcRWYLnCfGyCAYt++faLZbFq1z8bGAOPd3ui6jiuvy3W709YfjuWlXeZhkt3+//Fub3Q97ZW/LPXHjj9pVCC7d+8Oltu3b1/YtnfvXuF53qLtlhigvqvASSEzH0aOzsafFHLqpMxSf3iSp13mZZLd/P/R/cy631nrB3NBqMJuz549ot1uWxd+Rg0QAMygQi60wHCpO/zaL302cfv09JQdBlhGI8BKwMYQ1Bl+8/P5lhx+WjU8FCz/+pdj4r4Hvh5MgQm3VdYAdYz5AcEPySGou9v73R3bFm3b+fgTQdDdvvHBYH3Llh+JV08eEJ+88w7xhbs2ix/+4HvVNkCAgknb0HOfolSmeZcmxvxuuunGRe/D5nu+5A+t3SRefvm4+M/sYTE0eIf44M33iRcPHRDXvPva6hig6UnPtk+6Nn3yGay/NAEIi9n6tS3i2V8ejHSFD4vTp18PusAXpt4urN48DJC7wUDp4W4w5b4bjNGuQcb8IgABLKXfA1AZYNZpaAQgAAForQHyt8AA0LcGmPlDAAMEwAAxQAAADBAAMEAMEAAAAwQADBADBADAAAEAA8QAAQAwQADAADFAAAAMEAAwQAwQAAADBAAMEAMEAMAA+4+64wxG11u+P6ut7qGB9z780I6vJn2v0Wj87ciRI0/zDgEGWNEA/O8ttwQv9ooTJ5wsP5Mp/FbWrvJmOteodXdl7YKY6VzSFYIbN268V4bcU/HtszOz4uLURTExMfGo5Duc2mBDAOZxR+hCusCjv3F8VYoOwm6354kKv7fe+tdrqohm7UOefB/iVqiTtueJmdlZIV+PkOH3iIQABCvI46lwuQdgkcEXtbp42EXXi7C/AGl/ruuuUV++8OeD4s03/3FIrZsMwZrrivoKN3hS0NSlKfHtnTsJQbCCPMYAcw3AaPhN3uUXEkJJIagl/GL86rlflCIE1c4OvWulGLlhRJw5c1acOPma2Lz57kfq9fonOEUAA1ye3B6MriP8oiFnMvyiIahQIbhq1fvuFCs6F2QIFn1hZNGzUJUFrhwcFCPrRoTX9AIjHB0dXc0pAhYYYKZzvusADAMuKdx0hl9SCJoIv6QQvP764Q/IA3qpMNtbYtRabR1w6+Lqq+rBmCCPA4dIm8EA8+wCx8f4TIRf3PyS1ivanH3R8TtLdYUVA9IGVQGwxAD1BGA03MLQK0P4LXdhRAdf/vT94lMf3SRUFziYFlMgbn1AHXA/7AeHZe4Lf3EaAlQY7QaYFIJlCD9TIRgNP8/zzhU9J9CR/3yVdNIB/XZb+LK7q0qn0xbtdmeu+zsHnWDAAIvoAsfDrgzmZyIE4+EnO51niwy/DRs2BJPWZcj57Y4X9ITbal0Wv6O8cK5nrELQxwEBAywmAKOhpzP8lgq7brbnhjS8wPQkOsNPcfTo0SDUW03PaTZbotXyhKxbeHLZbLVkkUu5XZWODElOD8AAL0/q0XIT4ddNwBUdgmqcT13tDb92PaHtT+GkAfqtTqtWi39uOfMCGI4QNjFAwAALDUAbUUGn5vktTHWZ6Wi9GUK73Vbz/ZyOGgOUiefLLxynFkx1GKjXAp9XLUJNiQGwxAD1zAOE/4egqbqPHTt2/uEdj6nAU77nB3O81BhgcFMLNR3QXxj/m5ycPM+7BRjg8nA7rPBA9MGEUflpFdyNpu66ouV5wnVdZ67/Oxd7XnTsT41XCjHLOwtVNsCsd4MhAPsoAAHgnXBHaACw1gDpAmOAABggBggAGCAGiAECYIAYIABggBggAEAC/xNgABT+eKeUWyLUAAAAAElFTkSuQmCC", 32px);
&.tree-rtl .tree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg=="); }
&.tree-rtl .tree-last { background:transparent; }
}
@@ -0,0 +1,91 @@
.tree-theme (@base-height, @image, @image-height) {
@correction: (@image-height - @base-height) / 2;
.tree-node { min-height:@base-height; line-height:@base-height; margin-left:@base-height + 6; min-width:@base-height; }
.tree-anchor { line-height:@base-height; height:@base-height; }
.tree-icon { width:@base-height; height:@base-height; line-height:@base-height; }
.tree-icon:empty { width:@base-height; height:@base-height; line-height:@base-height; }
&.tree-rtl .tree-node { margin-right:@base-height; }
.tree-wholerow { height:@base-height; }
.tree-node,
.tree-icon { background-image:url("@{image}"); }
.tree-node { background-position:-(@image-height * 9 + @correction) -(@correction); background-repeat:repeat-y; }
.tree-last { background:transparent; }
.tree-open > .tree-ocl { background-position:-(@image-height * 4 + @correction) -(@correction); }
.tree-closed > .tree-ocl { background-position:-(@image-height * 3 + @correction) -(@correction); }
.tree-leaf > .tree-ocl { background-position:-(@image-height * 2 + @correction) -(@correction); }
.tree-themeicon { background-position:-(@image-height * 8 + @correction) -(@correction); }
> .tree-no-dots {
.tree-node,
.tree-leaf > .tree-ocl { background:transparent; }
.tree-open > .tree-ocl { background-position:-(@image-height * 1 + @correction) -(@correction); }
.tree-closed > .tree-ocl { background-position:-(@correction) -(@correction); }
}
.tree-disabled {
background:transparent;
&.tree-hovered {
background:transparent;
}
&.tree-selected {
background:#efefef;
}
}
.tree-checkbox {
background-position:-(@image-height * 5 + @correction) -(@correction);
&:hover { background-position:-(@image-height * 5 + @correction) -(@image-height * 1 + @correction); }
}
&.tree-checkbox-selection .tree-selected, .tree-checked {
> .tree-checkbox {
background-position:-(@image-height * 7 + @correction) -(@correction);
&:hover { background-position:-(@image-height * 7 + @correction) -(@image-height * 1 + @correction); }
}
}
.tree-anchor {
> .tree-undetermined {
background-position:-(@image-height * 6 + @correction) -(@correction);
&:hover {
background-position:-(@image-height * 6 + @correction) -(@image-height * 1 + @correction);
}
}
}
.tree-checkbox-disabled { opacity:0.8; filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'tree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#tree-grayscale"); /* Firefox 10+ */ filter: gray; /* IE6-9 */ -webkit-filter: grayscale(100%); /* Chrome 19+ & Safari 6+ */ }
> .tree-striped { background-size:auto (@base-height * 2); }
&.tree-rtl {
.tree-node { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg=="); background-position: 100% 1px; background-repeat:repeat-y; }
.tree-last { background:transparent; }
.tree-open > .tree-ocl { background-position:-(@image-height * 4 + @correction) -(@image-height * 1 + @correction); }
.tree-closed > .tree-ocl { background-position:-(@image-height * 3 + @correction) -(@image-height * 1 + @correction); }
.tree-leaf > .tree-ocl { background-position:-(@image-height * 2 + @correction) -(@image-height * 1 + @correction); }
> .tree-no-dots {
.tree-node,
.tree-leaf > .tree-ocl { background:transparent; }
.tree-open > .tree-ocl { background-position:-(@image-height * 1 + @correction) -(@image-height * 1 + @correction); }
.tree-closed > .tree-ocl { background-position:-(@correction) -(@image-height * 1 + @correction); }
}
}
.tree-themeicon-custom { background-color:transparent; background-image:none; background-position:0 0; }
.tree-node.tree-loading{background: none;}
> .tree-container-ul .tree-loading > .tree-ocl { background:url("data:image/gif;base64,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") center center no-repeat; }
.tree-file { background:url("@{image}") -(@image-height * 3 + @correction) -(@image-height * 2 + @correction) no-repeat; }
.tree-folder { background:url("@{image}") -(@image-height * 8 + @correction) -(@correction) no-repeat; }
> .tree-container-ul > .tree-node { margin-left:0; margin-right:0; }
// ellipsis
.tree-ellipsis { overflow: hidden; }
// base height + PADDINGS!
.tree-ellipsis .tree-anchor { width: calc(100% ~"-" - ( @base-height + 5px) ); text-overflow: ellipsis; overflow: hidden; }
.tree-ellipsis.tree-no-icons .tree-anchor { width: calc(100% ~"-" 5px); }
}
@@ -0,0 +1,20 @@
/* tree default theme */
@theme-name: default;
@hovered-bg-color: #eee;
@hovered-shadow-color: #cccccc;
@disabled-color: #666666;
@disabled-bg-color: #efefef;
@clicked-bg-color: #e1e1e1;
@clicked-shadow-color: #999999;
@search-result-color: #8b0000;
@mobile-wholerow-bg-color: #ebebeb;
@mobile-wholerow-shadow: #666666;
@mobile-wholerow-bordert: rgba(255,255,255,0.7);
@mobile-wholerow-borderb: rgba(64,64,64,0.2);
@responsive: true;
@image-path: "./";
@base-height: 40px;
@import "./mixins.less";
@import "./base.less";
@import "./main.less";
@@ -0,0 +1,132 @@
<!--
Program: Change Password Popup
Author: SDL
Brief: Password Change Popup
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content" style="height:450px;">
<div class="modal-header">
<h5 class="modal-title">{{ $t('sdl.identity.label.pwdChange') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<form @submit.prevent>
<table class="table table-bordered ui--table-form ui--table-form-view">
<colgroup>
<col style="width:30%" />
<col style="width:70%" />
</colgroup>
<tbody>
<tr>
<th class="text-nowrap" scope="row">ID</th>
<td><input type="text" class="form-control" readonly v-model="userInfo.loginId" /></td>
</tr>
<tr v-if="!this.isPasswordNull">
<th class="text-nowrap" scope="row">
{{ $t('sdl.identity.label.oldPwd') }}<span class="ui--text-required">*</span>
</th>
<td><input type="password" class="form-control" v-model="userInfo.oldPwd" v-validation="{ groupId: 'changePassword'}" errorMessage="sdl.identity.message.emptyOldPasswd" />
</td>
</tr>
<tr>
<th class="text-nowrap" scope="row">
{{ $t('sdl.identity.label.newPwd') }}<span class="ui--text-required">*</span>
</th>
<td>
<input type="password" class="form-control" v-model="userInfo.pwd" v-validation="{groupId: 'changePassword'}" errorMessage="sdl.identity.message.emptyPasswd" /></td>
</tr>
<tr>
<th class="text-nowrap" scope="row">
{{ $t('sdl.identity.label.confirmPwd') }}<span class="ui--text-required">*</span>
</th>
<td><input type="password" class="form-control" v-model="userInfo.pwd2" v-validation="{groupId: 'changePassword'}" errorMessage="sdl.identity.message.emptyConfirmPasswd" /></td>
</tr>
</tbody>
</table>
{{ $t('sdl.identity.label.policy') }}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="confirm()">{{ $t('sdl.identity.label.change') }}</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
props: {},
data() {
return {
userInfo: {},
isPasswordNull: false,
};
},
methods: {
confirm() {
// validation
if (SDLUtil.onSubmitValidation()) return;
if (this.userInfo.pwd !== this.userInfo.pwd2) {
SDLUtil.alert('sdl.identity.message.invalidPassword.notMatch');
return;
}
SDLUtil.showLoadingBar(true);
const formData = new FormData();
formData.append('id', this.userInfo.loginId);
formData.append('password1', this.userInfo.pwd);
// 비밀번호 체크
axios
.post(`${SDLUtil.API_URL}/auth/noauth/passwd/rule-check`, formData)
.then(() => {
this.passwordChange();
})
.catch(err => {
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(err);
});
},
passwordChange() {
const params = {
...this.userInfo,
};
axios
.put(`${SDLUtil.API_URL}/auth/user/passwd`, params)
.then(() => {
SDLUtil.showLoadingBar(false);
SDLUtil.alert({
msg: 'sdl.identity.message.passwdChangeSuccess',
onOkEvt: () => {
this.$emit('close');
},
});
})
.catch(err => {
console.error(err);
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(err);
});
},
},
created() {
this.userInfo.loginId = SDLUtil.getLoginedUserInfo().knoxId;
axios
.get(`${SDLUtil.API_URL}/auth/user/password/init-check`)
.then(({ data }) => {
this.isPasswordNull = data;
});
},
};
</script>
@@ -0,0 +1,205 @@
<!--
Program: DeptListPopup.vue
Author: SDL
Description: popup page where you can view and select a list of department
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:744px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.department.label.departmentList') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- menu tree -->
<div class="ui--box-tree">
<sdl-tree
ref="tree"
class="ui--tree"
:data="treeData"
:showCheckbox="true"
:multiple="true"
:allowBatch="true"
text-field-name="name"
whole-row
@item-toggle="treeItemToggle"
drag-over-background-color="#e9ecef"
/>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="rtnData">{{ $t('sdl.common.label.confirm') }}</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import _ from 'lodash';
import SDLUtil from '@/utils/SDLUtil';
export default {
data() {
return {
treeData: [],
selectedDeptList: [], // List of selected departments (including sub-departments)
};
},
props: {
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Query department information to create a tree structure(0,1 Depth Department)
*/
getTopDepthTreeDeptList() {
/**
* Create a tree structure after invoking an API that retrieves 0,1 depth Knox department information
*/
return axios.get(`${SDLUtil.API_URL}/admin/knox-department`).then(({ data }) => {
// Create Tree Structure
if (data.length !== 0) {
this.makeTreeData_TopDepth(data);
}
});
},
/**
* param: object
* return: nothing
* exception: nothing
* description: Use the toggle trigger to prevent calls multiple times and call once
*/
// eslint-disable-next-line func-names
debouncedItemToggle: _.debounce(function (model) {
if (model.existSubDepartment) {
SDLUtil.showLoadingBar(true);
// Query Knox department, and delete loading bar
this.getBottomDepthTreeDeptList(model).then(() => SDLUtil.showLoadingBar(false));
}
}, 10),
/**
* param: object
* return: nothing
* exception: nothing
* description: The toggle function is performed in the tree structure.
*/
treeItemToggle(oriNode, oriItem) {
this.debouncedItemToggle(oriItem);
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Query department information to create a tree structure(2 Depth Department)
*/
getBottomDepthTreeDeptList(deptObj) {
// Bottem depth Knox department
return axios.get(`${SDLUtil.API_URL}/admin/knox-department/${deptObj.deptCode}`).then(({ data }) => {
// Create Tree Structure
this.makeTreeData_BottomDepth(deptObj, data);
});
},
/**
* param: array
* return: nothing
* exception: nothing
* description: Create Tree Structure(0,1 Depth Department)
*/
makeTreeData_TopDepth(data) {
const depts = data.map(dept => ({
...dept,
id: dept.deptCode,
name: dept.deptName,
}));
// Find Top Menu
const root = depts.find(dept => dept.deptLevel === 0);
root.opened = true;
root.dragDisabled = true;
root.name = 'Knox Department';
// Top & 1 Depth Department Settings
let children = depts.filter(dept => dept.upperDeptCode === root.deptCode);
children = _.sortBy(children, ['deptSequence']);
children.forEach(d => {
d.dragDisabled = true;
d.isFolder = d.existSubDepartment;
});
root.children = children;
this.treeData = [root];
this.$refs.tree.initializeData(this.treeData);
},
/**
* param: string, array
* return: nothing
* exception: nothing
* description: Create Tree Structure(Bottom Depth Department)
*/
makeTreeData_BottomDepth(deptObj, data) {
const { deptCode } = deptObj;
const checkYn = deptObj.selected;
let childDepts = data.map(dept => ({
...dept,
id: dept.deptCode,
name: dept.deptName,
}));
childDepts = _.sortBy(childDepts, ['deptSequence']);
childDepts.forEach(d => {
d.dragDisabled = true;
d.opened = false;
d.children = [];
d.selected = checkYn;
d.isFolder = d.existSubDepartment;
});
const addChild = (treeData, parantCode) => {
treeData.forEach(treeItem => {
if (treeItem.deptCode === deptCode) {
treeItem.children = childDepts;
} else {
addChild(treeItem.children, parantCode);
}
});
};
addChild(this.treeData, deptCode);
this.$refs.tree.initializeData(this.treeData);
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Forward selected list to call page via function
*/
rtnData() {
this.$refs.tree.handleRecursionNodeChilds(this.$refs.tree, nodeItem => {
if (nodeItem.model && nodeItem.model.selected) {
this.selectedDeptList.push(nodeItem.model);
}
});
this.rtnFunc(this.selectedDeptList);
this.$emit('close');
},
},
created() {
// Launch Loading bar
SDLUtil.showLoadingBar(true);
// Call up department list lookup and tree structure generation functions
this.getTopDepthTreeDeptList().then(() => SDLUtil.showLoadingBar(false));
},
};
</script>
<style></style>
@@ -0,0 +1,233 @@
<!--
Program: Entrust approval
Author: SDL
Brief: Entrust approval popup
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height: 530px">
<div class="modal-header">
<h5 class="modal-title">{{ $t('sdl.approval.label.delegateApproval') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- 대리결재자 정보 -->
<div class="mb-4 alert alert-info" v-if="curDelegatorUser.delegatorId">
<h6>
<strong>{{ $t('sdl.approval.label.delegateApprovalUser') }}</strong>
<button class="float-end ui--btn-icon-sm btn-close" :title="$t('sdl.approval.label.delDelegateApprovalUser')" @click="onBeforeDelete"></button>
</h6>
{{ curDelegatorUser.delegatorName }}/{{ curDelegatorUser.delegatorPositionName }}/{{ curDelegatorUser.delegatorDeptName }}/{{ curDelegatorUser.delegatorCompanyName }}<br />
{{ $t('sdl.common.label.period') }} : {{ $filters.dateFormat(curDelegatorUser.startDate) }} ~
{{ $filters.dateFormat(curDelegatorUser.endDate) }}
</div>
<!-- //대리결재자 정보 -->
<h5 class="mb-3">{{ $t('sdl.approval.label.addDelegateApprovalUser') }}</h5>
<table class="table table-bordered ui--table-form ui--table-form-view">
<colgroup>
<col style="width: 20%" />
<col style="width: 80%" />
</colgroup>
<tbody>
<tr>
<th class="text-nowrap" scope="row">{{ $t('sdl.approval.label.approver') }}<span class="ui--text-required">*</span></th>
<td>
<div class="input-group input-date-in">
<input
type="text"
class="form-control"
:placeholder="$t('sdl.user.label.userName')"
v-model="delegatorUser.userName"
@keyup.esc="delegatorUser.userName = ''"
@keyup.enter.stop="openSearchUser"
v-validation=""
errorMessage="sdl.samsung.validate.required"
/>
<button class="btn btn-outline-secondary" type="button" @click="openSearchUser">
<i class="bi bi-search"></i>
</button>
</div>
</td>
</tr>
<tr>
<th class="text-nowrap" scope="row">{{ $t('sdl.common.label.period') }}<span class="ui--text-required">*</span></th>
<td>
<sdl-datepicker v-model="searchPeriod" :period="searchPeriod" :wrap-class="'input-date-in'"></sdl-datepicker>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="delegateApproval">
{{ $t('sdl.common.label.save') }}
</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
import DateUtil from '@/utils/DateUtil';
export default {
props: {},
data() {
return {
delegatorUser: {},
searchPeriod: {},
curDelegatorUser: {},
};
},
methods: {
openSearchUser() {
if (this.delegatorUser.userName) {
if (this.delegatorUser.userName.length < 2) {
SDLUtil.alert('sdl.common.message.enterMoreThan2CharForSearchTerms');
return;
}
}
SDLUtil.openUserPopup({
searchColumn: 'userName',
searchTxt: this.delegatorUser.userName,
singleMode: true,
rtnFunc: this.getUserList,
knoxSearch: true,
});
},
// user search
getUserList(list) {
if (list.length > 0) {
// eslint-disable-next-line prefer-destructuring
this.delegatorUser = list[0];
}
},
/**
* param:
* return:
* exception:
* Description : event before Delete
*/
onBeforeDelete() {
SDLUtil.confirm({
msg: 'sdl.common.message.doDelete',
title: 'sdl.common.label.delete',
onOkEvt: () => {
this.onDelete();
},
});
},
/**
* param:
* return:
* exception:
* Description : delete
*/
onDelete() {
SDLUtil.showLoadingBar(true);
axios
.delete(`${SDLUtil.API_URL}/approval/approver-delegate`)
.then(() => {
// hide Loading bar
SDLUtil.showLoadingBar(false);
this.curDelegatorUser = {};
})
.catch(error => {
SDLUtil.showLoadingBar(false);
console.log(error);
SDLUtil.errorAlert(error);
});
},
/**
* param:
* return:
* exception:
* Description : event before Saving
*/
delegateApproval() {
if (SDLUtil.onSubmitValidation()) return;
SDLUtil.confirm({
msg: 'sdl.common.message.doSave',
title: 'sdl.common.label.save',
onOkEvt: () => {
this.onSave();
},
});
},
/**
* param:
* return:
* exception:
* Description : save
*/
onSave() {
SDLUtil.showLoadingBar(true);
const param = {
delegatorId: this.delegatorUser.userId,
endDate: DateUtil.stdFormat(this.searchPeriod.endDate),
startDate: DateUtil.stdFormat(this.searchPeriod.startDate),
userId: SDLUtil.getLoginedUserInfo().userId,
};
axios
.post(`${SDLUtil.API_URL}/approval/approver-delegate`, param)
.then(() => {
// hide Loading bar
SDLUtil.showLoadingBar(false);
this.$emit('close');
})
.catch(error => {
console.log(error);
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(error);
});
},
/**
* param:
* return:
* exception:
* Description : get Delegator Approval
*/
getDelegatorApproval() {
SDLUtil.showLoadingBar(true);
axios
.get(`${SDLUtil.API_URL}/approval/approver-delegate`)
.then(response => {
// hide Loading bar
SDLUtil.showLoadingBar(false);
this.curDelegatorUser = response.data;
})
.catch(error => {
SDLUtil.showLoadingBar(false);
SDLUtil.errorAlert(error);
console.log(error);
});
},
},
created() {
this.getDelegatorApproval();
this.searchPeriod.startDate = DateUtil.now();
this.searchPeriod.endDate = DateUtil.addDate(1, 'w');
},
};
</script>
<style scoped>
.vdp-datepicker__calendar {
position: fixed !important;
z-index: 100;
background: #fff;
width: 300px;
border: 1px solid #ccc;
}
.v--modal-overlay .v--modal-box {
overflow: visible;
}
.modal-dialog-scrollable .modal-content {
overflow: visible;
}
</style>
@@ -0,0 +1,201 @@
<!--
Program: MenuListPopup.vue
Author: SDL
Description: a popup page where you can view and select a menu list.
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:744px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.menu.label.menuList') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- menu tree -->
<div class="ui--box-tree">
<sdl-tree
ref="tree"
class="ui--tree"
:data="treeData"
:showCheckbox="true"
:multiple="true"
:allowBatch="true"
text-field-name="localeLabel"
whole-row
drag-over-background-color="#e9ecef"
/>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="rtnData">{{ $t('sdl.common.label.confirm') }}</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import _ from 'lodash';
import SDLUtil from '@/utils/SDLUtil';
import SDLLocale from '@/mixin/SDLLocale';
export default {
data() {
return {
treeData: [],
selectedMenuList: [],
menuFullPath: '',
};
},
mixins: [SDLLocale],
props: {
userId: {
required: false,
type: String,
},
workgroupId: {
required: false,
type: String,
},
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: nothing
* return: nothing
* exception: nothing
* description: To query and create a menu list as a tree structure
*/
getTreeMenuService() {
let ApiUrl = '';
// Set user-menu lookup API
if (this.userId !== null && this.userId !== undefined) {
ApiUrl = `${SDLUtil.API_URL}/auth/menus/${this.userId}/user-menu-popup`;
}
// Set task group-menu lookup API
if (this.workgroupId !== null && this.workgroupId !== undefined) {
ApiUrl = `${SDLUtil.API_URL}/auth/menus/${this.workgroupId}/workgroup-menu-popup`;
}
// Call up menu lookup API and create as a tree structure
return axios.get(ApiUrl).then(({ data }) => {
this.makeTreeData(data);
});
},
/**
* param: array
* return: nothing
* exception: nothing
* description: Create as a Tree Structure
*/
makeTreeData(data) {
const menus = data.map(menu => ({
...menu,
id: menu.menuId,
value: menu,
}));
// Set Locale Label
menus.map(m => {
m.localeLabel = this.getLabel(m);
return m;
});
/**
* Find Top-Level
*/
const root = menus.find(menu => menu.menuLevel === 0);
root.opened = true;
root.selected = false;
root.disabled = true;
root.dragDisabled = true;
this.treeData.push(root);
/**
* Set the submenu of the top menu to Tree structure, then return
*/
const getChildren = parentId => {
let children = menus.filter(menu => menu.parentId === parentId);
children = _.sortBy(children, ['menuSequence']);
children.forEach(m => {
m.dragDisabled = true;
m.selected = false;
// Check if the menu is registered and disable
if (m.checked) {
m.disabled = true;
} else {
m.disabled = false;
}
// Check if child nodes exist, call recursive function
if (_.find(menus, ['parentId', m.menuId])) {
m.opened = false;
m.children = getChildren(m.menuId);
} else {
// m.disabled = false;
}
});
return children;
};
// Invoke the recursive function of the submenu of the top menu to apply the returned value
root.children = getChildren(root.menuId);
// Apply menu objects set to Tree structure
this.treeData = [root];
this.$refs.tree.initializeData(this.treeData);
},
/**
* param: string
* return: nothing
* exception: nothing
* description: Save the Menu Full Path for the specified menu id
*/
getMenuFullPathTreeItemById(treeId) {
this.$refs.tree.handleRecursionNodeChilds(this.$refs.tree, node => {
if (node.model && node.model.id === treeId) {
const parentId = node.model.parentId || node.model.parentId;
if (parentId) {
this.menuFullPath = `${node.model.label} > ${this.menuFullPath}`;
this.getMenuFullPathTreeItemById(parentId);
}
}
});
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Forward selected list to call page via function
*/
rtnData() {
// Save the selected Menu in object format to the temporary storage array
this.$refs.tree.handleRecursionNodeChilds(this.$refs.tree, nodeItem => {
if (nodeItem.model && nodeItem.model.selected) {
this.selectedMenuList.push(nodeItem.model);
}
});
// Save menu full path in temporary storage array
this.selectedMenuList.forEach(selectItem => {
this.menuFullPath = selectItem.label;
this.getMenuFullPathTreeItemById(selectItem.parentId);
selectItem.menuFullPath = this.menuFullPath;
});
// Call up the popup screen by applying a list to pass on the function of the page.
this.rtnFunc(this.selectedMenuList);
this.$emit('close');
},
},
created() {
// Launch Loading bar
SDLUtil.showLoadingBar(true);
// Call up menu list lookup and tree structure generation functions
this.getTreeMenuService().then(() => SDLUtil.showLoadingBar(false));
},
};
</script>
<style></style>
@@ -0,0 +1,186 @@
<!--
Program: RoleFormPopup.vue
Author: SDL
Description: popup page for registering and modifying role information
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:404px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.role.label.roleReg') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<table class="table table-bordered ui--table-form ui--table-form-view mb-3">
<colgroup>
<col style="width:30%" />
<col style="width:70%" />
</colgroup>
<tbody>
<tr>
<th scope="row">{{ $t('sdl.role.label.heading') }} <span class="ui--text-required">&#42;</span></th>
<td>
<input type="text" class="form-control" v-model.trim="roleItem.roleName" v-validation="" errorMessage="sdl.samsung.validate.required" />
</td>
</tr>
<tr>
<th scope="row">{{ $t('sdl.role.label.roleNameOt') }} <span class="ui--text-required">&#42;</span></th>
<td>
<input type="text" class="form-control" v-model.trim="roleItem.label" v-validation="" errorMessage="sdl.samsung.validate.required" />
</td>
</tr>
<tr v-for="locale in $i18n.availableLocales" :key="locale">
<th scope="row">{{ $t('sdl.role.label.roleName') }}({{ locale }})</th>
<td>
<input type="text" class="form-control" v-model.trim="roleItem.labelJson[locale]" />
</td>
</tr>
<tr>
<th scope="row">{{ $t('sdl.commonCode.label.description') }}</th>
<td>
<input type="text" class="form-control" v-model.trim="roleItem.description" />
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="saveRole">{{ $t('sdl.common.label.save') }}</button>
</div>
<!-- // contents -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
data() {
return {
roleItem: {
labelJson: {},
},
insertStatus: true,
};
},
props: {
roleId: {
required: false,
type: String,
},
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: nothing
* return: nothing
* exception: Call error alert
* description: Query role information
*/
getRoleInfo() {
if (!this.insertStatus) {
// Launch Loading bar
SDLUtil.showLoadingBar(true);
/**
* Call role details lookup API
*/
axios
.get(`${SDLUtil.API_URL}/auth/roles/${this.roleId}`)
.then(({ data }) => {
this.roleItem = data;
// Remove Loading bar
SDLUtil.showLoadingBar(false);
})
.catch(err => {
// Remove Loading bar
SDLUtil.showLoadingBar(false);
// Call error alert
SDLUtil.errorAlert(err);
});
}
},
/**
* param: nothing
* return: nothing
* exception: Call error alert
* description: Save Role Information
*/
async saveRole() {
let chkFlag = false;
const params = {
...this.roleItem,
};
// validation
if (SDLUtil.onSubmitValidation()) return;
if (this.insertStatus) {
/**
* Call Role Registration API
*/
await axios
.post(`${SDLUtil.API_URL}/auth/roles`, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(response => {
if (response.status === 200) {
chkFlag = true;
}
})
.catch(err => {
SDLUtil.errorAlert(err);
});
} else {
/**
* Call role modification API
*/
await axios
.put(`${SDLUtil.API_URL}/auth/roles/${this.roleId}`, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(response => {
if (response.status === 200) {
chkFlag = true;
}
})
.catch(err => {
SDLUtil.errorAlert(err);
});
}
this.rtnFunc(chkFlag);
this.$emit('close');
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Initialization
*/
init() {
this.roleItem = {
labelJson: {},
};
},
},
created() {
if (this.roleId !== '' && this.roleId !== undefined) {
this.insertStatus = false;
this.getRoleInfo();
} else {
this.init();
}
},
};
</script>
@@ -0,0 +1,268 @@
<!--
Program: RoleListPopup.vue
Author: SDL
Description: popup page where you can view and select a list of roles
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:744px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.role.label.heading') }} {{ $t('sdl.common.label.search') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- SEARCH Area -->
<div class="ui--search-container clearfix">
<div class="col-11 float-start">
<div class="row">
<div class="col">
<div class="row">
<label class="col-4 col-form-label text-end">{{
$t('sdl.role.label.heading')
}}</label>
<div class="col-8">
<input
type="text"
class="form-control"
v-model="searchParam.sesearchRoleName"
@keyup.enter="getRoleList()"
@keyup.esc="searchParam.sesearchRoleName = ''"
/>
</div>
</div>
</div>
<div class="col">
<div class="row">
<label class="col-4 col-form-label text-end">{{
$t('sdl.role.label.roleName')
}}</label>
<div class="col-8">
<input
type="text"
class="form-control"
v-model="searchParam.searchLabel"
@keyup.enter="getRoleList()"
@keyup.esc="searchParam.searchLabel = ''"
/>
</div>
</div>
</div>
</div>
</div>
<!-- search button -->
<div class="col-1 float-end text-end">
<button class="btn btn-primary btn-search" type="submit" @click="getRoleList()">
{{ $t('sdl.common.label.search') }}
</button>
</div>
</div>
<!-- TABLE Area-->
<table class="table table-bordered ui--table">
<colgroup>
<col style="width:3%" />
<col style="width:27%" />
<col style="width:30%" />
<col style="width:40%" />
</colgroup>
<thead>
<tr>
<th scope="col">
<input type="checkbox" @click.stop v-model="selectAll" @change="toggleSelectAll" />
</th>
<th scope="col">{{ $t('sdl.role.label.heading') }}</th>
<th scope="col">{{ $t('sdl.role.label.roleName') }}</th>
<th scope="col">{{ $t('sdl.common.label.description') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="role in roleList" :key="role.roleId">
<td class="text-center">
<input type="checkbox" v-model="role.selected" @change="checkBoxClick(role)" />
</td>
<td class="text-left">{{ role.roleName }}</td>
<td class="text-left">{{ getLabel(role) }}</td>
<td class="text-left">{{ role.description }}</td>
</tr>
</tbody>
</table>
<!-- TABLE Area-->
<!-- Pagination Area-->
<sdl-pagination
:current-page="current_page"
:total-rows="total"
:per-page="per_page"
@change="handleCurrentChange"
align="center"
/>
<!-- Pagination Area-->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="rtnData">{{ $t('sdl.common.label.confirm') }}</button>
</div>
<!-- // contents -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import { SDLUtil, StringUtil } from '@/utils';
import SDLLocale from '@/mixin/SDLLocale';
export default {
mixins: [SDLLocale],
data() {
return {
searchParam: {
sesearchRoleName: '',
searchLabel: '',
},
selectAll: false,
per_page: null,
current_page: null,
total: null,
roleList: [],
};
},
props: {
sesearchRoleName: {
required: false,
type: String,
},
searchLabel: {
required: false,
type: String,
},
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: object
* return: nothing
* exception: nothing
* description: Select check box in the list of roles
*/
checkBoxClick(item) {
if (!item.selected) {
if (this.selectAll) {
this.selectAll = false;
}
}
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Select All
*/
toggleSelectAll() {
if (this.roleList.length > 0) {
this.roleList.forEach(roleItem => {
roleItem.selected = this.selectAll;
});
}
},
/**
* param: number
* return: nothing
* exception: Call error alert
* description: Look up the list of roles
*/
getRoleList(pageIndex = 1) {
// Set the page number sent to the current page number
this.current_page = pageIndex;
// Launch Loading bar
SDLUtil.showLoadingBar(true);
/**
* Set parameters to query the list of roles
*/
const params = {
orderBy: 'ROLE_NAME',
roleName: this.searchParam.sesearchRoleName,
label: this.searchParam.searchLabel,
pageIndex: this.current_page,
pageSize: this.per_page,
};
/**
* Call role list lookup API
*/
axios
.get(`${SDLUtil.API_URL}/auth/roles-with-paging?${StringUtil.queryStringfy(params)}`)
.then(({ data }) => {
this.roleList = data.rows;
// Apply the page number that was retrieved to the current_page
this.current_page = data.currentPage;
// Full Row Count
this.total = data.records;
/**
* If the paging result is 1 and the query result is one, return and close
*/
if (this.roleList.length === 1 && this.current_page === 1) {
this.rtnFunc(this.roleList);
this.$emit('close');
}
// Remove Loading bar
SDLUtil.showLoadingBar(false);
})
.catch(err => {
// Remove Loading bar
SDLUtil.showLoadingBar(false);
// Call error alert
SDLUtil.errorAlert(err);
});
// Unselect All
this.selectAll = false;
},
/**
* param: number
* return: nothing
* exception: nothing
* description: paging
*/
handleCurrentChange(currentPage) {
// Calling up a function that queries the list of roles
this.getRoleList(currentPage);
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Forward selected list to call page via function
*/
rtnData() {
const selectRoleData = this.roleList.filter(role => role.selected);
this.rtnFunc(selectRoleData);
this.$emit('close');
},
},
created() {
// Page size - 10 settings
this.per_page = 10;
if (this.sesearchRoleName !== null && this.sesearchRoleName !== undefined) {
this.searchParam.sesearchRoleName = this.sesearchRoleName;
}
if (this.searchLabel !== null && this.searchLabel !== undefined) {
this.searchParam.searchLabel = this.searchLabel;
}
// Calling up a function that queries the list of roles
this.getRoleList();
},
};
</script>
<style></style>
@@ -0,0 +1,47 @@
<!--
Program: SentMail
Author: SDL
Brief: SentMail popup
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-xl" style="max-height: fit-content;">
<div class="modal-content" style="min-height: 850px;">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
</div>
<div class="modal-body">
<div v-html="body"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.close') }}
</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
title: {
required: false,
type: String,
},
body: {
required: false,
type: String,
},
},
data() {
return {
};
},
methods: {
},
created() {
},
};
</script>
@@ -0,0 +1,79 @@
<!--
Program: Terms and conditions
Author: SDL
Brief: Terms and conditions popup
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-xl ui--height-100">
<div class="modal-content" style="min-height:100%;">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<div class="row align-items-center">
<div class="col-auto pe-0"><label class="p-2" for="termHistory">{{ $t('sdl.common.label.trialHistory') }}</label></div>
<div class="col-auto ps-0 pe-0">
<select class="form-select" id="termHistory" @change="changeTerm">
<option v-for="item in list" :key="item.termsId" :value="item.termsId">{{ item.startDate }}</option>
</select>
</div>
<div class="col-auto"><button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button></div>
</div>
</div>
<div class="modal-body">
<div v-html="content"></div>
<p class="mt-4">{{ $t('sdl.common.label.enforcementDate') }} : {{ startDate }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.close') }}
</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import _ from 'lodash';
import SDLUtil from '@/utils/SDLUtil';
export default {
props: {
term: {
required: true,
type: Object,
},
title: {
required: false,
type: String,
},
},
data() {
return {
content: '',
startDate: '',
list: [],
};
},
methods: {
changeTerm(event) {
const termsId = event.target.value;
const term = _.find(this.list, ['termsId', termsId]);
this.content = term.content;
this.startDate = term.startDate;
},
},
created() {
this.startDate = this.term.startDate;
this.content = this.term.content;
axios
.get(`${SDLUtil.API_URL}/privacypolicy/terms/${this.term.partId}/prev?langType=${this.term.langType}`)
.then(({ data }) => {
this.list = [this.term, ...data];
})
.catch(err => {
console.error(err);
});
},
};
</script>
@@ -0,0 +1,421 @@
<!--
Program: UserListPopup.vue
Author: SDL
Description: A pop-up screen that queries the user's information and sends the selected user's information.
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:700px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.user.label.heading') }} {{ $t('sdl.common.label.search') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- SEARCH Area -->
<div class="ui--search-container clearfix">
<div class="col-11 float-start">
<div class="row">
<div :class="[userStatus ? 'col-7' : 'col']">
<!-- form -->
<div class="row">
<label
:class="[userStatus ? 'col-3 col-form-label text-end' : 'col-2 col-form-label text-end']"
>{{ $t('sdl.user.label.userCondition') }}</label
>
<div class="col-9">
<div class="input-group">
<!-- dropdown -->
<select class="form-select d-block" style="width:40%" v-model="searchParam.searchColumn">
<option :value="userType.code" v-for="userType in userTypeList" :key="userType.codeId">{{
getLabel(userType)
}}</option>
</select>
<input
type="text"
class="form-control"
style="width:60%"
v-model="searchParam.searchTxt"
@keyup.enter.stop="getUserList()"
@keyup.esc="searchParam.searchTxt = ''"
/>
</div>
</div>
</div>
</div>
<template v-if="userStatus">
<div class="col-5">
<!-- form -->
<div class="row">
<label class="col-4 col-form-label text-end">{{ $t('sdl.user.label.userStatus') }}</label>
<div class="col-8">
<!-- dropdown -->
<select class="form-select" v-model="searchParam.searchStatus">
<option :value="userStat.code" v-for="userStat in userStatList" :key="userStat.codeId">{{
getLabel(userStat)
}}</option>
</select>
</div>
</div>
</div>
</template>
</div>
<!-- //row2 -->
</div>
<!-- search button -->
<div class="col-1 float-end text-end">
<button class="btn btn-primary btn-search" type="submit" @click="getUserList()">
{{ $t('sdl.common.label.search') }}
</button>
</div>
</div>
<!-- TABLE Area-->
<table class="table table-bordered ui--table">
<colgroup>
<col style="width:3%">
<col style="width:20%">
<col style="width:17%">
<col>
<col style="width:23%">
</colgroup>
<thead>
<tr>
<th scope="col">
<input type="checkbox" v-if="!singleMode" @click.stop v-model="selectAll" @change="toggleSelectAll" />
</th>
<th scope="col">{{ $t('sdl.user.label.knoxId') }}</th>
<th scope="col">{{ $t('sdl.common.label.name') }}</th>
<th scope="col">{{ $t('sdl.user.label.deptName') }}</th>
<th scope="col">{{ $t('sdl.user.label.mobile') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="user in userList" :key="user.userId">
<td class="text-center">
<input type="checkbox" v-if="!singleMode" v-model="user.selected" @change="selectRow(user)" />
<input type="radio" v-else name="userCheck" :value="user.userId" v-model="checkUserId" />
</td>
<td class="text-left">{{ user.knoxId }}</td>
<td class="text-left">{{ user.userName }}</td>
<td class="text-left text-break">{{ user.deptName }}</td>
<td class="text-left">{{ user.mobile }}</td>
</tr>
</tbody>
</table>
<!-- TABLE Area-->
<!-- Pagination Area-->
<sdl-pagination
:current-page="current_page"
:total-rows="total"
:per-page="per_page"
@change="handleCurrentChange"
align="center"
/>
<!-- Pagination Area-->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="rtnData">{{ $t('sdl.common.label.confirm') }}</button>
</div>
<!-- // contents -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import { SDLUtil, StringUtil } from '@/utils';
import SDLLocale from '@/mixin/SDLLocale';
export default {
data() {
return {
searchParam: {
searchColumn: '',
searchTxt: '',
searchStatus: '',
active: '',
},
selectAll: false,
per_page: null,
current_page: null,
total: null,
userTypeList: [],
userStatList: [],
userList: [],
selectUser: [],
checkUserId: '',
regExp: {
email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,24}))$/,
},
};
},
props: {
searchColumn: {
required: false,
type: String,
},
searchTxt: {
required: false,
type: String,
},
userStatus: {
required: false,
type: Boolean,
},
knoxSearch: {
required: false,
type: Boolean,
},
singleMode: {
required: false,
type: Boolean,
},
rtnFunc: {
type: Function,
},
},
mixins: [SDLLocale],
methods: {
/**
* param: object
* return: nothing
* exception: nothing
* description: User Lookup List Checkbox Click
*/
selectRow(item) {
if (!item.selected) {
if (this.selectAll) {
this.selectAll = false;
}
}
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Select All
*/
toggleSelectAll() {
this.userList.forEach(user => {
user.selected = this.selectAll;
});
},
/**
* param: number
* return: nothing
* exception: Call error alert
* description: User Lookup
*/
getUserList(pageIndex = 1) {
/**
* Query value must be entered when querying knox
*/
if (this.knoxSearch) {
if (this.searchParam.searchTxt === undefined || this.searchParam.searchTxt === '') {
SDLUtil.alert('sdl.popup.message.enterRequired');
return;
}
if (
this.searchParam.searchColumn === 'userName'
&& (this.getByte(this.searchParam.searchTxt) > 0 && this.getByte(this.searchParam.searchTxt) < 4)
) {
SDLUtil.alert('sdl.popup.message.enterMore4Byte');
return;
}
if (this.searchParam.searchColumn === 'email' && !this.regExp.email.test(this.searchParam.searchTxt)) {
SDLUtil.alert('sdl.samsung.validate.email');
return;
}
}
// Set the page number sent to the current page number
this.current_page = pageIndex;
// Launch Loading bar
SDLUtil.showLoadingBar(true);
/**
* Activate user selection when present
*/
if (this.userStatus) {
if (this.searchParam.searchStatus === 'ALL') {
this.searchParam.active = '';
} else {
this.searchParam.active = this.searchParam.searchStatus;
}
}
let apiUrl = '';
/**
* Set user lookup API enforcement parameter
*/
const params = {
searchCondition: this.searchParam.searchColumn,
searchKeyword: this.searchParam.searchTxt,
active: this.searchParam.active,
pageIndex: this.current_page,
pageSize: this.per_page,
};
/**
* User lookup API address branching out and applying
*/
if (this.knoxSearch) {
apiUrl = `${SDLUtil.API_URL}/auth/users/ldap?${StringUtil.queryStringfy(params)}`;
} else {
apiUrl = `${SDLUtil.API_URL}/auth/users?${StringUtil.queryStringfy(params)}`;
}
/**
* Call user lookup API
*/
axios
.get(apiUrl)
.then(({ data }) => {
this.userList = data.rows;
// Apply the page number that was retrieved to the current_page
this.current_page = data.currentPage;
// Full Row Count
this.total = data.records;
/**
* If the paging result is 1 and the query result is one, return and close
*/
if (this.userList.length === 1 && this.current_page === 1) {
this.rtnFunc(this.userList);
this.$emit('close');
}
// Remove Loading bar
SDLUtil.showLoadingBar(false);
})
.catch(err => {
// Remove Loading bar
SDLUtil.showLoadingBar(false);
// Call error alert
SDLUtil.errorAlert(err);
});
// Unselect All
this.selectAll = false;
},
/**
* param: string
* return: number
* exception: nothing
* description: Return string byte length
*/
getByte(str) {
return str
.split('')
.map(s => s.charCodeAt(0))
// eslint-disable-next-line no-bitwise,no-nested-ternary
.reduce((prev, c) => prev + (c === 10 ? 2 : c >> 7 ? 2 : 1), 0);
},
/**
* param: number
* return: nothing
* exception: nothing
* description: paging
*/
handleCurrentChange(currentPage) {
// Invoke user lookup method
this.getUserList(currentPage);
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Calling up functions that send selected user information to the screen from which it was recalled
*/
rtnData() {
let selectUser = [];
/**
* Apply outgoing values by branching out if they are applied on a quarterly basis
*/
if (this.singleMode) {
selectUser = this.userList.filter(user => user.userId === this.checkUserId);
} else {
selectUser = this.userList.filter(user => user.selected);
}
this.rtnFunc(selectUser);
this.$emit('close');
},
},
created() {
// Page size - 10 settings
this.per_page = 10;
let commonGrpCode = 'USER_SEARCH_TYPE_S';
if (this.knoxSearch) {
commonGrpCode = 'USER_SEARCH_TYPE_K';
}
// user query condition type common code
axios.get(`${SDLUtil.API_URL}/commcode/groupcodes/${commonGrpCode}/commcodes`).then(({ data }) => {
this.userTypeList = data;
});
// user status common code
axios.get(`${SDLUtil.API_URL}/commcode/groupcodes/USER_STAT/commcodes`).then(({ data }) => {
this.userStatList = data;
});
/**
* Query condition setting
*/
if (this.searchColumn === null || this.searchColumn === undefined) {
this.searchParam.searchColumn = 'userName';
} else {
this.searchParam.searchColumn = this.searchColumn;
}
if (this.searchTxt !== null && this.searchTxt !== undefined) {
this.searchParam.searchTxt = this.searchTxt;
}
/**
* Applying exceptional conditions of inquiry to Knox inquiry
*/
if (this.knoxSearch) {
if (this.searchTxt === null || this.searchTxt === undefined) {
return;
}
if (this.searchParam.searchColumn === 'userName' && this.searchParam.searchTxt.length < 2) {
return;
}
}
// Apply Default Value When User Status Existence
if (this.userStatus) {
this.searchParam.searchStatus = 'ALL';
}
// If there is no param value to select only one user, apply multiple selections
if (this.singleMode === undefined) {
this.singleMode = false;
}
/**
* Apply to lookup only if there is a lookup value when inquiring knox
*/
if (this.knoxSearch) {
if (this.searchParam.searchTxt) {
this.getUserList();
}
} else {
this.getUserList();
}
},
};
</script>
<style></style>
@@ -0,0 +1,187 @@
<!--
Program: WorkgroupFormPopup.vue
Author: SDL
Description: popup page for registering and modifying workgroup information
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:404px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.workGroup.label.heading') }} {{ $t('sdl.common.label.register') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<table class="table table-bordered ui--table-form ui--table-form-view mb-3">
<colgroup>
<col style="width:30%" />
<col style="width:70%" />
</colgroup>
<tbody>
<tr>
<th scope="row">{{ $t('sdl.workGroup.label.heading') }} <span class="ui--text-required">&#42;</span></th>
<td>
<input type="text" class="form-control" v-model.trim="workgroupItem.workgroupName" v-validation="" errorMessage="sdl.samsung.validate.required" />
</td>
</tr>
<tr>
<th scope="row">
{{ $t('sdl.workGroup.label.workGroupNameOt') }} <span class="ui--text-required">&#42;</span>
</th>
<td>
<input type="text" class="form-control" v-model.trim="workgroupItem.label" v-validation="" errorMessage="sdl.samsung.validate.required" />
</td>
</tr>
<tr v-for="locale in $i18n.availableLocales" :key="locale">
<th scope="row">
{{ $t('sdl.workGroup.label.workGroupName') }}({{ locale }})
</th>
<td>
<input type="text" class="form-control" v-model.trim="workgroupItem.labelJson[locale]" />
</td>
</tr>
<tr>
<th scope="row">{{ $t('sdl.commonCode.label.description') }}</th>
<td>
<input type="text" class="form-control" v-model.trim="workgroupItem.description" />
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="saveWorkgroup">{{ $t('sdl.common.label.save') }}</button>
</div>
<!-- // contents -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import SDLUtil from '@/utils/SDLUtil';
export default {
data() {
return {
workgroupItem: {
labelJson: {},
},
insertStatus: true,
};
},
props: {
workgroupId: {
required: false,
type: String,
},
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: nothing
* return: nothing
* exception: Call error alert
* description: Query workgroup information
*/
getWorkgroupInfo() {
if (!this.insertStatus) {
// Launch Loading bar
SDLUtil.showLoadingBar(true);
axios
.get(`${SDLUtil.API_URL}/auth/workgroups/${this.workgroupId}`)
.then(({ data }) => {
this.workgroupItem = data;
// Remove Loading bar
SDLUtil.showLoadingBar(false);
})
.catch(err => {
// Remove Loading bar
SDLUtil.showLoadingBar(false);
// Call error alert
SDLUtil.errorAlert(err);
});
}
},
/**
* param: nothing
* return: nothing
* exception: Call error alert
* description: Save workgroup Information
*/
async saveWorkgroup() {
let chkFlag = false;
const params = {
...this.workgroupItem,
};
// validation
if (SDLUtil.onSubmitValidation()) return;
if (this.insertStatus) {
/**
* Call workgroup Registration API
*/
await axios
.post(`${SDLUtil.API_URL}/auth/workgroups`, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(response => {
if (response.status === 200) {
chkFlag = true;
}
})
.catch(err => {
SDLUtil.errorAlert(err);
});
} else {
/**
* Call workgroup modification API
*/
await axios
.put(`${SDLUtil.API_URL}/auth/workgroups/${this.workgroupId}`, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(response => {
if (response.status === 200) {
chkFlag = true;
}
})
.catch(err => {
SDLUtil.errorAlert(err);
});
}
this.rtnFunc(chkFlag);
this.$emit('close');
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Initialization
*/
init() {
this.workgroupItem = {
labelJson: {},
};
},
},
created() {
if (this.workgroupId !== '' && this.workgroupId !== undefined) {
this.insertStatus = false;
this.getWorkgroupInfo();
} else {
this.init();
}
},
};
</script>
@@ -0,0 +1,259 @@
<!--
Program: WorkgroupListPopup.vue
Author: SDL
Description: popup page where you can view and select a list of workgroups
-->
<template>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content" style="height:744px;">
<!-- contents -->
<div class="modal-header">
<h5 class="modal-title h4">{{ $t('sdl.workGroup.label.heading') }} {{ $t('sdl.common.label.search') }}</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- SEARCH Area -->
<div class="ui--search-container clearfix">
<div class="col-11 float-start">
<div class="row">
<div class="col-6">
<div class="row">
<label class="col-4 col-form-label text-end">{{
$t('sdl.workGroup.label.heading')
}}</label>
<div class="col-8">
<input
type="text"
class="form-control"
v-model="searchParam.searchWorkgroupName"
@keyup.enter="getWorkgroupList()"
@keyup.esc="searchParam.searchWorkgroupName = ''"
/>
</div>
</div>
</div>
<div class="col-6">
<div class="row">
<label class="col-4 col-form-label text-end">{{
$t('sdl.workGroup.label.workGroupName')
}}</label>
<div class="col-8">
<input
type="text"
class="form-control"
v-model="searchParam.searchLabel"
@keyup.enter="getWorkgroupList()"
@keyup.esc="searchParam.searchLabel = ''"
/>
</div>
</div>
</div>
</div>
</div>
<!-- search button -->
<div class="col-1 float-end text-end">
<button class="btn btn-primary btn-search" type="submit" @click="getWorkgroupList()">
{{ $t('sdl.common.label.search') }}
</button>
</div>
</div>
<!-- TABLE Area-->
<table class="table table-bordered ui--table">
<colgroup>
<col style="width:3%" />
<col style="width:27%" />
<col style="width:30%" />
<col style="width:40%" />
</colgroup>
<thead>
<tr>
<th scope="col">
<input type="checkbox" @click.stop v-model="selectAll" @change="toggleSelectAll" />
</th>
<th scope="col">{{ $t('sdl.workGroup.label.heading') }}</th>
<th scope="col">{{ $t('sdl.workGroup.label.workGroupName') }}</th>
<th scope="col">{{ $t('sdl.common.label.description') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="workgroup in workgroupList" :key="workgroup.workgroupId">
<td class="text-center">
<input type="checkbox" v-model="workgroup.selected" @change="checkBoxClick(workgroup)" />
</td>
<td class="text-left">{{ workgroup.workgroupName }}</td>
<td class="text-left">{{ getLabel(workgroup) }}</td>
<td class="text-left">{{ workgroup.description }}</td>
</tr>
</tbody>
</table>
<!-- TABLE Area-->
<!-- Pagination Area-->
<sdl-pagination
:current-page="current_page"
:total-rows="total"
:per-page="per_page"
@change="handleCurrentChange"
align="center"
/>
<!-- Pagination Area-->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="$emit('close')">
{{ $t('sdl.common.label.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="rtnData">{{ $t('sdl.common.label.confirm') }}</button>
</div>
<!-- // contents -->
</div>
</div>
</template>
<script>
import axios from 'axios';
import { SDLUtil, StringUtil } from '@/utils';
import SDLLocale from '@/mixin/SDLLocale';
export default {
mixins: [SDLLocale],
data() {
return {
searchParam: {
searchWorkgroupName: '',
searchLabel: '',
},
selectAll: false,
per_page: null,
current_page: null,
total: null,
workgroupList: [],
};
},
props: {
searchWorkgroupName: {
required: false,
type: String,
},
searchLabel: {
required: false,
type: String,
},
rtnFunc: {
type: Function,
},
},
methods: {
/**
* param: object
* return: nothing
* exception: nothing
* description: Select check box in the list of workgroups
*/
checkBoxClick(item) {
if (!item.selected) {
if (this.selectAll) {
this.selectAll = false;
}
}
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Select All
*/
toggleSelectAll() {
if (this.workgroupList.length > 0) {
this.workgroupList.forEach(item => {
item.selected = this.selectAll;
});
}
},
/**
* param: number
* return: nothing
* exception: Call error alert
* description: Look up the list of workgroups
*/
getWorkgroupList(pageIndex = 1) {
// Set the page number sent to the current page number
this.current_page = pageIndex;
// Launch Loading bar
SDLUtil.showLoadingBar(true);
/**
* Set parameters to query the list of workgroups
*/
const params = {
orderBy: 'WORKGROUP_NAME',
workgroupName: this.searchParam.searchWorkgroupName,
label: this.searchParam.searchLabel,
pageIndex: this.current_page,
pageSize: this.per_page,
};
/**
* Call workgroup list lookup API
*/
axios
.get(`${SDLUtil.API_URL}/auth/workgroups-with-paging?${StringUtil.queryStringfy(params)}`)
.then(({ data }) => {
this.workgroupList = data.rows;
// Apply the page number that was retrieved to the current_page
this.current_page = data.currentPage;
// Full Row Count
this.total = data.records;
// Remove Loading bar
SDLUtil.showLoadingBar(false);
})
.catch(err => {
// Remove Loading bar
SDLUtil.showLoadingBar(false);
// Call error alert
SDLUtil.errorAlert(err);
});
this.selectAll = false;
},
/**
* param: number
* return: nothing
* exception: nothing
* description: paging
*/
handleCurrentChange(currentPage) {
// Calling up a function that queries the list of workgroups
this.getWorkgroupList(currentPage);
},
/**
* param: nothing
* return: nothing
* exception: nothing
* description: Forward selected list to call page via function
*/
rtnData() {
const selectWorkgroup = this.workgroupList.filter(workgroupItem => workgroupItem.selected);
this.rtnFunc(selectWorkgroup);
this.$emit('close');
},
},
created() {
// Page size - 10 settings
this.per_page = 10;
if (this.searchWorkgroupName !== null && this.searchWorkgroupName !== undefined) {
this.searchParam.searchWorkgroupName = this.searchWorkgroupName;
}
if (this.searchLabel !== null && this.searchLabel !== undefined) {
this.searchParam.searchLabel = this.searchLabel;
}
// Calling up a function that queries the list of workgroups
this.getWorkgroupList();
},
};
</script>
<style></style>

Some files were not shown because too many files have changed in this diff Show More