This commit is contained in:
Xuan Loi
2026-01-09 17:25:23 +07:00
commit ae1ea57130
315 changed files with 57694 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
<template>
<div>
<Caption v-bind="{title: 'Nhập mã duyệt'}"></Caption>
<div class="field is-grouped mt-5">
<div class="control mr-5" v-for="v in [1,2,3,4]">
<input class="input is-dark" style="font-size: 18px; max-width: 40px; font-weight:bold; text-align: center;" type="password"
maxlength="1" :id="`input${v}`" v-model="data[v]" @keyup="changeNext(v)" />
</div>
</div>
<div class="mt-4">
<a @click="reset()">Nhập lại</a>
</div>
</div>
</template>
<script>
export default {
data() {
return {
data: {},
code: undefined
}
},
async created() {
if(!this.approvalcode && this.$store.state.login.approval_code) this.approvalcode = this.$store.state.login.approval_code
if(!this.approvalcode) {
let user = await this.$getdata('user', {id: this.$store.state.login.id}, undefined, true)
this.approvalcode = user.approval_code
}
},
mounted() {
this.reset()
},
computed: {
approvalcode: {
get: function() {return this.$store.state['approvalcode']},
set: function(val) {this.$store.commit('updateStore', {name: 'approvalcode', data: val})}
}
},
methods: {
checkError() {
if(Object.keys(this.data).length<4) return true
let code = ''
for (const [key, value] of Object.entries(this.data)) {
if(!this.$empty(value)) code += value.toString()
}
if(this.$empty(code) || !this.$isNumber(code)) return true
this.code = code
return false
},
checkValid() {
if(this.checkError()) return this.$snackbar(' phê duyệt không hợp lệ')
if(this.code!==this.approvalcode) return this.$snackbar(' phê duyệt không chính xác')
this.$emit('modalevent', {name: 'approvalcode', data: this.code})
this.$emit('close')
},
changeNext(v) {
if(this.$empty(this.data[v])) return
else if(v===4) return this.checkValid()
let doc = document.getElementById(`input${v+1}`)
if(doc) doc.focus()
},
reset() {
this.data = {}
let doc = document.getElementById(`input1`)
if(doc) doc.focus()
}
}
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<div>
<p v-html="content"></p>
<p class="border-bottom mt-3 mb-5"></p>
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-primary has-text-white" @click="confirm()">{{ store.lang==='en'? 'Accept' : 'Đồng ý'}} </button>
<button class="button is-dark ml-5" @click="cancel()">{{store.lang==='en'? 'Cancel' : 'Hủy bỏ'}} </button>
</div>
<div class="control" v-if="duration">
<CountDown v-bind="{duration: duration}" @close="cancel()"></CountDown>
</div>
</div>
</div>
</template>
<script>
import { useStore } from '~/stores/index'
export default {
setup() {
const store = useStore()
return { store }
},
props: ['content', 'duration'],
methods: {
cancel() {
this.$emit('close')
},
confirm() {
this.$emit('modalevent', {name: 'confirm'})
this.cancel()
}
}
}
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div id="countdown">
<div id="countdown-number"></div>
<svg><circle r="18" cx="20" cy="20" color="red"></circle></svg>
</div>
</template>
<script>
export default {
props: ['duration'],
data() {
return {
timer: undefined,
countdown: this.duration || 10
}
},
mounted() {
var countdownNumberEl = document.getElementById('countdown-number')
countdownNumberEl.textContent = this.countdown;
this.timer = setInterval(()=>this.startCount(), 1000)
},
beforeDestroy() {
clearInterval(this.timer)
},
methods: {
startCount() {
this.countdown -= 1
var countdownNumberEl = document.getElementById('countdown-number')
countdownNumberEl.textContent = this.countdown;
if(this.countdown===0) {
clearInterval(this.timer)
this.$emit('close')
}
}
}
}
</script>
<style scoped>
#countdown {
position: relative;
margin: auto;
height: 40px;
width: 40px;
text-align: center;
}
#countdown-number {
color: black;
display: inline-block;
line-height: 40px;
}
:deep(svg) {
position: absolute;
top: 0;
right: 0;
width: 40px;
height: 40px;
transform: rotateY(-180deg) rotateZ(-90deg);
}
:deep(svg circle) {
stroke-dasharray: 113px;
stroke-dashoffset: 0px;
stroke-linecap: round;
stroke-width: 2px;
stroke: black;
fill: none;
animation: countdown 10s linear infinite forwards;
}
@keyframes countdown {
from {
stroke-dashoffset: 0px;
}
to {
stroke-dashoffset: 113px;
}
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div>
<p v-html="content"></p>
<p class="border-bottom mt-4 mb-5"></p>
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-danger" @click="remove()">Đồng ý</button>
<button class="button is-dark ml-5" @click="cancel()">Hủy bỏ</button>
</div>
<div class="control" v-if="duration">
<CountDown v-bind="{duration: duration}" @close="cancel()"></CountDown>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['content', 'duration', 'vbind', 'setdeleted'],
methods: {
cancel() {
this.$emit('close')
},
async remove() {
let pagename = this.vbind.pagename
let pagedata = this.$store.state[pagename]
let name = pagedata.origin_api.name || this.vbind.api
let id = this.vbind.row.id
let result
if(this.setdeleted) {
let record = await this.$getdata(name, {id: id}, undefined, true)
record.deleted = 1
result = await this.$updateapi(name, record)
} else result = await this.$deleteapi(name, id)
if(result==='error') return this.$dialog('Đã xảy ra lỗi, xóa dữ liệu thất bại', 'Lỗi', 'Error')
this.$snackbar('Dữ liệu đã được xoá khỏi hệ thống', undefined, 'Success')
let arr = Array.isArray(id)? id : [{id: id}]
let copy = this.$copy(this.$store.state[pagename].data)
arr.map(x=>{
let index = copy.findIndex(v=>v.id===x.id)
index>=0? this.$delete(copy,index) : false
})
this.$store.commit('updateState', {name: pagename, key: 'update', data: {data: copy}})
this.cancel()
}
}
}
</script>

View File

@@ -0,0 +1,28 @@
<template>
<div>
<div class="field is-grouped">
<div class="control is-expanded pr-3" v-html="content"></div>
<div class="control">
<SvgIcon v-bind="{name: 'error.svg', type: 'danger', size: 24}"></SvgIcon>
</div>
</div>
<p class="border-bottom mt-3 mb-5"></p>
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-danger has-text-white" @click="cancel()">Đóng</button>
</div>
<div class="control" v-if="duration">
<CountDown v-bind="{duration: duration}" @close="cancel()"></CountDown>
</div>
</div>
</div>
</template>
<script setup>
var props = defineProps({
content: String,
duration: Number
})
function cancel() {
this.$emit('close')
}
</script>

View File

@@ -0,0 +1,24 @@
<template>
<div>
<p v-html="content"></p>
<p class="border-bottom mt-3 mb-5"></p>
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-dark has-text-white" @click="cancel()">Đóng</button>
</div>
<div class="control" v-if="duration">
<CountDown v-bind="{duration: duration}" @close="cancel()"></CountDown>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['content', 'duration'],
methods: {
cancel() {
this.$emit('close')
}
}
}
</script>

View File

@@ -0,0 +1,29 @@
<template>
<div>
<div class="field is-grouped">
<div class="control is-expanded pr-3" v-html="content"></div>
<div class="control">
<SvgIcon v-bind="{name: 'check2.svg', type: 'primary', size: 24}"></SvgIcon>
</div>
</div>
<p class="border-bottom mt-3 mb-5"></p>
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-primary has-text-white" @click="cancel()">Đóng</button>
</div>
<div class="control" v-if="duration">
<CountDown v-bind="{duration: duration}" @close="cancel()"></CountDown>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['content', 'duration'],
methods: {
cancel() {
this.$emit('close')
}
}
}
</script>