Merge branch 'develop'
# Conflicts: # src/views/MainLayout.vue
This commit is contained in:
commit
1de1f5da73
66 changed files with 6739 additions and 120 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
**/node_modules
|
||||
**/dist
|
||||
Dockerfile
|
||||
docker-compose.yaml
|
||||
|
||||
23
.github/workflows/deploy.yaml
vendored
Normal file
23
.github/workflows/deploy.yaml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: deploy-docker
|
||||
run-name: deploy-docker ${{ github.actor }}
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
# https://github.com/appleboy/ssh-action
|
||||
# act -W .github/workflows/deploy.yaml -j remote-deploy -s SSH_PASSWORD
|
||||
remote-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remote Deployment
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
with:
|
||||
host: frappet.com
|
||||
username: frappet
|
||||
password: ${{ secrets.SSH_PASSWORD }}
|
||||
port: 22
|
||||
script: |
|
||||
cd /home/frappet/docker/bma-ehr-recruit-qualifying-exam
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
touch success
|
||||
|
||||
34
.github/workflows/local-build.yaml
vendored
Normal file
34
.github/workflows/local-build.yaml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
name: local-build
|
||||
run-name: local-build ${{ github.actor }}
|
||||
on:
|
||||
# push:
|
||||
# tags:
|
||||
# - v1.**
|
||||
# branches:
|
||||
# - 'main'
|
||||
# branches:
|
||||
# - 'release-*'
|
||||
|
||||
# Allow run workflow manually from Action tab
|
||||
workflow_dispatch:
|
||||
env:
|
||||
REGISTRY: docker.frappet.com
|
||||
CMS_IMAGE_NAME: demo/qualifying-exam-cms
|
||||
CMS_IMAGE_TAG: 0.1.1
|
||||
jobs:
|
||||
# act -W .github/workflows/local-build.yaml -j local-image
|
||||
local-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Check out code"
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: cms
|
||||
load: true
|
||||
tags: ${{env.REGISTRY}}/${{env.CMS_IMAGE_NAME}}:${{env.CMS_IMAGE_TAG}},${{env.REGISTRY}}/${{env.CMS_IMAGE_NAME}}:latest
|
||||
72
.github/workflows/release.yaml
vendored
Normal file
72
.github/workflows/release.yaml
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
name: release
|
||||
run-name: release ${{ github.actor }}
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
tags-ignore:
|
||||
- '2.*'
|
||||
# Allow run workflow manually from Action tab
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
IMAGE_VER:
|
||||
description: "version for build image"
|
||||
type: string
|
||||
env:
|
||||
REGISTRY: docker.frappet.com
|
||||
IMAGE_NAME: demo/test-fe-exam
|
||||
jobs:
|
||||
# act workflow_dispatch -W .github/workflows/release.yaml --input IMAGE_VER=v0.2.1-dev -s DOCKER_USER=sorawit -s DOCKER_PASS=P@ssword -s SSH_PASSWORD=P@ssw0rd
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# https://thekevinwang.com/2022/06/13/github-actions-survival-skills/
|
||||
- name: Check out code # checkout only cms is possible but I checkout all
|
||||
uses: actions/checkout@v3
|
||||
- name: Gen Version
|
||||
id: gen_ver
|
||||
run: |
|
||||
if [[ $GITHUB_REF == 'refs/tags/'* ]]; then
|
||||
IMAGE_VER='${GITHUB_REF/refs\/tags\//}'
|
||||
else
|
||||
IMAGE_VER=${{ github.event.inputs.IMAGE_VER }}
|
||||
fi
|
||||
if [[ $IMAGE_VER == '' ]]; then
|
||||
IMAGE_VER='beta'
|
||||
fi
|
||||
# echo "{\"version\":\"$IMAGE_VER\", \"builddate\":\"$(date +"%Y-%m-%d_%T")\",\"ref_name\":\"$GITHUB_REF\" }" > ./cms/src/lib/ver.json
|
||||
# cat ./cms/src/lib/ver.json
|
||||
echo '::set-output name=image_ver::'$IMAGE_VER
|
||||
- name: Test Version
|
||||
run: |
|
||||
echo $GITHUB_REF
|
||||
echo ${{ steps.gen_ver.outputs.image_ver }}
|
||||
# cat ./cms/src/lib/ver.json
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login in to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{env.REGISTRY}}
|
||||
username: ${{secrets.DOCKER_USER}}
|
||||
password: ${{secrets.DOCKER_PASS}}
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
# context: cms
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{env.REGISTRY}}/${{env.IMAGE_NAME}}:${{ steps.gen_ver.outputs.image_ver }},${{env.REGISTRY}}/${{env.IMAGE_NAME}}:latest
|
||||
# - name: Remote Deployment
|
||||
# uses: appleboy/ssh-action@v0.1.8
|
||||
# with:
|
||||
# host: frappet.com
|
||||
# username: frappet
|
||||
# password: ${{ secrets.SSH_PASSWORD }}
|
||||
# port: 22
|
||||
# script: |
|
||||
# cd /home/frappet/docker/bma-ehr-recruit-qualifying-exam
|
||||
# docker-compose pull
|
||||
# docker-compose up -d
|
||||
# touch success
|
||||
|
||||
55
.github/workflows/remote-build.yaml
vendored
Normal file
55
.github/workflows/remote-build.yaml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
name: remote-build
|
||||
run-name: remote-build ${{ github.actor }}
|
||||
on:
|
||||
# push:
|
||||
# tags:
|
||||
# - 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
# tags-ignore:
|
||||
# - '2.*'
|
||||
# Allow run workflow manually from Action tab
|
||||
workflow_dispatch:
|
||||
env:
|
||||
REGISTRY: docker.frappet.com
|
||||
CMS_IMAGE_NAME: demo/qualifying-exam-cms
|
||||
CMS_IMAGE_TAG: 0.2.1
|
||||
jobs:
|
||||
# act --workflows .github/workflows/build.yaml --job remote-image -s DOCKER_USER -s DOCKER_PASS -s SSH_PASSWORD
|
||||
# act -W .github/workflows/remote-build.yaml -j remote-image -s DOCKER_USER -s DOCKER_PASS -s SSH_PASSWORD
|
||||
remote-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# skip Set up QEMU because it fail on act and container
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login in to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{env.REGISTRY}}
|
||||
username: ${{secrets.DOCKER_USER}}
|
||||
password: ${{secrets.DOCKER_PASS}}
|
||||
# Node no need because use 2 state build
|
||||
# - uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: '18'
|
||||
- name: Build and push docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: cms
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{env.REGISTRY}}/${{env.CMS_IMAGE_NAME}}:${{env.CMS_IMAGE_TAG}},${{env.REGISTRY}}/${{env.CMS_IMAGE_NAME}}:latest
|
||||
- name: Remote Deployment
|
||||
uses: appleboy/ssh-action@v0.1.8
|
||||
with:
|
||||
host: frappet.com
|
||||
username: frappet
|
||||
password: ${{ secrets.SSH_PASSWORD }}
|
||||
port: 22
|
||||
script: |
|
||||
cd /home/frappet/docker/bma-ehr-recruit-qualifying-exam
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
touch success
|
||||
|
||||
|
||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# docker build . -t docker.frappet.com/demo/fe:latest
|
||||
FROM node:latest as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY ./ .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx as production-stage
|
||||
RUN mkdir /app
|
||||
COPY --from=build-stage /app/dist /app
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
30
nginx.conf
Normal file
30
nginx.conf
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
user nginx;
|
||||
worker_processes 1;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
package-lock.json
generated
103
package-lock.json
generated
|
|
@ -9,6 +9,9 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.15.11",
|
||||
"@vuepic/vue-datepicker": "^4.2.1",
|
||||
"keycloak-js": "^21.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.0.32",
|
||||
"quasar": "^2.11.7",
|
||||
"vue": "^3.2.47",
|
||||
|
|
@ -2031,6 +2034,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vuepic/vue-datepicker": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-4.2.1.tgz",
|
||||
"integrity": "sha512-O8hy0o9jQkv/Et7G0mUFDR94NTcHHouy70ELfrTmaWOIBamS/8cRWAwwb/reOvKPX+eo1XKs/v2mj+i5WA14kw==",
|
||||
"dependencies": {
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^1.3.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
|
@ -2354,7 +2372,6 @@
|
|||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -3054,6 +3071,26 @@
|
|||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns-tz": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
|
||||
"integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
|
||||
"peerDependencies": {
|
||||
"date-fns": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||
|
|
@ -5189,6 +5226,11 @@
|
|||
"url": "https://opencollective.com/js-sdsl"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sha256": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
@ -5383,6 +5425,15 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/keycloak-js": {
|
||||
"version": "21.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-21.0.1.tgz",
|
||||
"integrity": "sha512-ot0KW4qyDHl5AyDZNV0CkEkuvIZi+37y3BReNvqqfag7wqJeV13R/PcgECvbbd05+0NSOQjhBL8S+a4A++vNQw==",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"js-sha256": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lazy-ass": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
||||
|
|
@ -5801,6 +5852,14 @@
|
|||
"ufo": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
@ -9601,6 +9660,15 @@
|
|||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@vuepic/vue-datepicker": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-4.2.1.tgz",
|
||||
"integrity": "sha512-O8hy0o9jQkv/Et7G0mUFDR94NTcHHouy70ELfrTmaWOIBamS/8cRWAwwb/reOvKPX+eo1XKs/v2mj+i5WA14kw==",
|
||||
"requires": {
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^1.3.7"
|
||||
}
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
|
@ -9842,8 +9910,7 @@
|
|||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -10348,6 +10415,17 @@
|
|||
"whatwg-url": "^12.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||
},
|
||||
"date-fns-tz": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
|
||||
"integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
|
||||
|
|
@ -11909,6 +11987,11 @@
|
|||
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"js-sha256": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
@ -12065,6 +12148,15 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"keycloak-js": {
|
||||
"version": "21.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-21.0.1.tgz",
|
||||
"integrity": "sha512-ot0KW4qyDHl5AyDZNV0CkEkuvIZi+37y3BReNvqqfag7wqJeV13R/PcgECvbbd05+0NSOQjhBL8S+a4A++vNQw==",
|
||||
"requires": {
|
||||
"base64-js": "^1.5.1",
|
||||
"js-sha256": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"lazy-ass": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
||||
|
|
@ -12379,6 +12471,11 @@
|
|||
"ufo": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.15.11",
|
||||
"@vuepic/vue-datepicker": "^4.2.1",
|
||||
"keycloak-js": "^21.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"pinia": "^2.0.32",
|
||||
"quasar": "^2.11.7",
|
||||
"vue": "^3.2.47",
|
||||
|
|
|
|||
34
src/api/index.ts
Normal file
34
src/api/index.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**config api */
|
||||
import { ref } from "vue";
|
||||
|
||||
const env = ref<string>(process.env.NODE_ENV || "development");
|
||||
// if (process.env.VUE_APP_TEST) {
|
||||
// env = "test";
|
||||
// }
|
||||
|
||||
const config = ref<any>({
|
||||
development: {
|
||||
API_URI: "https://localhost:7006/api/v1",
|
||||
// API_URI: "https://wsh.frappet.com",
|
||||
MEET_URI: "meet.frappet.com",
|
||||
},
|
||||
test: {
|
||||
API_URI: "http://localhost:5010/api/v1",
|
||||
MEET_URI: "meet.frappet.com",
|
||||
},
|
||||
production: {
|
||||
// API_URI: "https://localhost:5010",
|
||||
API_URI: `${window.location.protocol}//api-${window.location.host}/api/v1`,
|
||||
MEET_URI: "meet.frappet.com",
|
||||
},
|
||||
});
|
||||
|
||||
const API_URI = ref<string>(config.value[env.value].API_URI);
|
||||
const MEET_URI = ref<string>(config.value[env.value].MEET_URI);
|
||||
|
||||
export default {
|
||||
env: env.value,
|
||||
config: config.value,
|
||||
API_URI: API_URI.value,
|
||||
MEET_URI: MEET_URI.value,
|
||||
};
|
||||
7
src/api/manage/api.organization.ts
Normal file
7
src/api/manage/api.organization.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import env from '../index'
|
||||
const dashbord = `${env.API_URI}/dashbord/`
|
||||
|
||||
export default {
|
||||
countDashbordSubHistory: (type: number) => `${dashbord}${type}`,
|
||||
countDashbordHistory: `${dashbord}`
|
||||
}
|
||||
11
src/app.config.ts
Normal file
11
src/app.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**ใช้รวมไฟล์ย่อยๆ ของ api แต่ละไฟล์ */
|
||||
|
||||
import manageOrganization from './api/manage/api.organization'
|
||||
|
||||
const API = {
|
||||
...manageOrganization
|
||||
}
|
||||
|
||||
export default {
|
||||
API: API
|
||||
}
|
||||
128
src/components/DialogFooter.vue
Normal file
128
src/components/DialogFooter.vue
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<q-card-actions class="text-primary q-py-sm">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
icon="mdi-menu-left"
|
||||
@click="clickPrevious"
|
||||
v-if="modalEdit == true"
|
||||
:disable="previous == false"
|
||||
:color="!previous ? 'grey-7' : 'public'"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
icon="mdi-menu-right"
|
||||
@click="clickNext"
|
||||
v-if="modalEdit == true"
|
||||
:disable="next == false"
|
||||
:color="!next ? 'grey-7' : 'public'"
|
||||
/>
|
||||
<q-space />
|
||||
<div v-if="editBtn">
|
||||
<q-btn
|
||||
v-if="!editvisible"
|
||||
flat
|
||||
round
|
||||
:disabled="editvisible"
|
||||
:color="editvisible ? 'grey-7' : 'primary'"
|
||||
@click="edit"
|
||||
icon="mdi-pencil-outline"
|
||||
>
|
||||
<q-tooltip>แก้ไขข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<div v-else>
|
||||
<!-- <q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="!editvisible"
|
||||
:outline="!editvisible"
|
||||
:color="!editvisible ? 'grey-7' : 'red'"
|
||||
@click="cancel()"
|
||||
icon="mdi-undo"
|
||||
v-if="modalEdit == true"
|
||||
>
|
||||
<q-tooltip>ยกเลิก</q-tooltip>
|
||||
</q-btn> -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="!editvisible"
|
||||
:color="!editvisible ? 'grey-7' : 'public'"
|
||||
@click="checkSave"
|
||||
icon="mdi-content-save-outline"
|
||||
>
|
||||
<q-tooltip>บันทึก</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-actions>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
editvisible: Boolean,
|
||||
next: Boolean,
|
||||
previous: Boolean,
|
||||
modalEdit: Boolean,
|
||||
editBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
clickNext: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
clickPrevious: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
cancel: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
edit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
validate: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:editvisible', 'update:next', 'update:previous'])
|
||||
|
||||
const updateEdit = (value: Boolean) => {
|
||||
emit('update:editvisible', value)
|
||||
}
|
||||
const cancel = async () => {
|
||||
props.cancel()
|
||||
}
|
||||
const edit = async () => {
|
||||
updateEdit(!props.editvisible)
|
||||
props.edit()
|
||||
}
|
||||
const checkSave = () => {
|
||||
props.validate()
|
||||
props.save()
|
||||
// if (myForm.value !== null) {
|
||||
// myForm.value.validate().then((success) => {
|
||||
// if (success) {
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
const clickNext = async () => {
|
||||
await props.clickNext()
|
||||
}
|
||||
|
||||
const clickPrevious = async () => {
|
||||
await props.clickPrevious()
|
||||
}
|
||||
</script>
|
||||
32
src/components/DialogHeader.vue
Normal file
32
src/components/DialogHeader.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<q-card-section class="row items-center col-12 q-py-sm">
|
||||
<div class="row col-11">
|
||||
<div class="text-bold">{{ tittle }}</div>
|
||||
</div>
|
||||
<q-space />
|
||||
<!-- <div class="row col-1"> -->
|
||||
<q-btn
|
||||
icon="close"
|
||||
unelevated
|
||||
round
|
||||
dense
|
||||
@click="close"
|
||||
style="color: #ff8080; background-color: #ffdede"
|
||||
/>
|
||||
<!-- </div> -->
|
||||
</q-card-section>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
tittle: String,
|
||||
close: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
});
|
||||
const close = async () => {
|
||||
props.close();
|
||||
};
|
||||
</script>
|
||||
86
src/components/NotifyConfirm.vue
Normal file
86
src/components/NotifyConfirm.vue
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<q-dialog :model-value="modal" persistent>
|
||||
<q-card class="q-pa-sm">
|
||||
<q-card-section class="row items-center">
|
||||
<div class="q-pr-md">
|
||||
<q-avatar
|
||||
icon="mdi-alert-circle-outline"
|
||||
font-size="25px"
|
||||
size="lg"
|
||||
color="primary-1"
|
||||
text-color="primary"
|
||||
/>
|
||||
</div>
|
||||
<div class="col text-dark">
|
||||
<span class="text-bold">{{ modalTittle }}</span>
|
||||
<br />
|
||||
<span>{{ modalDetail }}</span>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn label="ยกเลิก" color="primary" @click="cancel" />
|
||||
<q-btn label="ตกลง" color="primary" @click="ok" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modal: Boolean,
|
||||
modalTittle: String,
|
||||
modalDetail: String,
|
||||
ok: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
cancel: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modal'])
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:modal', false)
|
||||
props.cancel()
|
||||
}
|
||||
const ok = () => {
|
||||
emit('update:modal', false)
|
||||
props.ok()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.icon-color {
|
||||
color: #4154b3;
|
||||
}
|
||||
.custom-header-table {
|
||||
max-height: 64vh;
|
||||
.q-table tr:nth-child(odd) td {
|
||||
background: white;
|
||||
}
|
||||
.q-table tr:nth-child(even) td {
|
||||
background: #f6f6f6ae;
|
||||
}
|
||||
|
||||
.q-table thead tr {
|
||||
background: #ecebeb;
|
||||
}
|
||||
|
||||
.q-table thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
/* this will be the loading indicator */
|
||||
.q-table thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
.q-table thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
92
src/components/NotifyError.vue
Normal file
92
src/components/NotifyError.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<q-dialog
|
||||
:model-value="modalError"
|
||||
persistent
|
||||
@update:model-value="updateClose"
|
||||
>
|
||||
<q-card class="q-pa-sm">
|
||||
<q-card-section class="row items-center">
|
||||
<div class="q-pr-md">
|
||||
<q-avatar
|
||||
icon="mdi-alert-circle-outline"
|
||||
font-size="25px"
|
||||
size="lg"
|
||||
color="red-1"
|
||||
text-color="red"
|
||||
/>
|
||||
</div>
|
||||
<div class="col text-dark">
|
||||
<span class="text-bold">{{ modalErrorTittle }}</span>
|
||||
<br />
|
||||
<span>{{ modalErrorDetail }}</span>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn
|
||||
label="ตกลง"
|
||||
color="primary"
|
||||
@click="updateClose"
|
||||
v-close-popup
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
modalError: Boolean,
|
||||
modalErrorTittle: String,
|
||||
modalErrorDetail: String,
|
||||
close: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"update:modalError",
|
||||
"update:modalErrorTittle",
|
||||
"update:modalErrorDetail",
|
||||
]);
|
||||
|
||||
const updateClose = () => {
|
||||
emit("update:modalError", false);
|
||||
emit("update:modalErrorTittle", "");
|
||||
emit("update:modalErrorDetail", "");
|
||||
props.close();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.icon-color {
|
||||
color: #4154b3;
|
||||
}
|
||||
.custom-header-table {
|
||||
max-height: 64vh;
|
||||
.q-table tr:nth-child(odd) td {
|
||||
background: white;
|
||||
}
|
||||
.q-table tr:nth-child(even) td {
|
||||
background: #f6f6f6ae;
|
||||
}
|
||||
|
||||
.q-table thead tr {
|
||||
background: #ecebeb;
|
||||
}
|
||||
|
||||
.q-table thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
/* this will be the loading indicator */
|
||||
.q-table thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
.q-table thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
222
src/components/Table.vue
Normal file
222
src/components/Table.vue
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<div class="q-pb-sm row">
|
||||
<!-- -->
|
||||
<HeaderTop
|
||||
v-model:edit="editBtn"
|
||||
:header="name"
|
||||
:icon="icon"
|
||||
:add="clickAdd"
|
||||
:editBtn="clickEdit"
|
||||
:cancel="clickCancel"
|
||||
:history="false"
|
||||
:addData="false"
|
||||
/>
|
||||
<!-- v-if="nameHeader" -->
|
||||
<!-- <div class="q-pl-sm">
|
||||
<q-btn size="12px" flat round color="add" @click="add" icon="mdi-plus" v-if="addData">
|
||||
<q-tooltip>เพิ่มข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
</div> -->
|
||||
<q-space />
|
||||
<div class="items-center" style="display: flex">
|
||||
<!-- ค้นหาข้อความใน table -->
|
||||
<q-input
|
||||
standout
|
||||
dense
|
||||
:model-value="inputfilter"
|
||||
ref="filterRef"
|
||||
@update:model-value="updateInput"
|
||||
outlined
|
||||
debounce="300"
|
||||
placeholder="ค้นหา"
|
||||
style="max-width: 200px"
|
||||
class="q-ml-sm"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon v-if="inputfilter == ''" name="search" />
|
||||
<q-icon
|
||||
v-if="inputfilter !== ''"
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="resetFilter"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- แสดงคอลัมน์ใน table -->
|
||||
<q-select
|
||||
:model-value="inputvisible"
|
||||
@update:model-value="updateVisible"
|
||||
:display-value="$q.lang.table.columns"
|
||||
multiple
|
||||
outlined
|
||||
dense
|
||||
:options="attrs.columns"
|
||||
options-dense
|
||||
option-value="name"
|
||||
map-options
|
||||
emit-value
|
||||
style="min-width: 150px"
|
||||
class="gt-xs q-ml-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
ref="table"
|
||||
flat
|
||||
bordered
|
||||
class="custom-header-table"
|
||||
v-bind="attrs"
|
||||
virtual-scroll
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
dense
|
||||
:pagination-label="paginationLabel"
|
||||
:pagination="initialPagination"
|
||||
:rows-per-page-options="[0]"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span class="text-weight-medium">{{ col.label }}</span>
|
||||
</q-th>
|
||||
<q-th auto-width v-if="editBtn == true" />
|
||||
</q-tr>
|
||||
</template>
|
||||
<template #body="props">
|
||||
<slot v-bind="props" name="columns"></slot>
|
||||
</template>
|
||||
</q-table>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs, watch } from 'vue'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
import type { Pagination } from '@/modules/01_exam/interface/index/Main'
|
||||
|
||||
const attrs = ref<any>(useAttrs())
|
||||
const table = ref<any>(null)
|
||||
const filterRef = ref<any>(null)
|
||||
const editBtn = ref<boolean>(false)
|
||||
const initialPagination = ref<Pagination>({
|
||||
// descending: false,
|
||||
rowsPerPage: 0
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
inputfilter: String,
|
||||
name: String,
|
||||
icon: String,
|
||||
inputvisible: Array,
|
||||
editvisible: Boolean,
|
||||
nameHeader: Boolean,
|
||||
addData: {
|
||||
type: Boolean,
|
||||
defualt: true
|
||||
},
|
||||
edit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
add: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
cancel: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
validate: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:inputfilter', 'update:inputvisible', 'update:editvisible'])
|
||||
|
||||
watch(props, (count: any, prevCount: any) => {
|
||||
editBtn.value = props.editvisible
|
||||
})
|
||||
|
||||
const updateEdit = (value: Boolean) => {
|
||||
emit('update:editvisible', value)
|
||||
}
|
||||
const updateInput = (value: string | number | null) => {
|
||||
emit('update:inputfilter', value)
|
||||
}
|
||||
const updateVisible = (value: []) => {
|
||||
emit('update:inputvisible', value)
|
||||
}
|
||||
|
||||
const paginationLabel = (start: string, end: string, total: string) => {
|
||||
return start + '-' + end + ' ใน ' + total
|
||||
}
|
||||
|
||||
const clickAdd = () => {
|
||||
// props.validate();
|
||||
props.add()
|
||||
}
|
||||
|
||||
const clickEdit = () => {
|
||||
// props.validate();
|
||||
props.edit()
|
||||
}
|
||||
|
||||
const clickCancel = () => {
|
||||
// props.validate();
|
||||
props.cancel()
|
||||
}
|
||||
|
||||
const edit = async () => {
|
||||
updateEdit(!props.editvisible)
|
||||
props.edit()
|
||||
}
|
||||
|
||||
const cancel = async () => {
|
||||
updateEdit(!props.editvisible)
|
||||
props.cancel()
|
||||
}
|
||||
|
||||
const resetFilter = () => {
|
||||
// reset ค่าที่ค้นหาเมื่อกดปุ่ม X ในกล่องค้นหา
|
||||
emit('update:inputfilter', '')
|
||||
filterRef.value.focus()
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
props.add()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.icon-color {
|
||||
color: #4154b3;
|
||||
}
|
||||
|
||||
.custom-header-table {
|
||||
max-height: 64vh;
|
||||
|
||||
.q-table tr:nth-child(odd) td {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.q-table tr:nth-child(even) td {
|
||||
background: #f6f6f6ae;
|
||||
}
|
||||
|
||||
.q-table thead tr {
|
||||
background: #ecebeb;
|
||||
}
|
||||
|
||||
.q-table thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* this will be the loading indicator */
|
||||
.q-table thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
.q-table thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
481
src/components/TableView.vue
Normal file
481
src/components/TableView.vue
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
<template>
|
||||
<div class="q-px-md q-pb-md">
|
||||
<!-- header บน table มี ค้นหา แสดงคอลัมน์ ปุ่มแก้ไข เพิ่ม เผยแพร่ข้อมูล ยกเลิก (status nornmalData false) -->
|
||||
<div class="col-12 row q-py-sm" v-if="nornmalData == false">
|
||||
<q-btn
|
||||
v-if="!editvisible == true && publicNoBtn == false"
|
||||
flat
|
||||
round
|
||||
:disabled="editvisible == true"
|
||||
:color="editvisible == true ? 'grey-7' : 'primary'"
|
||||
@click="edit"
|
||||
icon="mdi-pencil-outline"
|
||||
>
|
||||
<q-tooltip>แก้ไขข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- ยกเลิก แสดงเมื่อ กดปุ่มแก้ไข -->
|
||||
<q-btn
|
||||
v-else
|
||||
flat
|
||||
round
|
||||
:disabled="editvisible == false"
|
||||
:outline="editvisible == false"
|
||||
:color="editvisible == false ? 'grey-7' : 'red'"
|
||||
@click="cancel()"
|
||||
icon="mdi-undo"
|
||||
>
|
||||
<q-tooltip>ยกเลิก</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- <q-separator vertical /> -->
|
||||
<div class="q-px-sm">
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="editvisible == false"
|
||||
:color="editvisible == false ? 'grey-7' : 'add'"
|
||||
@click="add"
|
||||
icon="mdi-plus"
|
||||
>
|
||||
<q-tooltip>เพิ่มข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<!-- บันทึกร่าง แสดงเมื่อ กดปุ่มแก้ไข ข้อมูลมีการเป็นแปลงหรือ ยังไม่เผยแพร่ข้อมูล -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="!(editvisible == true && updateData == true)"
|
||||
:color="
|
||||
!(editvisible == true && updateData == true) ? 'grey-7' : 'public'
|
||||
"
|
||||
@click="checkSave"
|
||||
v-if="saveNoDraft == false"
|
||||
icon="mdi-content-save-outline"
|
||||
>
|
||||
<q-tooltip>บันทึกร่าง</q-tooltip>
|
||||
</q-btn>
|
||||
<!-- ลบบันทึกร่าง แสดงเมื่อ บันทึกร่างแล้ว -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="publicData == true"
|
||||
:color="publicData == true ? 'grey-7' : 'deep-orange'"
|
||||
@click="DeleteModal"
|
||||
icon="mdi-file-remove-outline"
|
||||
v-if="publicNoBtn == false"
|
||||
>
|
||||
<q-tooltip>ลบบันทึกร่าง</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<!-- เผยแพร่ -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
:disabled="!(publicData == false || updateData == true)"
|
||||
:color="
|
||||
!(publicData == false || updateData == true) ? 'grey-7' : 'public'
|
||||
"
|
||||
@click="publishModal"
|
||||
icon="mdi-cloud-upload-outline"
|
||||
v-if="publicNoBtn == false"
|
||||
>
|
||||
<q-tooltip>เผยแพร่</q-tooltip>
|
||||
</q-btn>
|
||||
<div class="items-center" style="display: flex">
|
||||
<div
|
||||
class="row items-center"
|
||||
style="display: flex"
|
||||
v-if="publicData == false && publicNoBtn == false"
|
||||
>
|
||||
<div class="text-public text-body2 text-weight-medium q-px-sm">
|
||||
ข้อมูลยังไม่เผยแพร่
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-space />
|
||||
<div class="items-center" style="display: flex">
|
||||
<!-- ข้อความสถานะเผยแพร่ โดยใช้ parameter publicData เป็นตัวกำหนดข้อความ -->
|
||||
<!-- <div class="row items-center" style="display: flex" v-if="publicData == true">
|
||||
<q-icon cener size="20px" name="label_important" class="icon-color" />
|
||||
<div class="text-size">ข้อมูลเผยแพร่แล้ว</div>
|
||||
</div> -->
|
||||
<div
|
||||
class="row items-center"
|
||||
style="display: flex"
|
||||
v-if="publicData == false && publicNoBtn == false"
|
||||
>
|
||||
<!-- <q-icon cener size="20px" name="label_important" color="amber" />
|
||||
<div class="text-grey-7 text-body2 text-weight-medium q-px-sm">
|
||||
ข้อมูลยังไม่เผยแพร่
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- ค้นหาข้อความใน table -->
|
||||
<q-input
|
||||
standout
|
||||
dense
|
||||
:model-value="inputfilter"
|
||||
ref="filterRef"
|
||||
@update:model-value="updateInput"
|
||||
outlined
|
||||
debounce="300"
|
||||
placeholder="ค้นหา"
|
||||
style="max-width: 200px"
|
||||
class="q-ml-sm"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon v-if="inputfilter == ''" name="search" />
|
||||
<q-icon
|
||||
v-if="inputfilter !== ''"
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="resetFilter"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- แสดงคอลัมน์ใน table -->
|
||||
<q-select
|
||||
:model-value="inputvisible"
|
||||
@update:model-value="updateVisible"
|
||||
:display-value="$q.lang.table.columns"
|
||||
multiple
|
||||
outlined
|
||||
dense
|
||||
:options="attrs.columns"
|
||||
options-dense
|
||||
option-value="name"
|
||||
map-options
|
||||
emit-value
|
||||
style="min-width: 150px"
|
||||
class="gt-xs q-ml-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- header บน table มี ค้นหา แสดงคอลัมน์ (status nornmalData true) -->
|
||||
<div class="col-12 row q-py-sm" v-if="nornmalData == true">
|
||||
<q-space />
|
||||
<div class="items-center" style="display: flex">
|
||||
<!-- ค้นหาข้อความใน table -->
|
||||
<q-input
|
||||
standout
|
||||
dense
|
||||
:model-value="inputfilter"
|
||||
ref="filterRef"
|
||||
@update:model-value="updateInput"
|
||||
outlined
|
||||
debounce="300"
|
||||
placeholder="ค้นหา"
|
||||
style="max-width: 200px"
|
||||
class="q-ml-sm"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon v-if="inputfilter == ''" name="search" />
|
||||
<q-icon
|
||||
v-if="inputfilter !== ''"
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="resetFilter"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- แสดงคอลัมน์ใน table -->
|
||||
<q-select
|
||||
:model-value="inputvisible"
|
||||
@update:model-value="updateVisible"
|
||||
:display-value="$q.lang.table.columns"
|
||||
multiple
|
||||
outlined
|
||||
dense
|
||||
:options="attrs.columns"
|
||||
options-dense
|
||||
option-value="name"
|
||||
map-options
|
||||
emit-value
|
||||
style="min-width: 150px"
|
||||
class="gt-xs q-ml-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
ref="table"
|
||||
flat
|
||||
bordered
|
||||
class="custom-header-table"
|
||||
v-bind="attrs"
|
||||
virtual-scroll
|
||||
:virtual-scroll-sticky-size-start="48"
|
||||
dense
|
||||
:pagination-label="paginationLabel"
|
||||
:pagination="initialPagination"
|
||||
:rows-per-page-options="paging == true ? [25, 50, 100, 500] : []"
|
||||
>
|
||||
<!-- :pagination="initialPagination" -->
|
||||
<!-- :rows-per-page-options="[0]" -->
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width v-if="boss == true" />
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<span class="text-weight-medium">{{ col.label }}</span>
|
||||
</q-th>
|
||||
<q-th
|
||||
auto-width
|
||||
v-if="
|
||||
editvisible == true || nextPageVisible == true || history == true
|
||||
"
|
||||
/>
|
||||
</q-tr>
|
||||
</template>
|
||||
<!-- สำหรับเรียกใช้ template ตัวข้างนอก -->
|
||||
<template #body="props">
|
||||
<slot v-bind="props" name="columns"></slot>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<!-- ข้อมูลการเผยแพร่ข้อมูล -->
|
||||
<q-dialog v-model="modalPublish" persistent>
|
||||
<q-card class="q-pa-sm">
|
||||
<q-card-section class="row">
|
||||
<div class="q-pr-md">
|
||||
<q-avatar
|
||||
icon="public"
|
||||
size="lg"
|
||||
font-size="25px"
|
||||
color="blue-1"
|
||||
text-color="public"
|
||||
/>
|
||||
</div>
|
||||
<div class="col text-dark">
|
||||
<span class="text-bold">ต้องการเผยแพร่ข้อมูลนี้หรือไม่?</span>
|
||||
<br />
|
||||
<span>ข้อมูลที่กำลังถูกเผยแพร่นี้จะมีผลใช้งานทันที</span>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn label="ยกเลิก" flat color="grey-8" v-close-popup />
|
||||
<q-btn
|
||||
label="เผยแพร่"
|
||||
color="public"
|
||||
@click="publish()"
|
||||
v-close-popup
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<!-- ข้อมูลการลบเผยแพร่ข้อมูล -->
|
||||
<q-dialog v-model="modalDelete" persistent>
|
||||
<q-card class="q-pa-sm">
|
||||
<q-card-section class="row">
|
||||
<div class="q-pr-md">
|
||||
<q-avatar
|
||||
icon="mdi-file-remove-outline"
|
||||
size="lg"
|
||||
font-size="25px"
|
||||
color="red-1"
|
||||
text-color="deep-orange"
|
||||
/>
|
||||
</div>
|
||||
<div class="col text-dark">
|
||||
<span class="text-bold">ต้องการลบข้อมูลบันทึกร่างนี้หรือไม่?</span>
|
||||
<br />
|
||||
<span>ข้อมูลบันทึกร่างที่กำลังถูกลบนี้จะมีผลใช้งานทันที</span>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn label="ยกเลิก" flat color="grey-8" v-close-popup />
|
||||
<q-btn label="ลบบันทึก" color="red" @click="deleted()" v-close-popup />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from "vue";
|
||||
|
||||
const attrs = ref<any>(useAttrs());
|
||||
const table = ref<any>(null);
|
||||
const filterRef = ref<any>(null);
|
||||
const modalPublish = ref<boolean>(false);
|
||||
const modalDelete = ref<boolean>(false);
|
||||
const props = defineProps({
|
||||
inputfilter: String,
|
||||
inputvisible: Array,
|
||||
editvisible: Boolean,
|
||||
boss: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
saveNoDraft: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
history: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
paging: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
nornmalData: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
nextPageVisible: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
publicData: {
|
||||
type: Boolean,
|
||||
defualt: true,
|
||||
required: false,
|
||||
},
|
||||
updateData: {
|
||||
type: Boolean,
|
||||
defualt: true,
|
||||
required: false,
|
||||
},
|
||||
publicNoBtn: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
},
|
||||
add: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
edit: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
deleted: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
cancel: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
publish: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
validate: {
|
||||
type: Function,
|
||||
default: () => console.log("not function"),
|
||||
},
|
||||
});
|
||||
const initialPagination = ref<any>({
|
||||
// descending: false,
|
||||
rowsPerPage: props.paging == true ? 25 : 0,
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"update:inputfilter",
|
||||
"update:inputvisible",
|
||||
"update:editvisible",
|
||||
]);
|
||||
|
||||
const updateEdit = (value: any) => {
|
||||
emit("update:editvisible", value);
|
||||
};
|
||||
const updateInput = (value: any) => {
|
||||
emit("update:inputfilter", value);
|
||||
};
|
||||
const updateVisible = (value: any) => {
|
||||
emit("update:inputvisible", value);
|
||||
};
|
||||
|
||||
const paginationLabel = (start: string, end: string, total: string) => {
|
||||
if (props.paging == true)
|
||||
return " " + start + " ใน " + end + " จากจำนวน " + total + " รายการ";
|
||||
else return start + "-" + end + " ใน " + total;
|
||||
};
|
||||
|
||||
const checkSave = () => {
|
||||
props.validate();
|
||||
props.save();
|
||||
// if (myForm.value !== null) {
|
||||
// myForm.value.validate().then((success) => {
|
||||
// if (success) {
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
};
|
||||
|
||||
const publishModal = () => {
|
||||
props.validate();
|
||||
const filter = attrs.value.rows.filter((r: any) => r.name == "");
|
||||
|
||||
if (filter.length == 0 || attrs.value.rows.length == 0) {
|
||||
modalPublish.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const DeleteModal = () => {
|
||||
modalDelete.value = true;
|
||||
};
|
||||
|
||||
const edit = async () => {
|
||||
updateEdit(!props.editvisible);
|
||||
props.edit();
|
||||
};
|
||||
|
||||
const add = async () => {
|
||||
// if (myForm.value !== null) {
|
||||
// myForm.value.validate();
|
||||
// }
|
||||
props.validate();
|
||||
props.add();
|
||||
await table.value.lastPage();
|
||||
await table.value.scrollTo(attrs.value.rows.length - 1);
|
||||
};
|
||||
|
||||
const deleted = async () => {
|
||||
if (props.publicNoBtn === false) {
|
||||
updateEdit(false);
|
||||
}
|
||||
props.deleted();
|
||||
};
|
||||
|
||||
const resetFilter = () => {
|
||||
// reset ค่าที่ค้นหาเมื่อกดปุ่ม X ในกล่องค้นหา
|
||||
emit("update:inputfilter", "");
|
||||
filterRef.value.focus();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.icon-color {
|
||||
color: #4154b3;
|
||||
}
|
||||
.custom-header-table {
|
||||
max-height: 64vh;
|
||||
.q-table tr:nth-child(odd) td {
|
||||
background: white;
|
||||
}
|
||||
.q-table tr:nth-child(even) td {
|
||||
background: #f6f6f6ae;
|
||||
}
|
||||
|
||||
.q-table thead tr {
|
||||
background: #ecebeb;
|
||||
}
|
||||
|
||||
.q-table thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
/* this will be the loading indicator */
|
||||
.q-table thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
.q-table thead tr:first-child th {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
src/components/top.vue
Normal file
165
src/components/top.vue
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center" v-if="header != ''">
|
||||
<q-icon :name="icon" size="1.5em" color="grey-5" class="q-mr-md" />
|
||||
<div class="text-weight-medium text-dark col-12 row items-center text-header">
|
||||
{{ header }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-gutter-sm q-mx-sm" v-if="addData == true">
|
||||
<q-btn
|
||||
size="12px"
|
||||
v-if="!edit"
|
||||
flat
|
||||
round
|
||||
:disabled="edit"
|
||||
:color="edit ? 'grey-7' : 'primary'"
|
||||
@click="ClickEdit"
|
||||
icon="mdi-pencil-outline"
|
||||
>
|
||||
<q-tooltip>แก้ไขข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
round
|
||||
v-if="edit"
|
||||
:disabled="!edit"
|
||||
:color="!edit ? 'grey-7' : 'public'"
|
||||
@click="save"
|
||||
icon="mdi-content-save-outline"
|
||||
>
|
||||
<q-tooltip>บันทึกข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
round
|
||||
v-if="edit"
|
||||
:disabled="!edit"
|
||||
:color="!edit ? 'grey-7' : 'red'"
|
||||
@click="ClickCancel"
|
||||
icon="mdi-undo"
|
||||
>
|
||||
<q-tooltip>ยกเลิก</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
<div class="q-pl-sm" v-else>
|
||||
<q-btn
|
||||
size="12px"
|
||||
v-if="!edit"
|
||||
flat
|
||||
round
|
||||
:disabled="edit"
|
||||
:color="edit ? 'grey-7' : 'primary'"
|
||||
@click="ClickEdit"
|
||||
icon="mdi-pencil-outline"
|
||||
>
|
||||
<q-tooltip>แก้ไขข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
round
|
||||
v-if="edit"
|
||||
:disabled="!edit"
|
||||
:color="!edit ? 'grey-7' : 'add'"
|
||||
@click="add"
|
||||
icon="mdi-plus"
|
||||
>
|
||||
<q-tooltip>เพิ่มข้อมูล</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
round
|
||||
v-if="edit"
|
||||
:disabled="!edit"
|
||||
:color="!edit ? 'grey-7' : 'red'"
|
||||
@click="ClickCancel"
|
||||
icon="mdi-undo"
|
||||
>
|
||||
<q-tooltip>ยกเลิก</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
<q-space />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: true
|
||||
},
|
||||
addData: {
|
||||
type: Boolean,
|
||||
defualt: true
|
||||
},
|
||||
add: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
save: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
deleted: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
cancel: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
editBtn: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
changeBtn: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:edit'])
|
||||
|
||||
const updateEdit = (value: any) => {
|
||||
emit('update:edit', value)
|
||||
}
|
||||
|
||||
const ClickEdit = () => {
|
||||
updateEdit(!props.edit)
|
||||
props.editBtn()
|
||||
props.changeBtn()
|
||||
}
|
||||
|
||||
const ClickCancel = () => {
|
||||
updateEdit(!props.edit)
|
||||
props.cancel()
|
||||
props.changeBtn()
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
props.save()
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
props.add()
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
/* .q-btn >>> .q-icon {
|
||||
font-size: 20px;
|
||||
} */
|
||||
</style>
|
||||
|
|
@ -33,15 +33,8 @@ const menuList = readonly<menuType[]>([
|
|||
key: 2,
|
||||
icon: 'o_person',
|
||||
activeIcon: 'person',
|
||||
label: 'ข้อมูลหลัก01',
|
||||
path: 'meta01'
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
icon: 'o_person',
|
||||
activeIcon: 'person',
|
||||
label: 'ข้อมูลหลัก02',
|
||||
path: 'meta02'
|
||||
label: 'รายการสอบทั้งหมด',
|
||||
path: 'exam'
|
||||
}
|
||||
])
|
||||
|
||||
|
|
@ -70,7 +63,7 @@ const notiList = readonly<notiType[]>([
|
|||
{
|
||||
id: 1,
|
||||
sender: 'ท',
|
||||
body: 'ขอแก้ไขข้อมูลทะเบียนประวัติ',
|
||||
body: 'ขอแก้ไขข้อมูลรายการสอบทั้งหมด',
|
||||
timereceive: '13/12/2565'
|
||||
}
|
||||
])
|
||||
|
|
|
|||
35
src/main.ts
35
src/main.ts
|
|
@ -1,21 +1,23 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { Dialog, Notify, Quasar } from 'quasar'
|
||||
|
||||
import { createApp, defineAsyncComponent } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { Dialog, Notify, Quasar } from 'quasar'
|
||||
import quasarUserOptions from './quasar-user-options'
|
||||
|
||||
import 'quasar/src/css/index.sass'
|
||||
import th from 'quasar/lang/th'
|
||||
|
||||
import '@vuepic/vue-datepicker/dist/main.css'
|
||||
import http from './plugins/http'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
// import './assets/main.css'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
|
||||
app.use(
|
||||
Quasar,
|
||||
|
|
@ -30,8 +32,27 @@ app.use(
|
|||
}
|
||||
},
|
||||
lang: th
|
||||
},
|
||||
quasarUserOptions
|
||||
}
|
||||
// quasarUserOptions
|
||||
)
|
||||
|
||||
app.component(
|
||||
'data-table',
|
||||
defineAsyncComponent(() => import('./components/TableView.vue'))
|
||||
)
|
||||
app.component(
|
||||
'notifyError',
|
||||
defineAsyncComponent(() => import('./components/NotifyError.vue'))
|
||||
)
|
||||
app.component(
|
||||
'datepicker',
|
||||
defineAsyncComponent(() => import('@vuepic/vue-datepicker'))
|
||||
)
|
||||
app.component(
|
||||
'full-loader',
|
||||
defineAsyncComponent(() => import('./plugins/FullLoader.vue'))
|
||||
)
|
||||
|
||||
app.config.globalProperties.$http = http
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
|||
40
src/modules/01_exam/components/ExamCrad.vue
Normal file
40
src/modules/01_exam/components/ExamCrad.vue
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<div class="q-pa-md shadow-4 rounded-borders cursor-pointer">
|
||||
<label class="text-h6 row cursor-pointer">{{ items.title }}</label>
|
||||
<label class="text-grey-7 row text-size cursor-pointer"
|
||||
>ประกาศวันที่ {{ date2Thai(items.announcementDate) }}</label
|
||||
>
|
||||
<label class="text-grey-7 row q-my-md text-size cursor-pointer"
|
||||
>ครั้งที่ {{ items.registerRound }}</label
|
||||
>
|
||||
<div class="row justify-between items-end cursor-pointer">
|
||||
<label class="text-grey-7 text-size row cursor-pointer"
|
||||
>เปิดรับสมัคร {{ date2Thai(items.registerDateStart) }} -
|
||||
{{ date2Thai(items.registerDateEnd) }}</label
|
||||
>
|
||||
<div class="rounded-borders q-pa-sm bg-teal cursor-pointer shadow-2">
|
||||
<label class="text-semi text-white text-size cursor-pointer">สมัครสอบ</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import type { ExamCard } from '../interface/index/Main'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai } = mixin
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Object as PropType<ExamCard>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.text-size {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
102
src/modules/01_exam/components/ExamDetail.vue
Normal file
102
src/modules/01_exam/components/ExamDetail.vue
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div class="q-px-md">
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md q-mt-md">
|
||||
<q-card-section>
|
||||
<div class="text-h6 q-py-sm">{{ exam.displayText }} ({{ exam.description }})</div>
|
||||
<div class="text-subtitle2 q-py-sm">ประกาศวันที่ {{ date2Thai(exam.createdUtc) }}</div>
|
||||
<div class="text-subtitle2 q-py-sm">ประจำปีงบประมาณ {{ exam.budgetYear }}</div>
|
||||
<div class="text-subtitle2 q-py-sm">
|
||||
เปิดรับสมัครวันที่ {{ date2Thai(exam.startDate) }} - {{ date2Thai(exam.endDate) }}
|
||||
</div>
|
||||
<div class="text-subtitle2 q-py-sm">ค่าธรรมเนียมการสมัคร {{ exam.fee }} บาท</div>
|
||||
<!-- <div v-html="exam.htmlBody.html"></div> -->
|
||||
</q-card-section>
|
||||
|
||||
<div class="text-subtitle1 text-center text-primary">เอกสารเกี่ยวกับการสอบนี้</div>
|
||||
<div class="row justify-start q-pa-md q-col-gutter-md">
|
||||
<div
|
||||
v-for="file in exam.document"
|
||||
:key="file.contentItemId"
|
||||
class="col-12"
|
||||
:href="`https://localhost:5001${file.file}`"
|
||||
>
|
||||
<p>
|
||||
<a :href="`https://localhost:5001${file.file}`" target="_blank">{{
|
||||
file.displayText
|
||||
}}</a
|
||||
><br />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <template v-slot:body-cell-name="props">
|
||||
<q-td :props="props">
|
||||
<div>
|
||||
<a href="https://quasar.dev/vue-components/table#QTable-API" />
|
||||
</div>
|
||||
</q-td>
|
||||
</template> -->
|
||||
</q-card>
|
||||
<div class="col-12">
|
||||
<div class="text-h6 q-py-sm">ข้อควรระวัง</div>
|
||||
<div class="text-warning q-pb-md">1. ผู้สมัครสามารถสมัครได้เพียงครั้งเดียว</div>
|
||||
<div class="text-grey-7 q-pb-md">
|
||||
2. ชื่อ และนามสกุล ที่ท่านกรอกลงใน "ใบสมัครออนไลน์"
|
||||
จะต้องเป็นชื่อที่ตรงกับบัตรประจำตัวประชาชน
|
||||
</div>
|
||||
<div class="text-grey-7 q-pb-md">
|
||||
3. กรุณาตรวจสอบเลขประจำตัวประชาชนของท่านให้ถูกต้อง
|
||||
เนื่องจากเลขประจำตัวประชาชนของท่านจะใช้อ้างอิงตลอดการสอบ
|
||||
</div>
|
||||
<div class="text-grey-7 q-pb-md">
|
||||
4. ชื่อ และนามสกุล ที่ท่านกรอกลงใน "ใบสมัครออนไลน์"
|
||||
จะต้องเป็นชื่อที่ตรงกับบัตรประจำตัวประชาชน
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-center q-pa-md">
|
||||
<q-btn color="primary" class="q-ml-md" label="ต่อไป" @click="applyCandidate" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
|
||||
const props = defineProps({
|
||||
fetchStep: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai } = mixin
|
||||
const exam = ref<any>({
|
||||
contentItemId: null,
|
||||
displayText: 'การสอบภาค ข.พิเศษ สำหรับผู้สอบผ่านของ่วนราชการแล้ว',
|
||||
description: 'ครั้งที่2',
|
||||
createdUtc: new Date(),
|
||||
budgetYear: 2566,
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
fee: 200,
|
||||
document: [
|
||||
{
|
||||
contentItemId: 111,
|
||||
file: '111',
|
||||
displayText: 'เอกสารเพิ่มเติมเกี่ยวกับการสอบ'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const applyCandidate = () => {
|
||||
props.fetchStep()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
103
src/modules/01_exam/components/ExamFinished.vue
Normal file
103
src/modules/01_exam/components/ExamFinished.vue
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="q-pa-md row items-center justify-center">
|
||||
<q-card class="my-card" style="max-width: 600px; width: 100%" bordered>
|
||||
<q-card-actions class="q-pa-md" :class="getClass(status)">
|
||||
<div v-if="status == false" class="text-black text-bold">
|
||||
เจ้าหน้าที่กำลังตรวจสถานที่สอบ
|
||||
</div>
|
||||
<div v-else class="text-black text-bold">สมัครสอบสำเร็จ</div>
|
||||
<q-space />
|
||||
<q-btn size="12px" flat round color="primary" @click="download" icon="mdi-download">
|
||||
<q-tooltip>พิมพ์บัตรประจำตัวผู้สอบ</q-tooltip>
|
||||
</q-btn>
|
||||
</q-card-actions>
|
||||
|
||||
<q-separator />
|
||||
|
||||
<q-card-section horizontal>
|
||||
<q-card-section class="col-8 q-pt-xs">
|
||||
<div class="text-bold">บัตรประจำตัวผู้สอบ</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">ชื่อ :</div>
|
||||
<div class="text-black text-bold">{{ fullName }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">เลขประจำตัวสอบ :</div>
|
||||
<div class="text-black text-bold">{{ examNumber }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">เลขประจำตัวประชาชน :</div>
|
||||
<div class="">{{ citizenId }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">เวลาสอบ :</div>
|
||||
<div class="">{{ examTime }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">สถานที่สอบ :</div>
|
||||
<div class="">{{ examLocation }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">ชั้นที่ :</div>
|
||||
<div class="">{{ floor }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">ห้องสอบ :</div>
|
||||
<div class="">{{ examRoom }}</div>
|
||||
</div>
|
||||
<div class="q-pt-xs row">
|
||||
<div class="">เลขที่นั่ง :</div>
|
||||
<div class="">{{ seatNumber }}</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="col-4 flex flex-center">
|
||||
<q-img
|
||||
class="rounded-borders"
|
||||
src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8cGVyc29ufGVufDB8fDB8fA%3D%3D&w=1000&q=80"
|
||||
:ratio="1"
|
||||
style="max-width: 300px; max-height: 300px"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<!-- <q-btn color="positive" @click="testChangeStatus">จัดที่นั่ง</q-btn> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
fetchStep: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const fullName = ref<string>('นางพิมพ์ภา วงศ์สวัสดิ์')
|
||||
const examNumber = ref<string>('CDE-004')
|
||||
const citizenId = ref<string>('1100700954521')
|
||||
const examTime = ref<string>('09:00-12:00')
|
||||
const examLocation = ref<string>('ศูนย์สอบ 01 กรุงเทพฯและนนทบุรี')
|
||||
const floor = ref<string>('4')
|
||||
const examRoom = ref<string>('CB-706')
|
||||
const seatNumber = ref<string>('75')
|
||||
const status = ref<boolean>(true)
|
||||
|
||||
const download = () => {}
|
||||
const testChangeStatus = () => {
|
||||
status.value = true
|
||||
}
|
||||
const getClass = (val: boolean) => {
|
||||
return val == true ? 'bg-green-4' : 'bg-yellow-3'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
194
src/modules/01_exam/components/ExamForm.vue
Normal file
194
src/modules/01_exam/components/ExamForm.vue
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<!-- step กรอกข้อมูล -->
|
||||
<template>
|
||||
<div>
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
dense
|
||||
class=""
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
align="justify"
|
||||
@update:model-value="changeTab"
|
||||
>
|
||||
<q-tab name="profile" label="ข้อมูลส่วนบุคคล" />
|
||||
<q-tab name="education" label="ประวัติการศีกษา" />
|
||||
<q-tab name="career" label="ประวัติการทำงาน/ฝึกงาน" />
|
||||
<q-tab name="document" label="เอกสาร" />
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="tab" animated class="bg-white">
|
||||
<!-- ข้อมูลส่วนบุคคล -->
|
||||
<q-tab-panel name="profile">
|
||||
<Profile :loader="loader" v-model:statusEdit="statusEdit" :step="step" />
|
||||
</q-tab-panel>
|
||||
|
||||
<!-- ประวัติการศีกษา -->
|
||||
<q-tab-panel name="education"
|
||||
><Education :loader="loader" v-model:statusEdit="statusEdit" :step="step"
|
||||
/></q-tab-panel>
|
||||
|
||||
<!-- ประวัติการทำงาน/ฝึกงาน -->
|
||||
<q-tab-panel name="career"
|
||||
><Career :loader="loader" v-model:statusEdit="statusEdit" :step="step"
|
||||
/></q-tab-panel>
|
||||
|
||||
<!-- ไฟล์ -->
|
||||
<q-tab-panel name="document">
|
||||
<Document :loader="loader" v-model:statusEdit="statusEdit" :step="step" />
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
|
||||
<div>
|
||||
<q-checkbox
|
||||
v-model="acceptTermOfUse"
|
||||
:disable="step !== 2"
|
||||
v-if="step !== 2 || tab == 'document'"
|
||||
label="ข้าพเจ้ารับรองว่า ข้าพเจ้ามีคุณสมบัติครบถ้วนตามประกาศรับสมัคร ข้อความข้างต้นตรงตามความจริงทุกประการ"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row justify-center q-pa-md">
|
||||
<q-btn
|
||||
color="primary"
|
||||
class="q-ml-md"
|
||||
label="กลับ"
|
||||
@click="clickPreview(tab)"
|
||||
v-if="step === 2 && (tab == 'education' || tab == 'career' || tab == 'document')"
|
||||
icon="mdi-chevron-left"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
class="q-ml-md"
|
||||
label="ต่อไป"
|
||||
@click="clickNext(tab)"
|
||||
v-if="step === 2 && (tab == 'profile' || tab == 'education' || tab == 'career')"
|
||||
icon-right="mdi-chevron-right"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
class="q-ml-md"
|
||||
label="สมัครสอบ"
|
||||
@click="okModalComfirm"
|
||||
v-if="step === 2 && tab == 'document'"
|
||||
:disable="!acceptTermOfUse"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<NotifyConfirm
|
||||
:modal="modalComfirm"
|
||||
:modalTittle="modalComfirmTittle"
|
||||
:modalDetail="modalComfirmDetail"
|
||||
:ok="applyCandidate"
|
||||
:cancel="cancelModalComfirm"
|
||||
/>
|
||||
<NotifyError
|
||||
:modalError="modalNoEdit"
|
||||
:modalErrorTittle="modalNoEditTittle"
|
||||
:modalErrorDetail="modalNoEditDetail"
|
||||
:close="closeModalError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import Profile from '@/modules/01_exam/components/Form/Profile.vue'
|
||||
import Education from '@/modules/01_exam/components/Form/Education.vue'
|
||||
import Career from '@/modules/01_exam/components/Form/Career.vue'
|
||||
import Document from '@/modules/01_exam/components/Form/Document.vue'
|
||||
import NotifyError from '@/components/NotifyError.vue'
|
||||
import NotifyConfirm from '@/components/NotifyConfirm.vue'
|
||||
|
||||
const props = defineProps({
|
||||
fetchStep: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const tab = ref<string>('profile')
|
||||
const tabRaw = ref<string>('profile')
|
||||
const loader = ref<boolean>(false)
|
||||
const statusEdit = ref<boolean>(false)
|
||||
const acceptTermOfUse = ref<boolean>(false)
|
||||
const modalComfirm = ref<boolean>(false)
|
||||
const modalComfirmTittle = ref<string>('ยืนยันการสมัครสอบ?')
|
||||
const modalComfirmDetail = ref<string>('เมื่อยืนยันการสมัครสอบแล้วจะไม่สามารถแก้ไขข้อมูลได้')
|
||||
const modalNoEdit = ref<boolean>(false)
|
||||
const modalNoEditTittle = ref<string>('ไม่สามารถเปลี่ยนแท็ปได้?')
|
||||
const modalNoEditDetail = ref<string>('มีข้อมูลที่ยังไม่ถูกบันทึกข้อมูล')
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.step > 2) {
|
||||
acceptTermOfUse.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const okModalComfirm = () => {
|
||||
if (statusEdit.value == true) {
|
||||
modalNoEditTittle.value = 'ไม่สามารถสมัครสอบได้?'
|
||||
modalNoEditDetail.value = 'มีข้อมูลที่ยังไม่ถูกบันทึกข้อมูล'
|
||||
modalNoEdit.value = true
|
||||
} else {
|
||||
modalComfirm.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const cancelModalComfirm = () => {
|
||||
modalComfirm.value = false
|
||||
}
|
||||
|
||||
const closeModalError = () => {
|
||||
modalNoEdit.value = false
|
||||
}
|
||||
|
||||
const applyCandidate = () => {
|
||||
props.fetchStep()
|
||||
}
|
||||
|
||||
const clickPreview = (val: string) => {
|
||||
console.log(val)
|
||||
switch (val) {
|
||||
case 'profile':
|
||||
return (tab.value = 'profile')
|
||||
case 'education':
|
||||
return (tab.value = 'profile')
|
||||
case 'career':
|
||||
return (tab.value = 'education')
|
||||
case 'document':
|
||||
return (tab.value = 'career')
|
||||
default:
|
||||
return (tab.value = 'profile')
|
||||
}
|
||||
}
|
||||
|
||||
const clickNext = (val: string) => {
|
||||
switch (val) {
|
||||
case 'profile':
|
||||
return (tab.value = 'education')
|
||||
case 'education':
|
||||
return (tab.value = 'career')
|
||||
case 'career':
|
||||
return (tab.value = 'document')
|
||||
case 'document':
|
||||
return (tab.value = 'profile')
|
||||
default:
|
||||
return (tab.value = 'profile')
|
||||
}
|
||||
}
|
||||
|
||||
const changeTab = () => {
|
||||
if (statusEdit.value == true) {
|
||||
modalNoEditTittle.value = 'ไม่สามารถเปลี่ยนแท็ปได้?'
|
||||
modalNoEditDetail.value = 'มีข้อมูลที่ยังไม่ถูกบันทึกข้อมูล'
|
||||
tab.value = tabRaw.value
|
||||
modalNoEdit.value = true
|
||||
} else {
|
||||
tabRaw.value = tab.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
131
src/modules/01_exam/components/ExamPayment.vue
Normal file
131
src/modules/01_exam/components/ExamPayment.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<q-card class="my-card q-mb-sm" bordered>
|
||||
<q-card-actions class="q-pa-md text-left row" :class="getClass(status)">
|
||||
<div class="text-black text-bold col-12" style="font-size: 16px">
|
||||
{{ message(status) }}
|
||||
</div>
|
||||
<div class="text-black col-12" v-if="status === 'rejected'">
|
||||
<li>{{ rejectMessage }}</li>
|
||||
</div>
|
||||
<!-- <q-space /> -->
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
|
||||
<div class="d-flex justify-content-center align-items-center row col-12">
|
||||
<!-- <div class="text-center q-pb-lg">
|
||||
<q-badge
|
||||
:outline="badgeOutline"
|
||||
:color="badgeColor"
|
||||
:label="badgeLabel"
|
||||
class="q-pa-nond text-center"
|
||||
style="font-size: 16px"
|
||||
/>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="row q-pa-nond text-center col-6"> -->
|
||||
<!-- <q-img :src="img" fit="contain" style="max-height: 300px" class="col-6">
|
||||
<div class="absolute-bottom text-center">หลักฐานชำระเงิน</div>
|
||||
</q-img> -->
|
||||
<q-file
|
||||
borderless
|
||||
v-model="fileData"
|
||||
stack-label
|
||||
@update:model-value="uploadImg"
|
||||
class="col-12"
|
||||
>
|
||||
<!-- <q-img src="@/assets/avatar_user.jpg" class="col-12">
|
||||
<div class="overlay" v-if="edit">
|
||||
<q-icon name="mdi-camera" />
|
||||
<br />อัปเดต
|
||||
</div>
|
||||
</q-img> -->
|
||||
<q-img :src="img" fit="contain" style="max-height: 300px" class="col-12">
|
||||
<div class="absolute-bottom text-center">หลักฐานชำระเงิน</div>
|
||||
</q-img>
|
||||
</q-file>
|
||||
<!-- <q-uploader
|
||||
ref="uploader"
|
||||
type="file"
|
||||
:factory="uploadImg"
|
||||
class="col-6"
|
||||
label="กรุณาอัปโหลดหลักฐานการชำระเงิน"
|
||||
flat
|
||||
color="blue"
|
||||
bordered
|
||||
/> -->
|
||||
<!-- </div> -->
|
||||
<div class="q-pa-md text-center col-12">
|
||||
<q-btn color="primary" @click="setStatus('processing')" label="ส่งหลักฐานการชำระเงิน" />
|
||||
</div>
|
||||
<div class="text-black text-center q-pb-lg col-12">
|
||||
***ถ้าต้องการเเก้ไขหลักฐานการโอนเงินกรุณาอัพโหลดซ้ำ***
|
||||
</div>
|
||||
<q-btn color="negative" @click="setStatus('rejected')">Re</q-btn>
|
||||
<q-btn color="positive" @click="setStatus('success')">Su</q-btn>
|
||||
<q-btn color="positive" @click="setStatus('next')">Ne</q-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
fetchStep: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const status = ref<string>('')
|
||||
const rejectMessage = ref<string>('กรุณาจ่ายเงินให้ครบตามจำนวน')
|
||||
const img = ref<string>('https://cdn-icons-png.flaticon.com/512/2496/2496846.png')
|
||||
const fileData = ref<any>()
|
||||
|
||||
const uploadImg = (file: any) => {
|
||||
fileData.value = null
|
||||
// img.value =
|
||||
// 'https://s359.kapook.com/r/600/auto/pagebuilder/ba154685-db18-4ac7-b318-a4a2b15b9d4c.jpg'
|
||||
img.value =
|
||||
'https://www.bangkokbank.com/-/media/feature/page-content/bbl-corporate/image-carousel-slides/digital-banking/bualuang-mbanking/how-to-use/payment/others/7_en.png'
|
||||
}
|
||||
|
||||
const setStatus = (val: string) => {
|
||||
status.value = val
|
||||
if (val == 'next') props.fetchStep()
|
||||
}
|
||||
|
||||
const getClass = (val: string) => {
|
||||
switch (val) {
|
||||
case 'processing':
|
||||
return 'bg-lime-12'
|
||||
case 'rejected':
|
||||
return 'bg-deep-orange-11'
|
||||
case 'success':
|
||||
return 'bg-green-12'
|
||||
default:
|
||||
return 'bg-blue-4'
|
||||
}
|
||||
}
|
||||
const message = (val: string) => {
|
||||
switch (val) {
|
||||
case 'processing':
|
||||
return 'รอการตรวจสอบ'
|
||||
case 'rejected':
|
||||
return 'หลักฐานการชำระเงินผิดพลาด'
|
||||
case 'success':
|
||||
return 'ตรวจสอบเเล้ว'
|
||||
default:
|
||||
return 'รออัปโหลดหลักฐานชำระเงิน'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
632
src/modules/01_exam/components/Form/Career.vue
Normal file
632
src/modules/01_exam/components/Form/Career.vue
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
<!-- tab ประวัติการทำงาน/ฝึกงาน -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md">
|
||||
<q-form ref="myForm">
|
||||
<Table
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
:filter="filter"
|
||||
:visible-columns="visibleColumns"
|
||||
v-model:inputfilter="filter"
|
||||
v-model:inputvisible="visibleColumns"
|
||||
v-model:editvisible="edit"
|
||||
:add="clickAdd"
|
||||
:edit="clickEdit"
|
||||
:cancel="clickCancel"
|
||||
:nameHeader="false"
|
||||
:addData="addData"
|
||||
>
|
||||
<template #columns="props">
|
||||
<q-tr :props="props">
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
@click="selectData(props)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<div v-if="col.name == 'salary'" class="">
|
||||
{{ col.value.toLocaleString('en-US') }}
|
||||
</div>
|
||||
<div v-else-if="col.name == 'duration'" class="">
|
||||
{{ dateThaiRange(col.value) }}
|
||||
</div>
|
||||
<div v-else class="">
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td auto-width v-if="edit === true">
|
||||
<q-btn
|
||||
color="red"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
size="14px"
|
||||
icon="mdi-trash-can-outline"
|
||||
@click="clickDeleteRow(props.row)"
|
||||
/>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</Table>
|
||||
</q-form>
|
||||
</q-card>
|
||||
<!-- popup Edit window-->
|
||||
<q-dialog v-model="modal" persistent>
|
||||
<q-card style="width: 600px">
|
||||
<q-form ref="myForm">
|
||||
<DialogHeader tittle="ประวัติการทำงาน/ฝึกงาน" :close="clickClose" />
|
||||
<q-separator />
|
||||
<q-card-section class="q-p-sm">
|
||||
<div class="row col-12 items-center q-col-gutter-x-xs q-col-gutter-y-xs">
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="location"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกสถานที่ทำงาน/ฝึกงาน'}`]"
|
||||
:label="`${'สถานที่ทำงาน/ฝึกงาน'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="position"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกตำแหน่ง/ลักษณะงาน'}`]"
|
||||
:label="`${'ตำแหน่ง/ลักษณะงาน'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="salary"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกเงินเดือนสุดท้ายก่อนออก'}`]"
|
||||
:label="`${'เงินเดือนสุดท้ายก่อนออก'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<datepicker
|
||||
:readonly="!edit"
|
||||
v-model="duration"
|
||||
:locale="'th'"
|
||||
autoApply
|
||||
range
|
||||
:enableTimePicker="false"
|
||||
week-start="0"
|
||||
>
|
||||
<template #year="{ year }">
|
||||
{{ year + 543 }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ parseInt(value + 543) }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
class="datepicker"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:borderless="!edit"
|
||||
:model-value="dateThaiRange(duration)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="event" class="cursor-pointer" style="color: var(--q-primary)">
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</datepicker>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="reason"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกเหตุผลที่ออก'}`]"
|
||||
:label="`${'เหตุผลที่ออก'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
type="textarea"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<DialogFooter
|
||||
:cancel="clickCancel"
|
||||
:edit="clickEdit"
|
||||
:save="clickSave"
|
||||
:validate="validateData"
|
||||
:clickNext="clickNext"
|
||||
:clickPrevious="clickPrevious"
|
||||
:editBtn="addData"
|
||||
v-model:editvisible="edit"
|
||||
v-model:next="next"
|
||||
v-model:previous="previous"
|
||||
v-model:modalEdit="modalEdit"
|
||||
/>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<notifyError
|
||||
:modalError="modalError"
|
||||
:modalErrorTittle="modalErrorTittle"
|
||||
:modalErrorDetail="modalErrorDetail"
|
||||
:close="closeModalError"
|
||||
></notifyError>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useExamDataStore } from '@/modules/01_exam/store'
|
||||
import Table from '@/components/Table.vue'
|
||||
import DialogHeader from '@/components/DialogHeader.vue'
|
||||
import DialogFooter from '@/components/DialogFooter.vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
import type {
|
||||
RequestItemsObject,
|
||||
Columns,
|
||||
DataProps
|
||||
} from '@/modules/01_exam/interface/request/Career'
|
||||
import type { ResponseObject } from '@/modules/01_exam/interface/response/Career'
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
loader: {
|
||||
//หน้า main มีการอัพเดทค่าให้ refresh data
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const $q = useQuasar()
|
||||
const mixin = useCounterMixin() //เรียกฟังก์ชันกลาง
|
||||
const { dateThaiRange } = mixin
|
||||
const store = useExamDataStore()
|
||||
const { examData, changeExamColumns } = store
|
||||
const loader = ref<boolean>(false)
|
||||
const id = ref<string>()
|
||||
const location = ref<string>()
|
||||
const position = ref<string>()
|
||||
const salary = ref<number | null>()
|
||||
const duration = ref<[Date, Date]>([new Date(), new Date()])
|
||||
const reason = ref<string>()
|
||||
const myForm = ref<any>() //form data input
|
||||
const edit = ref<boolean>(false) //เช็คการกดปุ่มแก้ไขใน dialog
|
||||
const modal = ref<boolean>(false) //modal add detail
|
||||
const modalEdit = ref<boolean>(false) //modal ที่แสดงใช้สำหรับแก้ไขหรือไม่
|
||||
const rawItem = ref<RequestItemsObject>() //ข้อมูลเดิมที่เลือกใน row นั้น
|
||||
const rowIndex = ref<number>(0) //indexข้อมูลเดิมที่เลือกใน row นั้น
|
||||
const previous = ref<boolean>() //แสดงปุ่มดูข้อมูลก่อนหน้า
|
||||
const next = ref<boolean>() //แสดงปุ่มดูข้อมูลต่อไป
|
||||
const editRow = ref<boolean>(false) //เช็คมีการแก้ไขข้อมูล
|
||||
const addData = ref<boolean>(true)
|
||||
const rawHistory = ref<RequestItemsObject[]>([]) //raw data history
|
||||
const modalError = ref<boolean>(false) // modal สำหรับแจ้งเตือนerror
|
||||
const modalErrorTittle = ref<string>('') // tittle modal error
|
||||
const modalErrorDetail = ref<string>('') // detail modal error
|
||||
const statusCode = ref<number>()
|
||||
const checkValidate = ref<boolean>(false) //validate data ผ่านหรือไม่
|
||||
const closeModalError = () => {
|
||||
modalError.value = false
|
||||
if (statusCode.value != 404) {
|
||||
// fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:loader', 'update:statusEdit'])
|
||||
|
||||
const rows = ref<RequestItemsObject[]>([
|
||||
{
|
||||
id: '1',
|
||||
location: 'แอดวานซ์ อินโฟร์ เซอร์วิส',
|
||||
position: 'Direct Sales Staff',
|
||||
salary: 30000,
|
||||
duration: [new Date('1995-04-15'), new Date('1999-02-25')],
|
||||
reason: 'ไล่ออก'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
location: 'บริษัท ทรู คอร์ปอเรชั่น จำกัด (มหาชน)',
|
||||
position: 'Direct Sales Staff',
|
||||
salary: 40000,
|
||||
duration: [new Date('2020-06-30'), new Date('2023-10-14')],
|
||||
reason: '-'
|
||||
}
|
||||
])
|
||||
|
||||
const filter = ref<string>('') //search data table
|
||||
|
||||
const visibleColumns = ref<String[]>([])
|
||||
|
||||
examData.career.columns.length == 0
|
||||
? (visibleColumns.value = ['location', 'position', 'salary', 'duration', 'reason'])
|
||||
: (visibleColumns.value = examData.career.columns)
|
||||
|
||||
const columns = ref<Columns>([
|
||||
{
|
||||
name: 'location',
|
||||
align: 'left',
|
||||
label: 'สถานที่ทำงาน/ฝึกงาน',
|
||||
sortable: true,
|
||||
field: 'location',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
align: 'left',
|
||||
label: 'ตำแหน่ง/ลักษณะงาน',
|
||||
sortable: true,
|
||||
field: 'position',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'salary',
|
||||
align: 'left',
|
||||
label: 'เงินเดือนสุดท้ายก่อนออก',
|
||||
sortable: true,
|
||||
field: 'salary',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
align: 'left',
|
||||
label: 'ระยะเวลา',
|
||||
sortable: true,
|
||||
field: 'duration',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'reason',
|
||||
align: 'left',
|
||||
label: 'เหตุผลที่ออก',
|
||||
sortable: true,
|
||||
field: 'reason',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
}
|
||||
])
|
||||
|
||||
watch(loader, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:loader', count)
|
||||
})
|
||||
|
||||
watch(visibleColumns, async (count: String[], prevCount: String[]) => {
|
||||
await changeExamColumns('career', count)
|
||||
})
|
||||
|
||||
watch(edit, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:statusEdit', count)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// await fetchData()
|
||||
rawHistory.value = rows.value
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const fetchData = async () => {}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลก่อนหน้า
|
||||
*/
|
||||
const clickPrevious = () => {
|
||||
// edit.value = false
|
||||
rowIndex.value -= 1
|
||||
const row = rows.value[rowIndex.value]
|
||||
location.value = row.location
|
||||
position.value = row.position
|
||||
salary.value = row.salary
|
||||
duration.value = row.duration
|
||||
reason.value = row.reason
|
||||
id.value = row.id
|
||||
checkRowPage()
|
||||
}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลต่อไป
|
||||
*/
|
||||
const clickNext = () => {
|
||||
// edit.value = false
|
||||
rowIndex.value += 1
|
||||
const row = rows.value[rowIndex.value]
|
||||
location.value = row.location
|
||||
position.value = row.position
|
||||
salary.value = row.salary
|
||||
duration.value = row.duration
|
||||
reason.value = row.reason
|
||||
id.value = row.id
|
||||
checkRowPage()
|
||||
}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลต่อไป
|
||||
*/
|
||||
const getData = () => {
|
||||
const row = rows.value[rowIndex.value]
|
||||
location.value = row.location
|
||||
position.value = row.position
|
||||
salary.value = row.salary
|
||||
duration.value = row.duration
|
||||
reason.value = row.reason
|
||||
id.value = row.id
|
||||
}
|
||||
|
||||
/**
|
||||
* เช็คปุ่มดูข้อมูล ย้อน กับ ต่อไป ว่าต้องแสดงไหม
|
||||
*/
|
||||
const checkRowPage = () => {
|
||||
editRow.value = false
|
||||
next.value = true
|
||||
previous.value = true
|
||||
if (rowIndex.value + 1 >= rows.value.length) {
|
||||
next.value = false
|
||||
}
|
||||
if (rowIndex.value - 1 < 0) {
|
||||
previous.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มแก้ไขใน dialog
|
||||
*/
|
||||
const clickEdit = () => {
|
||||
edit.value = true
|
||||
next.value = false
|
||||
previous.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มเพิ่มด้านบน table
|
||||
*/
|
||||
const clickAdd = () => {
|
||||
addRow()
|
||||
}
|
||||
|
||||
/**
|
||||
* ลบข้อมูลใน table
|
||||
*/
|
||||
const clickDeleteRow = (row: RequestItemsObject) => {
|
||||
$q.dialog({
|
||||
title: 'ยืนยันการลบข้อมูล',
|
||||
message: 'หากต้องการลบกดให้กดตกลง',
|
||||
cancel: true,
|
||||
persistent: true
|
||||
})
|
||||
.onOk(() => {
|
||||
rows.value = rows.value.filter((val: any) => val.id != row.id)
|
||||
})
|
||||
.onCancel(() => {})
|
||||
.onDismiss(() => {})
|
||||
}
|
||||
|
||||
/**
|
||||
* กดบันทึกใน dialog
|
||||
*/
|
||||
const clickSave = async () => {
|
||||
myForm.value.validate().then(async (result: boolean) => {
|
||||
if (result) {
|
||||
if (modalEdit.value) {
|
||||
await editData()
|
||||
} else {
|
||||
await saveData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* บันทึกเพิ่มข้อมูล
|
||||
*/
|
||||
const saveData = async () => {
|
||||
modal.value = false
|
||||
|
||||
// loader.value = true;
|
||||
// await http
|
||||
// .post(config.API.xxxxxxxxxxxxxxx, {
|
||||
// xxx: "xxx",
|
||||
// })
|
||||
// .then((res) => {
|
||||
// success($q, "บันทึกข้อมูลร่างสำเร็จ");
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// modalError.value = true;
|
||||
// modalErrorTittle.value = "ไม่สามารถบันทึกข้อมูลร่างได้";
|
||||
// modalErrorDetail.value = e.response.data.message;
|
||||
// statusCode.value = e.response.data.status;
|
||||
// })
|
||||
// .finally(async () => {
|
||||
// modal.value = false;
|
||||
// await fetchData();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* บันทึกแก้ไขข้อมูล
|
||||
*/
|
||||
const editData = async () => {
|
||||
// edit.value = false
|
||||
editRow.value = false
|
||||
|
||||
// loader.value = true;
|
||||
// await http
|
||||
// .post(config.API.xxxxxxxxxxxxxxx(id.value), {
|
||||
// xxx: "xxx",
|
||||
// })
|
||||
// .then((res) => {
|
||||
// success($q, "บันทึกข้อมูลร่างสำเร็จ");
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// modalError.value = true;
|
||||
// modalErrorTittle.value = "ไม่สามารถบันทึกข้อมูลร่างได้";
|
||||
// modalErrorDetail.value = e.response.data.message;
|
||||
// statusCode.value = e.response.data.status;
|
||||
// })
|
||||
// .finally(async () => {
|
||||
// edit.value = false;
|
||||
// await fetchData();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปิด dialog
|
||||
*/
|
||||
const clickClose = async () => {
|
||||
if (editRow.value == true) {
|
||||
$q.dialog({
|
||||
title: `ข้อมูลมีการแก้ไข`,
|
||||
message: `ยืนยันการบันทึกข้อมูลใช่หรือไม่?`,
|
||||
cancel: 'ยกเลิก',
|
||||
ok: 'ยืนยัน',
|
||||
persistent: true
|
||||
}).onOk(async () => {
|
||||
modal.value = false
|
||||
next.value = false
|
||||
previous.value = false
|
||||
await getData()
|
||||
})
|
||||
} else {
|
||||
modal.value = false
|
||||
next.value = false
|
||||
previous.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดเลือกข้อมูลที่จะแก้ไข
|
||||
* @param props ค่า props ใน row ที่เลือก
|
||||
*/
|
||||
const selectData = (props: DataProps) => {
|
||||
modalEdit.value = true
|
||||
modal.value = true
|
||||
// edit.value = false
|
||||
rawItem.value = props.row
|
||||
rowIndex.value = props.rowIndex
|
||||
location.value = props.row.location
|
||||
position.value = props.row.position
|
||||
salary.value = props.row.salary
|
||||
duration.value = props.row.duration
|
||||
reason.value = props.row.reason
|
||||
id.value = props.row.id
|
||||
if (edit.value == true) {
|
||||
next.value = false
|
||||
previous.value = false
|
||||
} else {
|
||||
checkRowPage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มเพิ่มบน table
|
||||
*/
|
||||
const addRow = () => {
|
||||
modalEdit.value = false
|
||||
modal.value = true
|
||||
// edit.value = true
|
||||
location.value = ''
|
||||
position.value = ''
|
||||
salary.value = null
|
||||
duration.value = [new Date(), new Date()]
|
||||
reason.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* ฟังก์ชันปุ่มยกเลิกการแก้ไขข้อมูล
|
||||
*/
|
||||
const clickCancel = async () => {
|
||||
// if (editRow.value == true) {
|
||||
// $q.dialog({
|
||||
// title: `ข้อมูลมีการแก้ไข`,
|
||||
// message: `ยืนยันการบันทึกข้อมูลใช่หรือไม่?`,
|
||||
// cancel: 'ยกเลิก',
|
||||
// ok: 'ยืนยัน',
|
||||
// persistent: true
|
||||
// }).onOk(async () => {
|
||||
// // edit.value = false
|
||||
// checkRowPage()
|
||||
// await getData()
|
||||
// })
|
||||
// } else {
|
||||
// // edit.value = false
|
||||
// checkRowPage()
|
||||
// }
|
||||
edit.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* เช็คว่ามีการแก้ไขข้อมูล
|
||||
*/
|
||||
const clickEditRow = () => {
|
||||
editRow.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* validate input ใน dialog
|
||||
*/
|
||||
const validateData = async () => {
|
||||
checkValidate.value = true
|
||||
await myForm.value.validate().then((result: boolean) => {
|
||||
if (result == false) {
|
||||
checkValidate.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* class จัดรูปแบบแสดงระหว่างข้อมูลที่แก้ไขหรือแสดงเฉยๆ
|
||||
* @param val ข้อมูล input สำหรับแก้ไขหรือไม่
|
||||
*/
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.modalfix {
|
||||
position: fixed !important;
|
||||
}
|
||||
</style>
|
||||
134
src/modules/01_exam/components/Form/Document.vue
Normal file
134
src/modules/01_exam/components/Form/Document.vue
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<!-- card เอกสารหลักฐาน -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md">
|
||||
<HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="เอกสารหลักฐาน(เช่น สำเนาบัตรประชาชน ทะเบียนบ้าน วุฒิการศึกษา)"
|
||||
icon="mdi-file-document"
|
||||
:history="false"
|
||||
:addData="addData"
|
||||
:cancel="cancelData"
|
||||
/>
|
||||
<div class="row col-12 q-gutter-sm q-pt-sm">
|
||||
<q-card bordered flat class="full-width">
|
||||
<q-list separator>
|
||||
<q-item v-for="file in files" :key="file.key" class="q-my-xs">
|
||||
<q-item-section>
|
||||
<q-item-label class="full-width ellipsis">
|
||||
{{ file.name }}
|
||||
</q-item-label>
|
||||
|
||||
<q-item-label caption> สถานะ: {{ file.status }} / {{ file.sizeLabel }} </q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section top side>
|
||||
<div class="q-gutter-sm">
|
||||
<q-btn size="12px" flat dense round color="blue" icon="mdi-download-outline">
|
||||
<q-tooltip>ดาวน์โหลด</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
size="12px"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
color="red"
|
||||
icon="mdi-delete-outline"
|
||||
v-if="edit"
|
||||
>
|
||||
<q-tooltip>ลบไฟล์</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<div v-if="edit" class="col-12 q-ma-none">
|
||||
<q-separator class="q-mt-sm" />
|
||||
<q-input
|
||||
class="q-my-sm"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="name"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกชื่อเอกสาร'}`]"
|
||||
:label="`${'ชื่อเอกสาร'}`"
|
||||
/>
|
||||
<q-uploader
|
||||
color="blue"
|
||||
type="file"
|
||||
flat
|
||||
auto-upload
|
||||
:factory="fileUpload"
|
||||
class="full-width"
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
bordered
|
||||
label="[ไฟล์ jpg,png,pdf,csv,doc]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
|
||||
const props = defineProps({
|
||||
loader: {
|
||||
//หน้า main มีการอัพเดทค่าให้ refresh data
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
notiNoEdit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const edit = ref<boolean>(false)
|
||||
const addData = ref<boolean>(true)
|
||||
const name = ref<string>('')
|
||||
const files = ref<any>([
|
||||
{
|
||||
key: 1,
|
||||
name: 'เอกสารข้อมูลการเปลี่ยนชื่อ',
|
||||
status: 'อัปโหลดเสร็จสิ้น',
|
||||
sizeLabel: '176MB'
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: 'เอกสารข้อมูลการสมรส',
|
||||
status: 'อัปโหลดเสร็จสิ้น',
|
||||
sizeLabel: '89MB'
|
||||
}
|
||||
])
|
||||
const emit = defineEmits(['update:loader', 'update:statusEdit'])
|
||||
|
||||
watch(edit, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:statusEdit', count)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const fileUpload = async (file: any) => {
|
||||
return {
|
||||
url: 'http://localhost:4444/upload',
|
||||
method: 'POST'
|
||||
}
|
||||
}
|
||||
|
||||
const cancelData = () => {}
|
||||
</script>
|
||||
661
src/modules/01_exam/components/Form/Education.vue
Normal file
661
src/modules/01_exam/components/Form/Education.vue
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
<!-- tab ประวัติการศึกษา -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md">
|
||||
<q-form ref="myForm">
|
||||
<Table
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
:filter="filter"
|
||||
:visible-columns="visibleColumns"
|
||||
v-model:inputfilter="filter"
|
||||
v-model:inputvisible="visibleColumns"
|
||||
v-model:editvisible="edit"
|
||||
:add="clickAdd"
|
||||
:edit="clickEdit"
|
||||
:cancel="clickCancel"
|
||||
:nameHeader="false"
|
||||
:addData="addData"
|
||||
>
|
||||
<template #columns="props">
|
||||
<q-tr :props="props">
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
@click="selectData(props)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<div v-if="col.name == 'duration'" class="">
|
||||
{{ dateThaiRange(col.value) }}
|
||||
</div>
|
||||
<div v-else class="">
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td auto-width v-if="edit === true">
|
||||
<q-btn
|
||||
color="red"
|
||||
flat
|
||||
dense
|
||||
round
|
||||
size="14px"
|
||||
icon="mdi-trash-can-outline"
|
||||
@click="clickDeleteRow(props.row)"
|
||||
/>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</Table>
|
||||
</q-form>
|
||||
</q-card>
|
||||
<!-- popup Edit window-->
|
||||
<q-dialog v-model="modal" persistent>
|
||||
<q-card style="width: 600px">
|
||||
<q-form ref="myForm">
|
||||
<DialogHeader tittle="ประวัติการศึกษา" :close="clickClose" />
|
||||
<q-separator />
|
||||
<q-card-section class="q-p-sm">
|
||||
<div class="row col-12 items-center q-col-gutter-x-xs q-col-gutter-y-xs">
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-select
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="qualificationId"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือกวุฒิที่ได้รับ'}`]"
|
||||
:label="`${'วุฒิที่ได้รับ'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="qualificationOptions"
|
||||
option-value="id"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="major"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกสาขาวิชา/วิชาเอก'}`]"
|
||||
:label="`${'สาขาวิชา/วิชาเอก'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
type="number"
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="scores"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกคะแนนเฉลี่ยตลอดหลักสูตร'}`]"
|
||||
:label="`${'คะแนนเฉลี่ยตลอดหลักสูตร'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="name"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอกชื่อสถานศึกษา'}`]"
|
||||
:label="`${'ชื่อสถานศึกษา'}`"
|
||||
@update:modelValue="clickEditRow"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6 col-md-6">
|
||||
<datepicker
|
||||
:readonly="!edit"
|
||||
v-model="duration"
|
||||
:locale="'th'"
|
||||
autoApply
|
||||
range
|
||||
:enableTimePicker="false"
|
||||
week-start="0"
|
||||
>
|
||||
<template #year="{ year }">
|
||||
{{ year + 543 }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ parseInt(value + 543) }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
class="datepicker"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:borderless="!edit"
|
||||
:model-value="dateThaiRange(duration)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="event" class="cursor-pointer" style="color: var(--q-primary)">
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</datepicker>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<DialogFooter
|
||||
:cancel="clickCancel"
|
||||
:edit="clickEdit"
|
||||
:save="clickSave"
|
||||
:validate="validateData"
|
||||
:clickNext="clickNext"
|
||||
:clickPrevious="clickPrevious"
|
||||
:editBtn="addData"
|
||||
v-model:editvisible="edit"
|
||||
v-model:next="next"
|
||||
v-model:previous="previous"
|
||||
v-model:modalEdit="modalEdit"
|
||||
/>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<notifyError
|
||||
:modalError="modalError"
|
||||
:modalErrorTittle="modalErrorTittle"
|
||||
:modalErrorDetail="modalErrorDetail"
|
||||
:close="closeModalError"
|
||||
></notifyError>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useExamDataStore } from '@/modules/01_exam/store'
|
||||
import Table from '@/components/Table.vue'
|
||||
import DialogHeader from '@/components/DialogHeader.vue'
|
||||
import DialogFooter from '@/components/DialogFooter.vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
import type {
|
||||
RequestItemsObject,
|
||||
Columns,
|
||||
DataProps
|
||||
} from '@/modules/01_exam/interface/request/Education'
|
||||
import type { ResponseObject } from '@/modules/01_exam/interface/response/Education'
|
||||
import type { DataOption } from '@/modules/01_exam/interface/index/Main'
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
loader: {
|
||||
//หน้า main มีการอัพเดทค่าให้ refresh data
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const $q = useQuasar()
|
||||
const mixin = useCounterMixin() //เรียกฟังก์ชันกลาง
|
||||
const { dateThaiRange } = mixin
|
||||
const store = useExamDataStore()
|
||||
const { examData, changeExamColumns } = store
|
||||
const loader = ref<boolean>(false)
|
||||
const id = ref<string>()
|
||||
const qualification = ref<string>()
|
||||
const qualificationId = ref<string>()
|
||||
const qualificationOptions = ref<DataOption[]>([])
|
||||
const major = ref<string>()
|
||||
const scores = ref<number | null>()
|
||||
const name = ref<string>()
|
||||
const duration = ref<[Date, Date]>([new Date(), new Date()])
|
||||
const myForm = ref<any>() //form data input
|
||||
const edit = ref<boolean>(false) //เช็คการกดปุ่มแก้ไขใน dialog
|
||||
const modal = ref<boolean>(false) //modal add detail
|
||||
const modalEdit = ref<boolean>(false) //modal ที่แสดงใช้สำหรับแก้ไขหรือไม่
|
||||
const rawItem = ref<RequestItemsObject>() //ข้อมูลเดิมที่เลือกใน row นั้น
|
||||
const rowIndex = ref<number>(0) //indexข้อมูลเดิมที่เลือกใน row นั้น
|
||||
const previous = ref<boolean>() //แสดงปุ่มดูข้อมูลก่อนหน้า
|
||||
const next = ref<boolean>() //แสดงปุ่มดูข้อมูลต่อไป
|
||||
const editRow = ref<boolean>(false) //เช็คมีการแก้ไขข้อมูล
|
||||
const addData = ref<boolean>(true)
|
||||
const rawHistory = ref<RequestItemsObject[]>([]) //raw data history
|
||||
const modalError = ref<boolean>(false) // modal สำหรับแจ้งเตือนerror
|
||||
const modalErrorTittle = ref<string>('') // tittle modal error
|
||||
const modalErrorDetail = ref<string>('') // detail modal error
|
||||
const statusCode = ref<number>()
|
||||
const checkValidate = ref<boolean>(false) //validate data ผ่านหรือไม่
|
||||
const closeModalError = () => {
|
||||
modalError.value = false
|
||||
if (statusCode.value != 404) {
|
||||
// fetchData();
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['update:loader', 'update:statusEdit'])
|
||||
|
||||
const rows = ref<RequestItemsObject[]>([
|
||||
{
|
||||
id: '1',
|
||||
qualificationId: 'ปริญญาตรี',
|
||||
qualification: 'ปริญญาตรี',
|
||||
major: 'คอมพิวเตอร์',
|
||||
scores: 3.99,
|
||||
name: 'มหาลัยเอ',
|
||||
duration: [new Date('1995-04-15'), new Date('1999-02-25')]
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
qualificationId: 'ปริญญาเอก',
|
||||
qualification: 'ปริญญาเอก',
|
||||
major: 'คอมพิวเตอร์',
|
||||
scores: 3.54,
|
||||
name: 'มหาลัยบี',
|
||||
duration: [new Date('2020-06-30'), new Date('2023-10-14')]
|
||||
}
|
||||
])
|
||||
|
||||
const filter = ref<string>('') //search data table
|
||||
|
||||
const visibleColumns = ref<String[]>([])
|
||||
|
||||
examData.education.columns.length == 0
|
||||
? (visibleColumns.value = ['qualification', 'major', 'scores', 'name', 'duration'])
|
||||
: (visibleColumns.value = examData.education.columns)
|
||||
|
||||
const columns = ref<Columns>([
|
||||
{
|
||||
name: 'qualification',
|
||||
align: 'left',
|
||||
label: 'วุฒิที่ได้รับ',
|
||||
sortable: true,
|
||||
field: 'qualification',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'major',
|
||||
align: 'left',
|
||||
label: 'สาขาวิชา/วิชาเอก',
|
||||
sortable: true,
|
||||
field: 'major',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'scores',
|
||||
align: 'left',
|
||||
label: 'คะแนนเฉลี่ยตลอดหลักสูตร',
|
||||
sortable: true,
|
||||
field: 'scores',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
align: 'left',
|
||||
label: 'ชื่อสถานศึกษา',
|
||||
sortable: true,
|
||||
field: 'name',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
align: 'left',
|
||||
label: 'ระยะเวลา',
|
||||
sortable: true,
|
||||
field: 'duration',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
}
|
||||
])
|
||||
|
||||
watch(loader, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:loader', count)
|
||||
})
|
||||
|
||||
watch(visibleColumns, async (count: String[], prevCount: String[]) => {
|
||||
await changeExamColumns('education', count)
|
||||
})
|
||||
|
||||
watch(edit, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:statusEdit', count)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// await fetchData()
|
||||
// await fetchQualification()
|
||||
rawHistory.value = rows.value
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const fetchQualification = async () => {
|
||||
// loader.value = true;
|
||||
// await http
|
||||
// .get(config.API.educationQualification)
|
||||
// .then((res) => {
|
||||
// const data = res.data.result
|
||||
// let option: DataOption[] = []
|
||||
// data.map((r: any) => {
|
||||
// option.push({ id: r.id.toString(), name: r.name.toString() })
|
||||
// })
|
||||
// qualificationOptions.value = option
|
||||
// })
|
||||
// .catch((e: any) => {})
|
||||
// .finally(() => {
|
||||
// // loader.value = false;
|
||||
// })
|
||||
}
|
||||
|
||||
const fetchData = async () => {}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลก่อนหน้า
|
||||
*/
|
||||
const clickPrevious = () => {
|
||||
// edit.value = false
|
||||
rowIndex.value -= 1
|
||||
const row = rows.value[rowIndex.value]
|
||||
qualification.value = row.qualification
|
||||
qualificationId.value = row.qualificationId
|
||||
major.value = row.major
|
||||
scores.value = row.scores
|
||||
name.value = row.name
|
||||
duration.value = row.duration
|
||||
id.value = row.id
|
||||
checkRowPage()
|
||||
}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลต่อไป
|
||||
*/
|
||||
const clickNext = () => {
|
||||
// edit.value = false
|
||||
rowIndex.value += 1
|
||||
const row = rows.value[rowIndex.value]
|
||||
qualification.value = row.qualification
|
||||
qualificationId.value = row.qualificationId
|
||||
major.value = row.major
|
||||
scores.value = row.scores
|
||||
name.value = row.name
|
||||
duration.value = row.duration
|
||||
id.value = row.id
|
||||
checkRowPage()
|
||||
}
|
||||
|
||||
/**
|
||||
* กดดูข้อมูลต่อไป
|
||||
*/
|
||||
const getData = () => {
|
||||
const row = rows.value[rowIndex.value]
|
||||
qualification.value = row.qualification
|
||||
qualificationId.value = row.qualificationId
|
||||
major.value = row.major
|
||||
scores.value = row.scores
|
||||
name.value = row.name
|
||||
duration.value = row.duration
|
||||
id.value = row.id
|
||||
}
|
||||
|
||||
/**
|
||||
* เช็คปุ่มดูข้อมูล ย้อน กับ ต่อไป ว่าต้องแสดงไหม
|
||||
*/
|
||||
const checkRowPage = () => {
|
||||
editRow.value = false
|
||||
next.value = true
|
||||
previous.value = true
|
||||
if (rowIndex.value + 1 >= rows.value.length) {
|
||||
next.value = false
|
||||
}
|
||||
if (rowIndex.value - 1 < 0) {
|
||||
previous.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มแก้ไขใน dialog
|
||||
*/
|
||||
const clickEdit = () => {
|
||||
edit.value = true
|
||||
next.value = false
|
||||
previous.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มเพิ่มด้านบน table
|
||||
*/
|
||||
const clickAdd = () => {
|
||||
addRow()
|
||||
}
|
||||
|
||||
/**
|
||||
* ลบข้อมูลใน table
|
||||
*/
|
||||
const clickDeleteRow = (row: RequestItemsObject) => {
|
||||
$q.dialog({
|
||||
title: 'ยืนยันการลบข้อมูล',
|
||||
message: 'หากต้องการลบกดให้กดตกลง',
|
||||
cancel: true,
|
||||
persistent: true
|
||||
})
|
||||
.onOk(() => {
|
||||
rows.value = rows.value.filter((val: any) => val.id != row.id)
|
||||
})
|
||||
.onCancel(() => {})
|
||||
.onDismiss(() => {})
|
||||
}
|
||||
|
||||
/**
|
||||
* กดบันทึกใน dialog
|
||||
*/
|
||||
const clickSave = async () => {
|
||||
myForm.value.validate().then(async (result: boolean) => {
|
||||
if (result) {
|
||||
if (modalEdit.value) {
|
||||
await editData()
|
||||
} else {
|
||||
await saveData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* บันทึกเพิ่มข้อมูล
|
||||
*/
|
||||
const saveData = async () => {
|
||||
modal.value = false
|
||||
|
||||
// loader.value = true;
|
||||
// await http
|
||||
// .post(config.API.xxxxxxxxxxxxxxx, {
|
||||
// xxx: "xxx",
|
||||
// })
|
||||
// .then((res) => {
|
||||
// success($q, "บันทึกข้อมูลร่างสำเร็จ");
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// modalError.value = true;
|
||||
// modalErrorTittle.value = "ไม่สามารถบันทึกข้อมูลร่างได้";
|
||||
// modalErrorDetail.value = e.response.data.message;
|
||||
// statusCode.value = e.response.data.status;
|
||||
// })
|
||||
// .finally(async () => {
|
||||
// modal.value = false;
|
||||
// await fetchData();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* บันทึกแก้ไขข้อมูล
|
||||
*/
|
||||
const editData = async () => {
|
||||
// edit.value = false
|
||||
editRow.value = false
|
||||
|
||||
// loader.value = true;
|
||||
// await http
|
||||
// .post(config.API.xxxxxxxxxxxxxxx(id.value), {
|
||||
// xxx: "xxx",
|
||||
// })
|
||||
// .then((res) => {
|
||||
// success($q, "บันทึกข้อมูลร่างสำเร็จ");
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// modalError.value = true;
|
||||
// modalErrorTittle.value = "ไม่สามารถบันทึกข้อมูลร่างได้";
|
||||
// modalErrorDetail.value = e.response.data.message;
|
||||
// statusCode.value = e.response.data.status;
|
||||
// })
|
||||
// .finally(async () => {
|
||||
// edit.value = false;
|
||||
// await fetchData();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปิด dialog
|
||||
*/
|
||||
const clickClose = async () => {
|
||||
if (editRow.value == true) {
|
||||
$q.dialog({
|
||||
title: `ข้อมูลมีการแก้ไข`,
|
||||
message: `ยืนยันการบันทึกข้อมูลใช่หรือไม่?`,
|
||||
cancel: 'ยกเลิก',
|
||||
ok: 'ยืนยัน',
|
||||
persistent: true
|
||||
}).onOk(async () => {
|
||||
modal.value = false
|
||||
next.value = false
|
||||
previous.value = false
|
||||
await getData()
|
||||
})
|
||||
} else {
|
||||
modal.value = false
|
||||
next.value = false
|
||||
previous.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดเลือกข้อมูลที่จะแก้ไข
|
||||
* @param props ค่า props ใน row ที่เลือก
|
||||
*/
|
||||
const selectData = (props: DataProps) => {
|
||||
modalEdit.value = true
|
||||
modal.value = true
|
||||
// edit.value = false
|
||||
rawItem.value = props.row
|
||||
rowIndex.value = props.rowIndex
|
||||
qualification.value = props.row.qualification
|
||||
qualificationId.value = props.row.qualificationId
|
||||
major.value = props.row.major
|
||||
scores.value = props.row.scores
|
||||
name.value = props.row.name
|
||||
duration.value = props.row.duration
|
||||
id.value = props.row.id
|
||||
if (edit.value == true) {
|
||||
next.value = false
|
||||
previous.value = false
|
||||
} else {
|
||||
checkRowPage()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* กดปุ่มเพิ่มบน table
|
||||
*/
|
||||
const addRow = () => {
|
||||
modalEdit.value = false
|
||||
modal.value = true
|
||||
// edit.value = true
|
||||
qualification.value = ''
|
||||
qualificationId.value = ''
|
||||
major.value = ''
|
||||
scores.value = null
|
||||
name.value = ''
|
||||
duration.value = [new Date(), new Date()]
|
||||
}
|
||||
|
||||
/**
|
||||
* ฟังก์ชันปุ่มยกเลิกการแก้ไขข้อมูล
|
||||
*/
|
||||
const clickCancel = async () => {
|
||||
// if (editRow.value == true) {
|
||||
// $q.dialog({
|
||||
// title: `ข้อมูลมีการแก้ไข`,
|
||||
// message: `ยืนยันการบันทึกข้อมูลใช่หรือไม่?`,
|
||||
// cancel: 'ยกเลิก',
|
||||
// ok: 'ยืนยัน',
|
||||
// persistent: true
|
||||
// }).onOk(async () => {
|
||||
// edit.value = false
|
||||
// checkRowPage()
|
||||
// await getData()
|
||||
// })
|
||||
// } else {
|
||||
// edit.value = false
|
||||
// checkRowPage()
|
||||
// }
|
||||
edit.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* เช็คว่ามีการแก้ไขข้อมูล
|
||||
*/
|
||||
const clickEditRow = () => {
|
||||
editRow.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* validate input ใน dialog
|
||||
*/
|
||||
const validateData = async () => {
|
||||
checkValidate.value = true
|
||||
await myForm.value.validate().then((result: boolean) => {
|
||||
if (result == false) {
|
||||
checkValidate.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* class จัดรูปแบบแสดงระหว่างข้อมูลที่แก้ไขหรือแสดงเฉยๆ
|
||||
* @param val ข้อมูล input สำหรับแก้ไขหรือไม่
|
||||
*/
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.modalfix {
|
||||
position: fixed !important;
|
||||
}
|
||||
</style>
|
||||
86
src/modules/01_exam/components/Form/Profile.vue
Normal file
86
src/modules/01_exam/components/Form/Profile.vue
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<!-- tab ข้อมูลส่วนบุคคล -->
|
||||
<template>
|
||||
<Information
|
||||
:prefixOptions="prefixOptions"
|
||||
:genderOptions="genderOptions"
|
||||
:bloodOptions="bloodOptions"
|
||||
:statusOptions="statusOptions"
|
||||
:religionOptions="religionOptions"
|
||||
:provinceOptions="provinceOptions"
|
||||
v-model:statusEdit="statusEdit"
|
||||
:notiNoEdit="notiNoEdit"
|
||||
:step="step"
|
||||
/>
|
||||
<Address v-model:statusEdit="statusEdit" :notiNoEdit="notiNoEdit" :step="step" />
|
||||
<Family
|
||||
:prefixOptions="prefixOptions"
|
||||
v-model:statusEdit="statusEdit"
|
||||
:notiNoEdit="notiNoEdit"
|
||||
:step="step"
|
||||
/>
|
||||
<Occupation v-model:statusEdit="statusEdit" :notiNoEdit="notiNoEdit" :step="step" />
|
||||
<NotifyError
|
||||
:modalError="modalNoEdit"
|
||||
:modalErrorTittle="modalNoEditTittle"
|
||||
:modalErrorDetail="modalNoEditDetail"
|
||||
:close="closeModalError"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import type { DataOption } from '@/modules/01_exam/interface/index/Main'
|
||||
import Information from '@/modules/01_exam/components/Form/Profile/Information.vue'
|
||||
import Address from '@/modules/01_exam/components/Form/Profile/Address.vue'
|
||||
import Family from '@/modules/01_exam/components/Form/Profile/Family.vue'
|
||||
import Occupation from '@/modules/01_exam/components/Form/Profile/Occupation.vue'
|
||||
import NotifyError from '@/components/NotifyError.vue'
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
|
||||
const props = defineProps({
|
||||
loader: {
|
||||
//หน้า main มีการอัพเดทค่าให้ refresh data
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const loader = ref<boolean>(true)
|
||||
const statusEdit = ref<boolean>(false)
|
||||
const modalNoEdit = ref<boolean>(false)
|
||||
// const modalNoEditTittle = ref<string>('ยืนยันการเปลี่ยนแท็ปใช่หรือไม่?')
|
||||
// const modalNoEditDetail = ref<string>(
|
||||
// 'ยังมีข้อมูลที่ยังไม่ถูกบันทึก ถ้าตกลงเปลี่ยนแท็ปข้อมูลที่ยังไม่ถูกบันทึกจะหาย'
|
||||
// )
|
||||
const modalNoEditTittle = ref<string>('ไม่สามารถไม่สามารถแก้ไขข้อมูลได้?')
|
||||
const modalNoEditDetail = ref<string>('มีข้อมูลที่ยังไม่ถูกบันทึกข้อมูล')
|
||||
|
||||
const prefixOptions = ref<DataOption[]>([])
|
||||
const bloodOptions = ref<DataOption[]>([])
|
||||
const genderOptions = ref<DataOption[]>([])
|
||||
const statusOptions = ref<DataOption[]>([])
|
||||
const religionOptions = ref<DataOption[]>([])
|
||||
const provinceOptions = ref<DataOption[]>([])
|
||||
|
||||
const emit = defineEmits(['update:loader', 'update:statusEdit'])
|
||||
|
||||
watch(statusEdit, (count: boolean, prevCount: boolean) => {
|
||||
emit('update:statusEdit', count)
|
||||
})
|
||||
|
||||
const closeModalError = () => {
|
||||
modalNoEdit.value = false
|
||||
}
|
||||
|
||||
const notiNoEdit = () => {
|
||||
modalNoEdit.value = true
|
||||
}
|
||||
</script>
|
||||
421
src/modules/01_exam/components/Form/Profile/Address.vue
Normal file
421
src/modules/01_exam/components/Form/Profile/Address.vue
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
<!-- card ข้อมูลที่อยู่ -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md q-mt-md">
|
||||
<!-- <HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="ข้อมูลที่อยู่"
|
||||
icon="mdi-map-marker"
|
||||
:save="saveData"
|
||||
/> -->
|
||||
<HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="ข้อมูลที่อยู่"
|
||||
icon="mdi-map-marker"
|
||||
:save="saveData"
|
||||
:history="true"
|
||||
:addData="addData"
|
||||
:cancel="cancelData"
|
||||
:changeBtn="changeBtn"
|
||||
/>
|
||||
<q-form ref="myform">
|
||||
<div class="row col-12 items-center q-col-gutter-x-xs q-col-gutter-y-xs">
|
||||
<div class="col-xs-12">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="addressData.address"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ที่อยู่ตามทะเบียนบ้าน'}`]"
|
||||
:label="`${'ที่อยู่ตามทะเบียนบ้าน'}`"
|
||||
/>
|
||||
<!-- :filled="edit" -->
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก จังหวัด'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.provinceId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="provinceOptions"
|
||||
option-value="id"
|
||||
:label="`${'จังหวัด'}`"
|
||||
@update:model-value="(value) => selectProvince(value, '1')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก เขต / อำเภอ'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.districtId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="districtOptions"
|
||||
option-value="id"
|
||||
:label="`${'เขต / อำเภอ'}`"
|
||||
@update:model-value="(value) => selectDistrict(value, '1')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก ตำบล / แขวง'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.subdistrictId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="subdistrictOptions"
|
||||
option-value="id"
|
||||
:label="`${'ตำบล / แขวง'}`"
|
||||
@update:model-value="(value) => selectSubDistrict(value, '1')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
readonly
|
||||
borderless
|
||||
v-model="addressData.code"
|
||||
:style="!edit ? '' : 'padding:0 12px;'"
|
||||
:label="`${'รหัสไปรษณีย์'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 q-pt-lg"><q-separator /></div>
|
||||
<div class="col-xs-12 q-gutter-sm items-center flex q-my-sm">
|
||||
<label class="text-bold">ที่อยู่ปัจจุบันตรงกับที่อยู่ตามทะเบียนบ้าน</label>
|
||||
<q-radio
|
||||
v-model="addressData.same"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="1"
|
||||
label="ใช่"
|
||||
dense
|
||||
:disable="!edit"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="addressData.same"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="0"
|
||||
label="ไม่"
|
||||
dense
|
||||
:disable="!edit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12" v-if="addressData.same == '0'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="addressData.addressC"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ที่อยู่ปัจจุบัน'}`]"
|
||||
:label="`${'ที่อยู่ปัจจุบัน'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="addressData.same == '0'">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก จังหวัด'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.provinceIdC"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="provinceOptions"
|
||||
option-value="id"
|
||||
:label="`${'จังหวัด'}`"
|
||||
@update:model-value="(value) => selectProvince(value, '2')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="addressData.same == '0'">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก เขต / อำเภอ'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.districtIdC"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="districtCOptions"
|
||||
option-value="id"
|
||||
:label="`${'เขต / อำเภอ'}`"
|
||||
@update:model-value="(value) => selectDistrict(value, '2')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="addressData.same == '0'">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก ตำบล / แขวง'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="addressData.subdistrictIdC"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="subdistrictCOptions"
|
||||
option-value="id"
|
||||
:label="`${'ตำบล / แขวง'}`"
|
||||
@update:model-value="(value) => selectSubDistrict(value, '2')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="addressData.same == '0'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
readonly
|
||||
borderless
|
||||
v-model="addressData.codeC"
|
||||
:style="!edit ? '' : 'padding:0 12px;'"
|
||||
:label="`${'รหัสไปรษณีย์'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
import type { Address, DataOption, zipCodeOption } from '@/modules/01_exam/interface/index/Main'
|
||||
import { defaultAddress } from '@/modules/01_exam/interface/index/Main'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai, calAge } = mixin
|
||||
|
||||
const edit = ref<boolean>(false)
|
||||
const addData = ref<boolean>(true)
|
||||
const addressData = ref<Address>(defaultAddress)
|
||||
const myform = ref<any>()
|
||||
const codep = ref<string>('')
|
||||
const codec = ref<string>('')
|
||||
|
||||
const provinceOptions = ref<DataOption[]>([])
|
||||
const districtOptions = ref<DataOption[]>([])
|
||||
const districtCOptions = ref<DataOption[]>([])
|
||||
const subdistrictOptions = ref<zipCodeOption[]>([])
|
||||
const subdistrictCOptions = ref<zipCodeOption[]>([])
|
||||
|
||||
const props = defineProps({
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
notiNoEdit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:statusEdit'])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
// fetchProvince()
|
||||
// fetchDistrict(addressData.value.provinceId, '1')
|
||||
// fetchDistrict(addressData.value.provinceIdC, '2')
|
||||
})
|
||||
|
||||
const saveData = async () => {
|
||||
await myform.value.validate().then(async (success: boolean) => {
|
||||
if (success) {
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeBtn = async () => {
|
||||
if (edit.value == true) {
|
||||
if (props.statusEdit === true) {
|
||||
edit.value = false
|
||||
props.notiNoEdit()
|
||||
} else {
|
||||
emit('update:statusEdit', true)
|
||||
}
|
||||
} else {
|
||||
emit('update:statusEdit', false)
|
||||
}
|
||||
}
|
||||
|
||||
const selectProvince = (e: string, name: string) => {
|
||||
if (name == '1') {
|
||||
addressData.value.districtId = ''
|
||||
addressData.value.subdistrictId = ''
|
||||
codep.value = ''
|
||||
} else {
|
||||
addressData.value.districtIdC = ''
|
||||
addressData.value.subdistrictIdC = ''
|
||||
codec.value = ''
|
||||
}
|
||||
|
||||
myform.value.resetValidation()
|
||||
fetchDistrict(e, name)
|
||||
}
|
||||
|
||||
const selectDistrict = (e: string, name: string) => {
|
||||
if (name == '1') {
|
||||
addressData.value.subdistrictId = ''
|
||||
codep.value = ''
|
||||
} else {
|
||||
addressData.value.subdistrictIdC = ''
|
||||
codec.value = ''
|
||||
}
|
||||
|
||||
myform.value.resetValidation()
|
||||
fetchSubDistrict(e, name)
|
||||
}
|
||||
|
||||
const selectSubDistrict = (e: string, name: string) => {
|
||||
if (name == '1') {
|
||||
const findcode = subdistrictOptions.value.filter((r) => r.id == e)
|
||||
const namecode = findcode.length > 0 ? findcode[0].zipCode : ''
|
||||
codep.value = namecode
|
||||
} else {
|
||||
const findcode = subdistrictCOptions.value.filter((r) => r.id == e)
|
||||
const namecode = findcode.length > 0 ? findcode[0].zipCode : ''
|
||||
codec.value = namecode
|
||||
}
|
||||
|
||||
// myform.value.resetValidation();
|
||||
// fetchSubDistrict(e, name);
|
||||
}
|
||||
|
||||
const fetchProvince = async () => {
|
||||
// // loader.value = true;
|
||||
// await http
|
||||
// .get(config.API.province)
|
||||
// .then((res) => {
|
||||
// const data = res.data.result
|
||||
// let option: DataOption[] = []
|
||||
// // console.log(data);
|
||||
// data.map((r: any) => {
|
||||
// option.push({ id: r.id.toString(), name: r.name.toString() })
|
||||
// })
|
||||
// provinceOptions.value = option
|
||||
// })
|
||||
// .catch((e: any) => {})
|
||||
// .finally(() => {
|
||||
// // loader.value = false;
|
||||
// })
|
||||
}
|
||||
|
||||
const fetchDistrict = async (id: string, position: string) => {
|
||||
// // loader.value = true;
|
||||
// await http
|
||||
// .get(config.API.listDistrict(id))
|
||||
// .then((res) => {
|
||||
// const data = res.data.result
|
||||
// let option: DataOption[] = []
|
||||
// // console.log(data);
|
||||
// data.map((r: any) => {
|
||||
// option.push({ id: r.id.toString(), name: r.name.toString() })
|
||||
// })
|
||||
// if (position == '1') {
|
||||
// districtOptions.value = option
|
||||
// } else {
|
||||
// districtCOptions.value = option
|
||||
// }
|
||||
// })
|
||||
// .catch((e: any) => {})
|
||||
// .finally(() => {
|
||||
// // loader.value = false;
|
||||
// })
|
||||
}
|
||||
|
||||
const fetchSubDistrict = async (id: string, position: string) => {
|
||||
// // loader.value = true;
|
||||
// await http
|
||||
// .get(config.API.listSubDistrict(id))
|
||||
// .then((res) => {
|
||||
// const data = res.data.result
|
||||
// let option: zipCodeOption[] = []
|
||||
// // console.log(res);
|
||||
// data.map((r: any) => {
|
||||
// option.push({
|
||||
// id: r.id.toString(),
|
||||
// name: r.name.toString(),
|
||||
// zipCode: r.zipCode.toString()
|
||||
// })
|
||||
// })
|
||||
// if (position == '1') {
|
||||
// subdistrictOptions.value = option
|
||||
// } else {
|
||||
// subdistrictCOptions.value = option
|
||||
// }
|
||||
// })
|
||||
// .catch((e: any) => {})
|
||||
// .finally(() => {
|
||||
// // loader.value = false;
|
||||
// })
|
||||
}
|
||||
|
||||
const cancelData = () => {}
|
||||
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
361
src/modules/01_exam/components/Form/Profile/Family.vue
Normal file
361
src/modules/01_exam/components/Form/Profile/Family.vue
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
<!-- card ข้อมูลครอบครัว -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md q-mt-md text-dark">
|
||||
<HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="ข้อมูลครอบครัว"
|
||||
icon="mdi-account-group"
|
||||
:save="saveData"
|
||||
:addData="addData"
|
||||
:cancel="cancelData"
|
||||
:changeBtn="changeBtn"
|
||||
/>
|
||||
<q-form ref="myform" class="col-12">
|
||||
<div class="row col-12 items-center q-col-gutter-x-xs q-col-gutter-y-xs">
|
||||
<div class="col-xs-12 q-gutter-sm items-center flex q-my-sm">
|
||||
<label class="text-weight-bold">• คู่สมรส</label>
|
||||
<q-radio
|
||||
v-model="familyData.same"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="1"
|
||||
label="มี"
|
||||
dense
|
||||
:disable="!edit"
|
||||
@update:model-value="selectRadio"
|
||||
/>
|
||||
<q-radio
|
||||
v-model="familyData.same"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="0"
|
||||
label="ไม่มี"
|
||||
dense
|
||||
:disable="!edit"
|
||||
@update:model-value="selectRadio"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2" v-if="familyData.same == '1'">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก คำนำหน้า'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="familyData.prefixIdC"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="prefixOptions"
|
||||
option-value="id"
|
||||
:label="`${'คำนำหน้า'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="familyData.same == '1'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.firstnameC"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ชื่อ'}`]"
|
||||
:label="`${'ชื่อ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3" v-if="familyData.same == '1'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.lastnameC"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก นามสกุล'}`]"
|
||||
:label="`${'นามสกุล'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2" v-if="familyData.same == '1'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.occupationC"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก อาชีพ'}`]"
|
||||
:label="`${'อาชีพ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2" v-if="familyData.same == '1'">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.nationalityC"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สัญชาติ'}`]"
|
||||
:label="`${'สัญชาติ'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 text-weight-bold">• บิดา</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก คำนำหน้า'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="familyData.prefixIdM"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="prefixOptions"
|
||||
option-value="id"
|
||||
:label="`${'คำนำหน้า'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.firstnameM"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ชื่อ'}`]"
|
||||
:label="`${'ชื่อ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.lastnameM"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก นามสกุล'}`]"
|
||||
:label="`${'นามสกุล'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.occupationM"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก อาชีพ'}`]"
|
||||
:label="`${'อาชีพ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.nationalityM"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สัญชาติ'}`]"
|
||||
:label="`${'สัญชาติ'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 text-weight-bold">• มารดา</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก คำนำหน้า'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="familyData.prefixIdF"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="prefixOptions"
|
||||
option-value="id"
|
||||
:label="`${'คำนำหน้า'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.firstnameF"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ชื่อ'}`]"
|
||||
:label="`${'ชื่อ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.lastnameF"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก นามสกุล'}`]"
|
||||
:label="`${'นามสกุล'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.occupationF"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก อาชีพ'}`]"
|
||||
:label="`${'อาชีพ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-2 col-md-2">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="familyData.nationalityF"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สัญชาติ'}`]"
|
||||
:label="`${'สัญชาติ'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
import type { Family, DataOption } from '@/modules/01_exam/interface/index/Main'
|
||||
import { defaultFamily } from '@/modules/01_exam/interface/index/Main'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai, calAge } = mixin
|
||||
|
||||
const edit = ref<boolean>(false)
|
||||
const addData = ref<boolean>(true)
|
||||
const myform = ref<any>()
|
||||
const familyData = ref<Family>(defaultFamily)
|
||||
const statusOptions = ref<DataOption[]>([])
|
||||
|
||||
// const prefixOptions = ref<any>([
|
||||
// { id: "1", name: "นาย" },
|
||||
// { id: "2", name: "นาง" },
|
||||
// { id: "3", name: "นางสาว" },
|
||||
// ]);
|
||||
|
||||
const props = defineProps({
|
||||
prefixOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
notiNoEdit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:statusEdit'])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const saveData = async () => {
|
||||
await myform.value.validate().then(async (success: boolean) => {
|
||||
if (success) {
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeBtn = async () => {
|
||||
if (edit.value == true) {
|
||||
if (props.statusEdit === true) {
|
||||
edit.value = false
|
||||
props.notiNoEdit()
|
||||
} else {
|
||||
emit('update:statusEdit', true)
|
||||
}
|
||||
} else {
|
||||
emit('update:statusEdit', false)
|
||||
}
|
||||
}
|
||||
|
||||
const selectRadio = (e: boolean, i: any) => {
|
||||
if (!e) {
|
||||
familyData.value.prefixIdC = ''
|
||||
familyData.value.firstnameC = ''
|
||||
familyData.value.lastnameC = ''
|
||||
familyData.value.occupationC = ''
|
||||
}
|
||||
}
|
||||
|
||||
const cancelData = () => {}
|
||||
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
494
src/modules/01_exam/components/Form/Profile/Information.vue
Normal file
494
src/modules/01_exam/components/Form/Profile/Information.vue
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
<!-- card ข้อมูลส่วนตัว -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md">
|
||||
<HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="ข้อมูลส่วนตัว"
|
||||
icon="mdi-account"
|
||||
:save="saveData"
|
||||
:addData="addData"
|
||||
:cancel="cancelData"
|
||||
:changeBtn="changeBtn"
|
||||
/>
|
||||
<q-form ref="myform" class="col-12 row">
|
||||
<div class="row col-10 items-center q-col-gutter-x-sm q-col-gutter-y-sm">
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก คำนำหน้า'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="informaData.prefixId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="prefixOptions"
|
||||
option-value="id"
|
||||
:label="`${'คำนำหน้า'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.firstname"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ชื่อ'}`]"
|
||||
:label="`${'ชื่อ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.lastname"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก นามสกุล'}`]"
|
||||
:label="`${'นามสกุล'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.nationality"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สัญชาติ'}`]"
|
||||
:label="`${'สัญชาติ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<datepicker
|
||||
v-model="informaData.birthDate"
|
||||
:locale="'th'"
|
||||
autoApply
|
||||
:enableTimePicker="false"
|
||||
week-start="0"
|
||||
:max-date="new Date()"
|
||||
>
|
||||
<template #year="{ year }">
|
||||
{{ year + 543 }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ parseInt(value + 543) }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:model-value="date2Thai(informaData.birthDate)"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก วัน/เดือน/ปี เกิด'}`]"
|
||||
:label="`${'วัน/เดือน/ปี เกิด'}`"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="event" class="cursor-pointer" style="color: var(--q-primary)">
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</datepicker>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
readonly
|
||||
borderless
|
||||
:style="!edit ? '' : 'padding:0 12px;'"
|
||||
:model-value="calAge(informaData.birthDate)"
|
||||
:label="`${'อายุ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก สถานภาพ'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="informaData.statusId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="statusOptions"
|
||||
option-value="id"
|
||||
:label="`${'สถานภาพ'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.email"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก E-mail address'}`]"
|
||||
label="E-mail address"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.cardid"
|
||||
maxlength="13"
|
||||
:rules="[
|
||||
(val) => !!val || `${'กรุณากรอก เลขบัตรประจำตัวประชาชน'}`,
|
||||
(val) => val.length >= 13 || `${'กรุณากรอกเลขบัตรประจำตัวประชาชนให้ครบ'}`
|
||||
]"
|
||||
label="เลขบัตรประจำตัวประชาชน"
|
||||
mask="#############"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.cardid"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ออกให้ ณ อำเภอ'}`]"
|
||||
label="ออกให้ ณ อำเภอ"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-select
|
||||
hide-bottom-space
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก จังหวัด'}`]"
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
v-model="informaData.provinceId"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
:options="provinceOptions"
|
||||
option-value="id"
|
||||
:label="`${'จังหวัด'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<datepicker
|
||||
v-model="informaData.cardIdDate"
|
||||
:locale="'th'"
|
||||
autoApply
|
||||
:enableTimePicker="false"
|
||||
week-start="0"
|
||||
:max-date="new Date()"
|
||||
>
|
||||
<template #year="{ year }">
|
||||
{{ year + 543 }}
|
||||
</template>
|
||||
<template #year-overlay-value="{ value }">
|
||||
{{ parseInt(value + 543) }}
|
||||
</template>
|
||||
<template #trigger>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
:model-value="date2Thai(informaData.cardIdDate)"
|
||||
:rules="[(val) => !!val || `${'กรุณาเลือก วัน/เดือน/ปี'}`]"
|
||||
:label="`${'วัน/เดือน/ปี'}`"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="event" class="cursor-pointer" style="color: var(--q-primary)">
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
</datepicker>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="tel"
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.tel"
|
||||
:rules="[
|
||||
(val) => !!val || `${'กรุณากรอก โทรศัพท์'}`,
|
||||
(val) => val.length >= 9 || `${'กรุณากรอกข้อมูลโทรศัพท์ให้ครบ'}`
|
||||
]"
|
||||
:label="`${'โทรศัพท์'}`"
|
||||
mask="#########"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="tel"
|
||||
:class="getClass(edit)"
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.phone"
|
||||
:rules="[
|
||||
(val) => !!val || `${'กรุณากรอก โทรศัพท์มือถือ'}`,
|
||||
(val) => val.length >= 10 || `${'กรุณากรอกข้อมูลโทรศัพท์มือถือให้ครบ'}`
|
||||
]"
|
||||
:label="`${'โทรศัพท์มือถือ'}`"
|
||||
mask="##########"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="informaData.knowledge"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก ความรู้ความสามารถพิเศษ'}`]"
|
||||
label="ความรู้ความสามารถพิเศษ"
|
||||
type="textarea"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row col-2 justify-center q-pt-md">
|
||||
<div class="containerimage row justify-center">
|
||||
<q-file
|
||||
borderless
|
||||
v-model="fileData"
|
||||
stack-label
|
||||
:readonly="!edit"
|
||||
@update:model-value="pickFile"
|
||||
>
|
||||
<q-img src="@/assets/avatar_user.jpg" class="col-12">
|
||||
<div class="overlay" v-if="edit">
|
||||
<q-icon name="mdi-camera" />
|
||||
<br />อัปเดต
|
||||
</div>
|
||||
</q-img>
|
||||
</q-file>
|
||||
</div>
|
||||
<div class="col-12 text-center" v-if="disabledPic">
|
||||
<q-btn outline dense color="black" icon="mdi-content-save-outline" @click="savePic">
|
||||
<q-tooltip content-class="bg-grey-2 text-black">บันทึกรูป</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
import type { PropType } from 'vue'
|
||||
import type { Information, DataOption } from '@/modules/01_exam/interface/index/Main'
|
||||
import { defaultInformation } from '@/modules/01_exam/interface/index/Main'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
import type { file } from '@babel/types'
|
||||
|
||||
const mixin = useCounterMixin()
|
||||
const { date2Thai, calAge } = mixin
|
||||
|
||||
const edit = ref<boolean>(false)
|
||||
const addData = ref<boolean>(true)
|
||||
const informaData = ref<Information>(defaultInformation)
|
||||
const provinceOptions = ref<DataOption[]>([])
|
||||
const myform = ref<any>()
|
||||
const imageUrl = ref<string | null>(null)
|
||||
const disabledPic = ref<boolean>(false)
|
||||
const fileData = ref<File | null>()
|
||||
const props = defineProps({
|
||||
prefixOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
genderOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
bloodOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
statusOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
religionOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
provinceOptions: {
|
||||
type: Array as PropType<DataOption[]>,
|
||||
required: true
|
||||
},
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
notiNoEdit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:statusEdit'])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
// fetchProvince()
|
||||
})
|
||||
|
||||
const saveData = async () => {
|
||||
await myform.value.validate().then(async (success: boolean) => {
|
||||
if (success) {
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeBtn = async () => {
|
||||
if (edit.value == true) {
|
||||
if (props.statusEdit === true) {
|
||||
edit.value = false
|
||||
props.notiNoEdit()
|
||||
} else {
|
||||
emit('update:statusEdit', true)
|
||||
}
|
||||
} else {
|
||||
emit('update:statusEdit', false)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchProvince = async () => {
|
||||
// // loader.value = true;
|
||||
// await http
|
||||
// .get(config.API.province)
|
||||
// .then((res) => {
|
||||
// const data = res.data.result
|
||||
// let option: DataOption[] = []
|
||||
// // console.log(data);
|
||||
// data.map((r: any) => {
|
||||
// option.push({ id: r.id.toString(), name: r.name.toString() })
|
||||
// })
|
||||
// provinceOptions.value = option
|
||||
// })
|
||||
// .catch((e: any) => {})
|
||||
// .finally(() => {
|
||||
// // loader.value = false;
|
||||
// })
|
||||
}
|
||||
|
||||
const savePic = () => {
|
||||
disabledPic.value = false
|
||||
}
|
||||
|
||||
const onFilePicked = () => {}
|
||||
|
||||
const pickFile = () => {
|
||||
disabledPic.value = true
|
||||
}
|
||||
|
||||
const cancelData = () => {
|
||||
fileData.value = null
|
||||
disabledPic.value = false
|
||||
}
|
||||
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.containerimage {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.q-img {
|
||||
display: block;
|
||||
width: 160px;
|
||||
height: 170px;
|
||||
padding: 1%;
|
||||
border-radius: 3px;
|
||||
border: solid 2px rgba(168, 168, 168, 0.055) !important;
|
||||
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.048), 0 3px 6px 0 rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background: rgb(0, 0, 0);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #f1f1f1;
|
||||
transition: 0.5s ease;
|
||||
width: 160px;
|
||||
height: 70px;
|
||||
opacity: 0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
padding: 5% 0 5% 0;
|
||||
}
|
||||
|
||||
.containerimage:hover .overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
297
src/modules/01_exam/components/Form/Profile/Occupation.vue
Normal file
297
src/modules/01_exam/components/Form/Profile/Occupation.vue
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
<!-- card อาชีพ -->
|
||||
<template>
|
||||
<q-card flat bordered class="col-12 q-px-lg q-py-md q-mt-md">
|
||||
<HeaderTop
|
||||
v-model:edit="edit"
|
||||
header="อาชีพ"
|
||||
icon="mdi-briefcase"
|
||||
:save="saveData"
|
||||
:addData="addData"
|
||||
:cancel="cancelData"
|
||||
:changeBtn="changeBtn"
|
||||
/>
|
||||
<q-form ref="myform">
|
||||
<div class="row col-12 items-center q-col-gutter-x-xs q-col-gutter-y-xs">
|
||||
<div class="col-12 row">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.official"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:disable="occupationData.status !== 'official' || !edit"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="official"
|
||||
label="ข้าราชการกรุงเทพมหานคร ตำแหน่ง"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.personnel"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:disable="occupationData.status !== 'personnel' || !edit"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="personnel"
|
||||
label="บุคลากรกรุงเทพมหานคร ตำแหน่ง"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.officialsOther"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:disable="occupationData.status !== 'officialsOther' || !edit"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="officialsOther"
|
||||
label="ข้าราชการประเภทอื่น ตำแหน่ง"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.employee"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:disable="occupationData.status !== 'employee' || !edit"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="employee"
|
||||
label="ลูกจ้าง/พนักงานราชการของส่วนราชการอื่น ตำแหน่ง"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="studying"
|
||||
label="กำลังศึกษาต่อ"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.other"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:disable="occupationData.status !== 'other' || !edit"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-radio
|
||||
v-model="occupationData.status"
|
||||
checked-icon="task_alt"
|
||||
unchecked-icon="panorama_fish_eye"
|
||||
val="other"
|
||||
label="อื่นๆ"
|
||||
dense
|
||||
:disable="!edit"
|
||||
size="md"
|
||||
style="font-size: 14px; color: black"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.company"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก สำนัก/บริษัท'}`]"
|
||||
:label="`${'สำนัก/บริษัท'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.department"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก กอง/ฝ่าย'}`]"
|
||||
:label="`${'กอง/ฝ่าย'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.email"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก E-mail address'}`]"
|
||||
:label="`${'E-mail address'}`"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-3 col-md-3">
|
||||
<q-input
|
||||
:class="getClass(edit)"
|
||||
hide-bottom-space
|
||||
:outlined="edit"
|
||||
dense
|
||||
lazy-rules
|
||||
type="textarea"
|
||||
autogrow
|
||||
:readonly="!edit"
|
||||
:borderless="!edit"
|
||||
v-model="occupationData.tel"
|
||||
:rules="[(val) => !!val || `${'กรุณากรอก โทรศัพท์'}`]"
|
||||
:label="`${'โทรศัพท์'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
|
||||
import http from '@/plugins/http'
|
||||
import config from '@/app.config'
|
||||
import type { Occupation } from '@/modules/01_exam/interface/index/Main'
|
||||
import { defaultOccupation } from '@/modules/01_exam/interface/index/Main'
|
||||
import HeaderTop from '@/components/top.vue'
|
||||
|
||||
const edit = ref<boolean>(false)
|
||||
const addData = ref<boolean>(true)
|
||||
const occupationData = ref<Occupation>(defaultOccupation)
|
||||
const myform = ref<any>()
|
||||
|
||||
const props = defineProps({
|
||||
statusEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
notiNoEdit: {
|
||||
type: Function,
|
||||
default: () => console.log('not function')
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:statusEdit'])
|
||||
|
||||
onMounted(() => {
|
||||
if (props.step !== 2) {
|
||||
addData.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const saveData = async () => {
|
||||
await myform.value.validate().then(async (success: boolean) => {
|
||||
if (success) {
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeBtn = async () => {
|
||||
if (edit.value == true) {
|
||||
if (props.statusEdit === true) {
|
||||
edit.value = false
|
||||
props.notiNoEdit()
|
||||
} else {
|
||||
emit('update:statusEdit', true)
|
||||
}
|
||||
} else {
|
||||
emit('update:statusEdit', false)
|
||||
}
|
||||
}
|
||||
|
||||
const cancelData = () => {}
|
||||
|
||||
const getClass = (val: boolean) => {
|
||||
return {
|
||||
'full-width inputgreen cursor-pointer': val,
|
||||
'full-width cursor-pointer': !val
|
||||
}
|
||||
}
|
||||
</script>
|
||||
283
src/modules/01_exam/interface/index/Main.ts
Normal file
283
src/modules/01_exam/interface/index/Main.ts
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
interface Pagination {
|
||||
rowsPerPage: number
|
||||
}
|
||||
|
||||
interface DataDateMonthObject {
|
||||
month: number
|
||||
year: number
|
||||
}
|
||||
|
||||
interface ChangeActive {
|
||||
name: string
|
||||
id: number
|
||||
}
|
||||
|
||||
//ข้อมูลส่วนตัว
|
||||
interface Information {
|
||||
cardid: string
|
||||
prefix: string
|
||||
prefixId: string
|
||||
firstname: string
|
||||
lastname: string
|
||||
birthDate: Date
|
||||
genderId: string
|
||||
bloodId: string
|
||||
nationality: string
|
||||
ethnicity: string
|
||||
religionId: string
|
||||
tel: string
|
||||
phone: string
|
||||
email: string
|
||||
provinceId: string
|
||||
cardIdDate: Date
|
||||
statusId: string
|
||||
knowledge: string
|
||||
}
|
||||
|
||||
interface Family {
|
||||
prefixC: string // couple
|
||||
prefixIdC: string
|
||||
firstnameC: string
|
||||
lastnameC: string
|
||||
occupationC: string
|
||||
nationalityC: string
|
||||
prefixM: string // male
|
||||
prefixIdM: string
|
||||
firstnameM: string
|
||||
lastnameM: string
|
||||
occupationM: string
|
||||
nationalityM: string
|
||||
prefixF: string // female
|
||||
prefixIdF: string
|
||||
firstnameF: string
|
||||
lastnameF: string
|
||||
occupationF: string
|
||||
nationalityF: string
|
||||
same: string
|
||||
}
|
||||
|
||||
interface Occupation {
|
||||
status: string
|
||||
company: string
|
||||
department: string
|
||||
email: string
|
||||
tel: string
|
||||
official: string | null
|
||||
personnel: string | null
|
||||
officialsOther: string | null
|
||||
employee: string | null
|
||||
other: string | null
|
||||
}
|
||||
|
||||
interface Address {
|
||||
address: string
|
||||
provinceId: string
|
||||
districtId: string
|
||||
subdistrictId: string
|
||||
code: number
|
||||
addressC: string
|
||||
provinceIdC: string
|
||||
districtIdC: string
|
||||
subdistrictIdC: string
|
||||
codeC: number
|
||||
same: string
|
||||
}
|
||||
|
||||
interface DataOption {
|
||||
id: string
|
||||
name: string
|
||||
zipCode?: string
|
||||
}
|
||||
|
||||
interface zipCodeOption {
|
||||
id: string
|
||||
name: string
|
||||
zipCode: string
|
||||
}
|
||||
|
||||
interface ExamCard {
|
||||
id: string
|
||||
title: string
|
||||
announcementDate: Date
|
||||
registerRound: Number
|
||||
registerDateStart: Date
|
||||
registerDateEnd: Date
|
||||
yearly: Date
|
||||
}
|
||||
|
||||
const defaultCard: ExamCard[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'การสอบภาค ข. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 2,
|
||||
announcementDate: new Date('2022-11-23'),
|
||||
registerDateStart: new Date('2022-11-09'),
|
||||
registerDateEnd: new Date('2022-11-10'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'การสอบภาค ก. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว',
|
||||
registerRound: 1,
|
||||
announcementDate: new Date('2022-10-14'),
|
||||
registerDateStart: new Date('2022-10-09'),
|
||||
registerDateEnd: new Date('2022-11-30'),
|
||||
yearly: new Date('2022-01-01')
|
||||
}
|
||||
]
|
||||
|
||||
const defaultAddress: Address = {
|
||||
address: 'บ้านเลขที่ 1/2 ซอย 56 ถนนตัดใหม่',
|
||||
provinceId: 'กรุงเทพ',
|
||||
districtId: 'หนองแขม',
|
||||
subdistrictId: 'หนองค้างพลู',
|
||||
code: 10160,
|
||||
addressC: 'บ้านเลขที่ 1/2 ซอย 56 ถนนตัดใหม่',
|
||||
provinceIdC: 'กรุงเทพ',
|
||||
districtIdC: 'หนองแขม',
|
||||
subdistrictIdC: 'หนองค้างพลู',
|
||||
codeC: 10160,
|
||||
same: '0'
|
||||
}
|
||||
|
||||
const defaultInformation: Information = {
|
||||
cardid: '10238002345325',
|
||||
prefix: 'นางสาว',
|
||||
prefixId: 'นางสาว',
|
||||
firstname: 'ณัฐกา',
|
||||
lastname: 'ชมสิน',
|
||||
birthDate: new Date('2002-01-01'),
|
||||
genderId: 'หญิง',
|
||||
bloodId: 'O',
|
||||
nationality: 'ไทย',
|
||||
ethnicity: 'ไทย',
|
||||
religionId: 'พุทธ',
|
||||
tel: '0914569982',
|
||||
phone: '0914569982',
|
||||
email: 'kittapath@frappet.com',
|
||||
provinceId: 'กรุงเทพ',
|
||||
cardIdDate: new Date('2000-01-10'),
|
||||
statusId: 'โสด',
|
||||
knowledge: 'excel, word, photoshop'
|
||||
}
|
||||
|
||||
const defaultFamily: Family = {
|
||||
prefixC: 'นาย',
|
||||
prefixIdC: 'นาย',
|
||||
firstnameC: 'ธนาคาร',
|
||||
lastnameC: 'กสิกร',
|
||||
occupationC: 'ว่าง',
|
||||
nationalityC: 'ไทย',
|
||||
prefixM: 'นาย',
|
||||
prefixIdM: 'นาย',
|
||||
firstnameM: 'ธนายุทธ',
|
||||
lastnameM: 'ชมสิน',
|
||||
occupationM: 'ว่าง',
|
||||
nationalityM: 'ไทย',
|
||||
prefixF: 'นางสาว',
|
||||
prefixIdF: 'นางสาว',
|
||||
firstnameF: 'ณัฐกาล',
|
||||
lastnameF: 'ชมสิน',
|
||||
occupationF: 'ว่าง',
|
||||
nationalityF: 'ไทย',
|
||||
same: '1'
|
||||
}
|
||||
|
||||
const defaultOccupation: Occupation = {
|
||||
status: 'official',
|
||||
company: 'บริษัท ทรู คอร์ปอเรชั่น จำกัด (มหาชน)',
|
||||
department: '-',
|
||||
email: 'kittapath@frappet.com',
|
||||
tel: '0846464646',
|
||||
official: 'Direct Sales Staff',
|
||||
personnel: null,
|
||||
officialsOther: null,
|
||||
employee: null,
|
||||
other: null
|
||||
}
|
||||
|
||||
export { defaultInformation, defaultFamily, defaultAddress, defaultOccupation, defaultCard }
|
||||
export type {
|
||||
Pagination,
|
||||
DataOption,
|
||||
DataDateMonthObject,
|
||||
ChangeActive,
|
||||
Information,
|
||||
Family,
|
||||
Address,
|
||||
zipCodeOption,
|
||||
Occupation,
|
||||
ExamCard
|
||||
}
|
||||
29
src/modules/01_exam/interface/request/Career.ts
Normal file
29
src/modules/01_exam/interface/request/Career.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
interface DataProps {
|
||||
row: RequestItemsObject
|
||||
rowIndex: number
|
||||
}
|
||||
|
||||
//ข้อมูล
|
||||
interface RequestItemsObject {
|
||||
id: string
|
||||
location: string
|
||||
position: string
|
||||
salary: number | null
|
||||
duration: [Date, Date]
|
||||
reason: string
|
||||
}
|
||||
|
||||
//columns
|
||||
interface Columns {
|
||||
[index: number]: {
|
||||
name: String
|
||||
align: String
|
||||
label: String
|
||||
sortable: Boolean
|
||||
field: String
|
||||
headerStyle: String
|
||||
style: String
|
||||
}
|
||||
}
|
||||
|
||||
export type { RequestItemsObject, Columns, DataProps }
|
||||
30
src/modules/01_exam/interface/request/Education.ts
Normal file
30
src/modules/01_exam/interface/request/Education.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
interface DataProps {
|
||||
row: RequestItemsObject
|
||||
rowIndex: number
|
||||
}
|
||||
|
||||
//ข้อมูล
|
||||
interface RequestItemsObject {
|
||||
id: string
|
||||
qualificationId: string
|
||||
qualification: string
|
||||
major: string
|
||||
scores: number | null
|
||||
name: string
|
||||
duration: [Date, Date]
|
||||
}
|
||||
|
||||
//columns
|
||||
interface Columns {
|
||||
[index: number]: {
|
||||
name: String
|
||||
align: String
|
||||
label: String
|
||||
sortable: Boolean
|
||||
field: String
|
||||
headerStyle: String
|
||||
style: String
|
||||
}
|
||||
}
|
||||
|
||||
export type { RequestItemsObject, Columns, DataProps }
|
||||
30
src/modules/01_exam/interface/request/Main.ts
Normal file
30
src/modules/01_exam/interface/request/Main.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//ข้อมูล
|
||||
interface RequestItemsObject {
|
||||
id: number;
|
||||
fullname: String;
|
||||
avatar: String;
|
||||
citizenId: String;
|
||||
position: String;
|
||||
line: String;
|
||||
linePosition: String;
|
||||
level: String;
|
||||
positionFormalManage: String;
|
||||
positionManage: String;
|
||||
numberPosition: String;
|
||||
government: String;
|
||||
}
|
||||
|
||||
//columns
|
||||
interface Columns {
|
||||
[index: number]: {
|
||||
name: String;
|
||||
align: String;
|
||||
label: String;
|
||||
sortable: Boolean;
|
||||
field: String;
|
||||
headerStyle: String;
|
||||
style: String;
|
||||
};
|
||||
}
|
||||
|
||||
export type { RequestItemsObject, Columns };
|
||||
11
src/modules/01_exam/interface/response/Career.ts
Normal file
11
src/modules/01_exam/interface/response/Career.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//ข้อมูล
|
||||
interface ResponseObject {
|
||||
id: string
|
||||
location: string
|
||||
position: string
|
||||
salary: number | null
|
||||
duration: [Date, Date]
|
||||
reason: string
|
||||
}
|
||||
|
||||
export type { ResponseObject }
|
||||
12
src/modules/01_exam/interface/response/Education.ts
Normal file
12
src/modules/01_exam/interface/response/Education.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//ข้อมูล
|
||||
interface ResponseObject {
|
||||
id: string
|
||||
qualificationId: string
|
||||
qualification: string
|
||||
major: string
|
||||
scores: number | null
|
||||
name: string
|
||||
duration: [Date, Date]
|
||||
}
|
||||
|
||||
export type { ResponseObject }
|
||||
17
src/modules/01_exam/interface/response/Main.ts
Normal file
17
src/modules/01_exam/interface/response/Main.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//ข้อมูล
|
||||
interface ResponseObject {
|
||||
id: number;
|
||||
fullname: String;
|
||||
avatar: String;
|
||||
citizenId: String;
|
||||
position: String;
|
||||
line: String;
|
||||
linePosition: String;
|
||||
level: String;
|
||||
positionFormalManage: String;
|
||||
positionManage: String;
|
||||
numberPosition: String;
|
||||
government: String;
|
||||
}
|
||||
|
||||
export type { ResponseObject };
|
||||
23
src/modules/01_exam/router.ts
Normal file
23
src/modules/01_exam/router.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
const Main = () => import('@/modules/01_exam/views/ExamMain.vue')
|
||||
const Detail = () => import('@/modules/01_exam/views/ExamDetail.vue')
|
||||
|
||||
export default [
|
||||
// {
|
||||
// path: '/exam',
|
||||
// name: 'exam',
|
||||
// component: Main,
|
||||
// meta: {
|
||||
// Auth: true
|
||||
// // Key: [7]
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/exam/:id',
|
||||
name: 'examDetail',
|
||||
component: Detail,
|
||||
meta: {
|
||||
Auth: true,
|
||||
Key: [7]
|
||||
}
|
||||
}
|
||||
]
|
||||
32
src/modules/01_exam/store.ts
Normal file
32
src/modules/01_exam/store.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useExamDataStore = defineStore('exam', () => {
|
||||
interface exam {
|
||||
main: { columns: String[] }
|
||||
education: { columns: String[] }
|
||||
career: { columns: String[] }
|
||||
}
|
||||
|
||||
const examData = ref<exam>({
|
||||
main: { columns: [] },
|
||||
education: { columns: [] },
|
||||
career: { columns: [] }
|
||||
})
|
||||
|
||||
const changeExamColumns = (system: String, val: String[]) => {
|
||||
if (system == 'main') examData.value.main.columns = val
|
||||
if (system == 'education') examData.value.education.columns = val
|
||||
if (system == 'career') examData.value.career.columns = val
|
||||
localStorage.setItem('exam', JSON.stringify(examData.value))
|
||||
}
|
||||
|
||||
if (localStorage.getItem('exam') !== null) {
|
||||
examData.value = JSON.parse(localStorage.getItem('exam') || '{}')
|
||||
}
|
||||
|
||||
return {
|
||||
examData,
|
||||
changeExamColumns
|
||||
}
|
||||
})
|
||||
152
src/modules/01_exam/views/ExamDetail.vue
Normal file
152
src/modules/01_exam/views/ExamDetail.vue
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<!-- page:detail page รายการสอบทั้งหมด -->
|
||||
<template>
|
||||
<q-toolbar class="q-py-sm q-px-md bg-grey-2">
|
||||
<q-toolbar-title class="toptitle text-dark col-12 row items-center">
|
||||
{{ tittle }}
|
||||
<q-space />
|
||||
<q-btn color="primary" label="ออกจากระบบ" push size="sm" v-close-popup @click="doLogout" />
|
||||
</q-toolbar-title>
|
||||
</q-toolbar>
|
||||
<q-card flat class="">
|
||||
<div class="justify-center">
|
||||
<!-- done-color="deep-orange"
|
||||
active-color="purple"
|
||||
inactive-color="secondary" -->
|
||||
<q-stepper
|
||||
v-model="step"
|
||||
ref="stepper"
|
||||
alternative-labels
|
||||
header-nav
|
||||
animated
|
||||
done-color="positive"
|
||||
active-color="blue-7"
|
||||
inactive-color="positive"
|
||||
done-icon="check"
|
||||
:active-icon="stepRaw === step && stepRaw != 4 ? 'mdi-pencil-outline' : 'mdi-eye-outline'"
|
||||
>
|
||||
<q-step
|
||||
:done="step > 1"
|
||||
:disable="stepRaw < 1"
|
||||
:name="1"
|
||||
title="อ่านคำชี้แจง"
|
||||
:icon="
|
||||
stepRaw >= 1 ? (stepRaw == 1 ? 'mdi-pencil-outline' : 'check') : 'mdi-pencil-outline'
|
||||
"
|
||||
>
|
||||
<ExamDetail :fetchStep="fetchStep" :step="stepRaw" />
|
||||
</q-step>
|
||||
<q-step
|
||||
:done="step > 2"
|
||||
:disable="stepRaw < 2"
|
||||
:name="2"
|
||||
title="ข้อมูลสมัครสอบ"
|
||||
:icon="
|
||||
stepRaw >= 2 ? (stepRaw == 2 ? 'mdi-pencil-outline' : 'check') : 'mdi-pencil-outline'
|
||||
"
|
||||
>
|
||||
<ExamForm :fetchStep="fetchStep" :step="stepRaw" />
|
||||
</q-step>
|
||||
<q-step
|
||||
:done="step > 3"
|
||||
:disable="stepRaw < 3"
|
||||
:name="3"
|
||||
title="ชำระค่าธรรมเนียมการสอบ"
|
||||
:icon="
|
||||
stepRaw >= 3 ? (stepRaw == 3 ? 'mdi-pencil-outline' : 'check') : 'mdi-pencil-outline'
|
||||
"
|
||||
>
|
||||
<ExamPayment :fetchStep="fetchStep" :step="stepRaw" />
|
||||
</q-step>
|
||||
<q-step
|
||||
:done="step > 4"
|
||||
:disable="stepRaw < 4"
|
||||
:name="4"
|
||||
title="สำเร็จ"
|
||||
:icon="
|
||||
stepRaw >= 4 ? (stepRaw == 4 ? 'mdi-pencil-outline' : 'check') : 'mdi-pencil-outline'
|
||||
"
|
||||
>
|
||||
<ExamFinished :fetchStep="fetchStep" :step="stepRaw" />
|
||||
</q-step>
|
||||
</q-stepper>
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import ExamDetail from '@/modules/01_exam/components/ExamDetail.vue'
|
||||
import ExamForm from '@/modules/01_exam/components/ExamForm.vue'
|
||||
import ExamPayment from '@/modules/01_exam/components/ExamPayment.vue'
|
||||
import ExamFinished from '@/modules/01_exam/components/ExamFinished.vue'
|
||||
import keycloak from '@/plugins/keycloak'
|
||||
import { useQuasar } from 'quasar'
|
||||
|
||||
const $q = useQuasar()
|
||||
const examId = ref<string>('zxc')
|
||||
const step = ref<number>(1)
|
||||
const stepRaw = ref<number>(1)
|
||||
const examPost = ref<any>()
|
||||
const test = ref<any>()
|
||||
const loading = ref<boolean>(false)
|
||||
const tittle = ref<string>('การสอบภาค ข. พิเศษ สำหรับผู้สอบผ่านของส่วนราชการแล้ว')
|
||||
|
||||
onMounted(() => {
|
||||
loadCandidate()
|
||||
fetchExams()
|
||||
})
|
||||
|
||||
const loadCandidate = () => {
|
||||
// this.loader = true
|
||||
// this.$http
|
||||
// .get(this.$config.API.getCandidateCurrentStep(this.examId, this.$keycloak.tokenParsed.sub))
|
||||
// .then((res) => {
|
||||
// this.step = res.data.items
|
||||
// this.stepRaw = res.data.items
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// this.step = 1
|
||||
// })
|
||||
// .finally(() => {
|
||||
// this.loader = false
|
||||
// })
|
||||
}
|
||||
|
||||
const fetchExams = () => {
|
||||
// const headers = { 'Content-Type': 'application/graphql' }
|
||||
// this.loading = true
|
||||
// this.$http
|
||||
// .post(
|
||||
// this.$config.API.getOrchardPath(),
|
||||
// this.$config.API.getExamPostsForExamDetailQuery(this.$route.params.examId),
|
||||
// { headers }
|
||||
// )
|
||||
// .then((response) => {
|
||||
// this.examPost = response.data.data.examPost[0]
|
||||
// this.test = this.examPost.bag.contentItems
|
||||
// })
|
||||
// .catch((err) => console.log(err))
|
||||
// .finally(() => (this.loading = false))
|
||||
}
|
||||
|
||||
const fetchStep = () => {
|
||||
stepRaw.value += 1
|
||||
step.value += 1
|
||||
}
|
||||
|
||||
/**
|
||||
* logout keycloak
|
||||
* confirm ก่อนออกจากระบบ
|
||||
*/
|
||||
const doLogout = () => {
|
||||
$q.dialog({
|
||||
title: 'ยืนยันการออกจากระบบ',
|
||||
message: `ต้องการออกจากระบบใช้หรือไม่?`,
|
||||
cancel: 'ยกเลิก',
|
||||
ok: 'ยืนยัน',
|
||||
persistent: true
|
||||
}).onOk(() => {
|
||||
keycloak.logout()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
235
src/modules/01_exam/views/ExamMain.vue
Normal file
235
src/modules/01_exam/views/ExamMain.vue
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
<!-- page:main page รายการสอบทั้งหมด -->
|
||||
<template>
|
||||
<q-toolbar class="q-py-sm q-px-md bg-grey-2">
|
||||
<q-toolbar-title class="toptitle text-dark col-12 row items-center"
|
||||
>รายการสอบทั้งหมด</q-toolbar-title
|
||||
>
|
||||
</q-toolbar>
|
||||
|
||||
<div class="q-pa-md q-gutter-md">
|
||||
<CardExam v-for="row in ExamData" :key="row.id" :items="row" @click="next(row.id)" />
|
||||
<!-- <data-table
|
||||
style="height: 80vh"
|
||||
:rows="rows"
|
||||
:columns="columns"
|
||||
:filter="filter"
|
||||
:visible-columns="visibleColumns"
|
||||
v-model:inputfilter="filter"
|
||||
v-model:inputvisible="visibleColumns"
|
||||
:pagination="initialPagination"
|
||||
:nornmalData="true"
|
||||
:paging="true"
|
||||
>
|
||||
<template #columns="props">
|
||||
<q-tr :props="props" @click="next(props.row.id)" class="cursor-pointer">
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<div v-if="col.name == 'no'" class="table_ellipsis">
|
||||
{{ props.rowIndex + 1 }}
|
||||
</div>
|
||||
<div v-else-if="col.name == 'fullname'">
|
||||
<div class="row col-12 items-center">
|
||||
<img
|
||||
:src="props.row.avatar"
|
||||
class="q-mr-sm col-4"
|
||||
style="width: 28px; height: 28px; border-radius: 50%"
|
||||
/>
|
||||
<div class="col-4">
|
||||
<div class="text-weight-medium">
|
||||
{{ props.row.fullname }}
|
||||
</div>
|
||||
<div class="text-weight-light">
|
||||
{{ props.row.citizenId }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="table_ellipsis">
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</data-table> -->
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useExamDataStore } from '@/modules/01_exam/store'
|
||||
import type { RequestItemsObject, Columns } from '@/modules/01_exam/interface/request/Main'
|
||||
import type { ResponseObject } from '@/modules/01_exam/interface/response/Main'
|
||||
import type { Pagination, ExamCard } from '@/modules/01_exam/interface/index/Main'
|
||||
import { defaultCard } from '@/modules/01_exam/interface/index/Main'
|
||||
import CardExam from '../components/ExamCrad.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useExamDataStore()
|
||||
const { examData, changeExamColumns } = store
|
||||
const filter = ref<string>('') //search data table
|
||||
const initialPagination = ref<Pagination>({
|
||||
rowsPerPage: 0
|
||||
})
|
||||
|
||||
const ExamData = ref<ExamCard[]>(defaultCard)
|
||||
const visibleColumns = ref<String[]>([])
|
||||
examData.main.columns.length == 0
|
||||
? (visibleColumns.value = [
|
||||
'no',
|
||||
'fullname',
|
||||
'position',
|
||||
'line',
|
||||
'linePosition',
|
||||
'level',
|
||||
'positionFormalManage',
|
||||
'positionManage',
|
||||
'numberPosition',
|
||||
'government'
|
||||
])
|
||||
: (visibleColumns.value = examData.main.columns)
|
||||
const columns = ref<Columns>([
|
||||
{
|
||||
name: 'no',
|
||||
align: 'left',
|
||||
label: 'ลำดับ',
|
||||
sortable: true,
|
||||
field: 'no',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'fullname',
|
||||
align: 'left',
|
||||
label: 'ชื่อ-สกุล',
|
||||
sortable: true,
|
||||
field: 'fullname',
|
||||
headerStyle: 'font-size: 14px; min-width: 200px',
|
||||
style: 'font-size: 14px; '
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
align: 'left',
|
||||
label: 'ตำแหน่ง',
|
||||
sortable: true,
|
||||
field: 'position',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'line',
|
||||
align: 'left',
|
||||
label: 'สายงาน',
|
||||
sortable: true,
|
||||
field: 'line',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'linePosition',
|
||||
align: 'left',
|
||||
label: 'ตำแหน่งในสายงาน',
|
||||
sortable: true,
|
||||
field: 'linePosition',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'level',
|
||||
align: 'left',
|
||||
label: 'ระดับ',
|
||||
sortable: true,
|
||||
field: 'level',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'positionFormalManage',
|
||||
align: 'left',
|
||||
label: 'ตำแหน่งทางการบริหาร',
|
||||
sortable: true,
|
||||
field: 'positionFormalManage',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'positionManage',
|
||||
align: 'left',
|
||||
label: 'ตำแหน่งการบริหาร',
|
||||
sortable: true,
|
||||
field: 'positionManage',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'numberPosition',
|
||||
align: 'left',
|
||||
label: 'เลขที่ตำแหน่ง',
|
||||
sortable: true,
|
||||
field: 'numberPosition',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
},
|
||||
{
|
||||
name: 'government',
|
||||
align: 'left',
|
||||
label: 'หน่วยงาน/ส่วนราชการ',
|
||||
sortable: true,
|
||||
field: 'government',
|
||||
headerStyle: 'font-size: 14px',
|
||||
style: 'font-size: 14px'
|
||||
}
|
||||
])
|
||||
const rows = ref<RequestItemsObject[]>([
|
||||
{
|
||||
id: 1,
|
||||
fullname: 'นางสาวณัฐกา ชมสิน',
|
||||
avatar: 'https://cdn.quasar.dev/img/boy-avatar.png',
|
||||
citizenId: '4016500103241',
|
||||
position: 'นักจัดการงานทั่วไป',
|
||||
line: 'จัดการทั่วไป',
|
||||
linePosition: 'ทั่วไป ',
|
||||
level: 'ต้น',
|
||||
positionFormalManage: 'นักจัดการทั่วไป',
|
||||
positionManage: 'นักจัดการทั่วไป',
|
||||
numberPosition: 'กทข.1',
|
||||
government: 'กองบริหารทั่วไป'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
fullname: 'นางสาวรัชภรณ์ ภักดี',
|
||||
avatar: 'https://cdn.quasar.dev/img/boy-avatar.png',
|
||||
citizenId: '4016500092355',
|
||||
position: 'นักจัดการงานทั่วไป',
|
||||
line: 'จัดการทั่วไป',
|
||||
linePosition: 'ทั่วไป ',
|
||||
level: 'ต้น',
|
||||
positionFormalManage: 'นักจัดการทั่วไป',
|
||||
positionManage: 'นักจัดการทั่วไป',
|
||||
numberPosition: 'กทข.1',
|
||||
government: 'กองบริหารทั่วไป'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
fullname: 'นางสาวภาพรรณ ลออ',
|
||||
avatar: 'https://cdn.quasar.dev/img/boy-avatar.png',
|
||||
citizenId: '4016500086436',
|
||||
position: 'นักจัดการงานทั่วไป',
|
||||
line: 'จัดการทั่วไป',
|
||||
linePosition: 'ทั่วไป ',
|
||||
level: 'ต้น',
|
||||
positionFormalManage: 'นักจัดการทั่วไป',
|
||||
positionManage: 'นักจัดการทั่วไป',
|
||||
numberPosition: 'กทข.1',
|
||||
government: 'กองบริหารทั่วไป'
|
||||
}
|
||||
])
|
||||
|
||||
watch(visibleColumns, async (count: String[], prevCount: String[]) => {
|
||||
await changeExamColumns('main', count)
|
||||
})
|
||||
|
||||
const next = (id: string) => {
|
||||
router.push(`/exam/${id}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
const Meta = () => import('@/modules/01_meta/views/Meta01View.vue')
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/meta01',
|
||||
name: 'meta01',
|
||||
component: Meta,
|
||||
meta: {
|
||||
Auth: true
|
||||
// Key: [7]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about META01</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
interface Pagination {
|
||||
rowsPerPage: number
|
||||
}
|
||||
|
||||
interface DataOption {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type { Pagination, DataOption }
|
||||
28
src/modules/02_meta/interface/request/Main.ts
Normal file
28
src/modules/02_meta/interface/request/Main.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
interface DataProps {
|
||||
row: RequestItemsObject
|
||||
rowIndex: number
|
||||
}
|
||||
|
||||
//ข้อมูล
|
||||
interface RequestItemsObject {
|
||||
id: string
|
||||
name: string
|
||||
certiNumber: string
|
||||
start: Date
|
||||
end: Date
|
||||
}
|
||||
|
||||
//columns
|
||||
interface Columns {
|
||||
[index: number]: {
|
||||
name: String
|
||||
align: String
|
||||
label: String
|
||||
sortable: Boolean
|
||||
field: String
|
||||
headerStyle: String
|
||||
style: String
|
||||
}
|
||||
}
|
||||
|
||||
export type { RequestItemsObject, Columns, DataProps }
|
||||
11
src/modules/02_meta/interface/response/Main.ts
Normal file
11
src/modules/02_meta/interface/response/Main.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//ข้อมูล
|
||||
interface ResponseObject {
|
||||
id: string
|
||||
date: Date
|
||||
status: string
|
||||
level: string
|
||||
refNo: string
|
||||
refDate: Date
|
||||
}
|
||||
|
||||
export type { ResponseObject }
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useMetaStore = defineStore('meta', () => {
|
||||
const meta = ref<string>('')
|
||||
|
||||
return {
|
||||
meta
|
||||
}
|
||||
})
|
||||
21
src/plugins/FullLoader.vue
Normal file
21
src/plugins/FullLoader.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!-- แสดง ui การโหลด -->
|
||||
<template>
|
||||
<div>
|
||||
<q-inner-loading :showing="loaderVisibility">
|
||||
<q-spinner-cube size="80px" color="primary" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, ref } from "vue";
|
||||
const props = defineProps({
|
||||
visibility: Boolean,
|
||||
});
|
||||
|
||||
const loaderVisibility = ref<boolean>(props.visibility);
|
||||
|
||||
watch(props, (count, prevCount) => {
|
||||
loaderVisibility.value = props.visibility;
|
||||
});
|
||||
</script>
|
||||
25
src/plugins/axios.ts
Normal file
25
src/plugins/axios.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import axios from "axios"
|
||||
import config from "process"
|
||||
// import { dotnetPath } from "../path/axiosPath";
|
||||
// import { getToken } from "@baloise/vue-keycloak";
|
||||
import keycloak from "../plugins/keycloak"
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
withCredentials: false,
|
||||
})
|
||||
|
||||
// axiosInstance.defaults.baseURL = dotnetPath;
|
||||
axiosInstance.interceptors.request.use(
|
||||
async (config) => {
|
||||
const token = await keycloak.token
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default axiosInstance
|
||||
45
src/plugins/http.ts
Normal file
45
src/plugins/http.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import Axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";
|
||||
import keycloak from "./keycloak";
|
||||
|
||||
const http = Axios.create({
|
||||
timeout: 1000000000, // เพิ่มค่า timeout
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
});
|
||||
|
||||
http.interceptors.request.use(
|
||||
async function (config: AxiosRequestConfig<any>) {
|
||||
await keycloak.updateToken(1);
|
||||
config.headers = config.headers ?? {};
|
||||
const token = keycloak.token;
|
||||
// const token = localStorage.getItem("access_token")
|
||||
// const token =
|
||||
// "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxU2VKV2dVRFVlNXZwNS13Q1ZHaG9lT2l4bDJTTkdKemthLU5ZN211NXZJIn0.eyJleHAiOjE2NzI0MTI1NDksImlhdCI6MTY3MjM3NjU0OSwiYXV0aF90aW1lIjoxNjcyMzc2NTQ5LCJqdGkiOiI1MTVhY2IwNC1jODQ3LTQzM2YtYjUxOC03ODUzMzJhY2ZjNWYiLCJpc3MiOiJodHRwczovL2tleWNsb2FrLmZyYXBwZXQuc3lub2xvZ3kubWUvYXV0aC9yZWFsbXMvYm1hLWVociIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJlZmM5YjRlMC1mZGU2LTQ1NDQtYmU1OS1lMTA0MjEwMjUzZjAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJibWEtZWhyIiwibm9uY2UiOiI3NjMyMGI3ZS0xZTMxLTQ5ODYtYWIzOC1iOTUyYjFlODY3OGYiLCJzZXNzaW9uX3N0YXRlIjoiMDZlNTBkZjktNzAyNi00ZGIwLTkxMjgtMWY3Y2FiYTRkNDEyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwczovL2xvY2FsaG9zdDo3MDA2Il0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWJtYS1laHIiLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiMDZlNTBkZjktNzAyNi00ZGIwLTkxMjgtMWY3Y2FiYTRkNDEyIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInJvbGUiOlsiZGVmYXVsdC1yb2xlcy1ibWEtZWhyIiwib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIl0sIm5hbWUiOiJTeXN0ZW0gQWRtaW5pc3RyYXRvciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZ2l2ZW5fbmFtZSI6IlN5c3RlbSIsImZhbWlseV9uYW1lIjoiQWRtaW5pc3RyYXRvciIsImVtYWlsIjoiYWRtaW5AbG9jYWxob3N0In0.xmfJ3pzI-jLYsaiFXyjTW7gfAEpvUmMVsp9BsB1CfRCVOKiGBbuZhnQY8W-1SWVAx1NjJ55L-zMHPK6hk1dRPLbEse3DlIBZw04W9j8m-Wz3eqdHf_UCjmrXb8qAwkeq0Iaxq9mVfJJeQWeKhFBi-Ff8ek4hCXTYDICXS8ny_BaC5WkyrefHQ2xBqQjwRyoxsg4IoVMjXYNb8L9A-4BNlRfs928SqgFYCRlF5h6zw_rC0XoLrGTmqeacBdpey-r3j2g_lTqWy8mQg2T9s65IDqW3kFPOsr0SVO88sjlFbN9Et0L57RmiqORk_RwzbWg-_Yb6dOuolXsnjBOhOoTzkA";
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
},
|
||||
function (error: any) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
http.interceptors.response.use(
|
||||
function (response: AxiosResponse<any, any>) {
|
||||
return response;
|
||||
},
|
||||
function (error: any) {
|
||||
if (typeof error !== undefined) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (error.hasOwnProperty("response")) {
|
||||
if (error.response.status === 401 || error.response.status === 403) {
|
||||
// Store.commit("SET_ERROR_MESSAGE", error.response.data.message);
|
||||
// Store.commit("REMOVE_ACCESS_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default http;
|
||||
23
src/plugins/keycloak.ts
Normal file
23
src/plugins/keycloak.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* front connect to keycloak
|
||||
*/
|
||||
import Keycloak from 'keycloak-js'
|
||||
// import config from "../app.config";
|
||||
// import http from "../shared/http";
|
||||
// import router from "../router";
|
||||
|
||||
const initOptions = {
|
||||
// url: "https://keycloak.frappet.synology.me/auth/",
|
||||
// realm: "bma-ehr",
|
||||
// clientId: "bma-ehr-vue3",
|
||||
url: 'https://identity.frappet.com/',
|
||||
realm: 'exam_test',
|
||||
clientId: 'exam_vue3'
|
||||
} //option keycloak ที่จะ connect
|
||||
|
||||
const keycloak = Keycloak(initOptions)
|
||||
|
||||
keycloak.onAuthSuccess = () => {} //เพิ่มlogin สำเร็จจะมาทำฟังก์ชันนี้
|
||||
|
||||
await keycloak.init({ checkLoginIframe: false }) //ทำการ connect keycloak
|
||||
export default keycloak
|
||||
|
|
@ -1,31 +1,34 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import Meta01 from '@/modules/01_meta/router'
|
||||
import Exam from '@/modules/01_exam/router'
|
||||
import Meta02 from '@/modules/02_meta/router'
|
||||
|
||||
import keycloak from '@/plugins/keycloak'
|
||||
|
||||
const MainLayout = () => import('@/views/MainLayout.vue')
|
||||
const Error404NotFound = () => import('@/views/Error404NotFound.vue')
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
path: '/exam/:id',
|
||||
// name: 'home',
|
||||
component: MainLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'dashboard',
|
||||
component: HomeView,
|
||||
meta: {
|
||||
Auth: true,
|
||||
Key: [7]
|
||||
}
|
||||
},
|
||||
...Meta01,
|
||||
...Meta02
|
||||
// {
|
||||
// path: '/',
|
||||
// name: 'dashboard',
|
||||
// component: HomeView,
|
||||
// meta: {
|
||||
// Auth: true,
|
||||
// Key: [7]
|
||||
// }
|
||||
// },
|
||||
...Exam
|
||||
// ...Meta02
|
||||
]
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 404 Not Found
|
||||
* ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route
|
||||
|
|
@ -35,6 +38,11 @@ const router = createRouter({
|
|||
// path: "/:pathMatch(.*)*",
|
||||
// component: Error404NotFound,
|
||||
// },
|
||||
{
|
||||
// path: "/:catchAll(.*)*", // TODO: ใช้ pathMatch แทนตามในเอกสารแนะนำ คงไว้เผื่อจำเป็น
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: Error404NotFound
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
|
@ -56,5 +64,21 @@ const router = createRouter({
|
|||
// }
|
||||
// ]
|
||||
// })
|
||||
router.beforeEach((to, from, next) => {
|
||||
// if (to.meta.Auth) {
|
||||
// if (!keycloak.authenticated) {
|
||||
// keycloak.login({
|
||||
// redirectUri: `${window.location.protocol}//${window.location.host}${to.path}`,
|
||||
// locale: 'th'
|
||||
// })
|
||||
// } else {
|
||||
// // keycloak.updateToken(60);
|
||||
// next()
|
||||
// }
|
||||
// } else {
|
||||
// next()
|
||||
// }
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
|||
348
src/stores/mixin.ts
Normal file
348
src/stores/mixin.ts
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import moment from 'moment'
|
||||
import type { DataDateMonthObject } from '@/modules/01_exam/interface/index/Main'
|
||||
|
||||
export const useCounterMixin = defineStore('mixin', () => {
|
||||
/**
|
||||
* ฟังก์ชันกลาง
|
||||
*/
|
||||
|
||||
const calAge = (srcDate: Date, birthCal: Date = new Date(), eng: boolean = false) => {
|
||||
const toDay = birthCal
|
||||
const birth = new Date(srcDate)
|
||||
|
||||
const yearNow = toDay.getFullYear()
|
||||
const monthNow = toDay.getMonth()
|
||||
const dateNow = toDay.getDate()
|
||||
|
||||
const yearDob = birth.getFullYear()
|
||||
const monthDob = birth.getMonth()
|
||||
const dateDob = birth.getDate()
|
||||
|
||||
const lastYear = 12
|
||||
const subtractDate: Object = moment().subtract(1, 'months').endOf('month')
|
||||
|
||||
const lastMonths = new Date(subtractDate.toString()).getDate()
|
||||
|
||||
let yearAge = yearNow - yearDob
|
||||
let monthAge = 0
|
||||
let dateAge = 0
|
||||
|
||||
if (monthNow >= monthDob) {
|
||||
monthAge = monthNow - monthDob
|
||||
} else {
|
||||
yearAge--
|
||||
monthAge = lastYear + monthNow - monthDob
|
||||
}
|
||||
|
||||
if (dateNow >= dateDob) {
|
||||
dateAge = dateNow - dateDob
|
||||
} else {
|
||||
monthAge--
|
||||
dateAge = lastMonths + dateNow - dateDob
|
||||
|
||||
if (monthAge < 0) {
|
||||
monthAge = 11
|
||||
yearAge--
|
||||
}
|
||||
}
|
||||
|
||||
const age = {
|
||||
years: yearAge,
|
||||
months: monthAge,
|
||||
days: dateAge
|
||||
}
|
||||
|
||||
const year = eng ? 'years' : 'ปี'
|
||||
const month = eng ? 'months' : 'เดือน'
|
||||
const day = eng ? 'days' : 'วัน'
|
||||
return `${yearAge} ${year} ${monthAge} ${month} ${dateAge} ${day}`
|
||||
}
|
||||
|
||||
function date2Thai(srcDate: Date, isFullMonth: boolean = false, isTime: boolean = false) {
|
||||
const date = new Date(srcDate)
|
||||
const isValidDate = Boolean(+date)
|
||||
if (!isValidDate) return srcDate.toString()
|
||||
if (isValidDate && date.getFullYear() < 1000) return srcDate.toString()
|
||||
const fullMonthThai = [
|
||||
'มกราคม',
|
||||
'กุมภาพันธ์',
|
||||
'มีนาคม',
|
||||
'เมษายน',
|
||||
'พฤษภาคม',
|
||||
'มิถุนายน',
|
||||
'กรกฎาคม',
|
||||
'สิงหาคม',
|
||||
'กันยายน',
|
||||
'ตุลาคม',
|
||||
'พฤศจิกายน',
|
||||
'ธันวาคม'
|
||||
]
|
||||
const abbrMonthThai = [
|
||||
'ม.ค.',
|
||||
'ก.พ.',
|
||||
'มี.ค.',
|
||||
'เม.ย.',
|
||||
'พ.ค.',
|
||||
'มิ.ย.',
|
||||
'ก.ค.',
|
||||
'ส.ค.',
|
||||
'ก.ย.',
|
||||
'ต.ค.',
|
||||
'พ.ย.',
|
||||
'ธ.ค.'
|
||||
]
|
||||
let dstYear = 0
|
||||
if (date.getFullYear() > 2500) {
|
||||
dstYear = date.getFullYear()
|
||||
} else {
|
||||
dstYear = date.getFullYear() + 543
|
||||
}
|
||||
let dstMonth = ''
|
||||
if (isFullMonth) {
|
||||
dstMonth = fullMonthThai[date.getMonth()]
|
||||
} else {
|
||||
dstMonth = abbrMonthThai[date.getMonth()]
|
||||
}
|
||||
let dstTime = ''
|
||||
if (isTime) {
|
||||
const H = date.getHours().toString().padStart(2, '0')
|
||||
const M = date.getMinutes().toString().padStart(2, '0')
|
||||
// const S = date.getSeconds().toString().padStart(2, "0")
|
||||
// dstTime = " " + H + ":" + M + ":" + S + " น."
|
||||
dstTime = ' ' + H + ':' + M + ' น.'
|
||||
}
|
||||
return date.getDate().toString().padStart(2, '0') + ' ' + dstMonth + ' ' + dstYear + dstTime
|
||||
}
|
||||
|
||||
function monthYear2Thai(month: number, year: number, isFullMonth = false) {
|
||||
const date = new Date(`${year}-${month + 1}-1`)
|
||||
const fullMonthThai = [
|
||||
'มกราคม',
|
||||
'กุมภาพันธ์',
|
||||
'มีนาคม',
|
||||
'เมษายน',
|
||||
'พฤษภาคม',
|
||||
'มิถุนายน',
|
||||
'กรกฎาคม',
|
||||
'สิงหาคม',
|
||||
'กันยายน',
|
||||
'ตุลาคม',
|
||||
'พฤศจิกายน',
|
||||
'ธันวาคม'
|
||||
]
|
||||
const abbrMonthThai = [
|
||||
'ม.ค.',
|
||||
'ก.พ.',
|
||||
'มี.ค.',
|
||||
'เม.ย.',
|
||||
'พ.ค.',
|
||||
'มิ.ย.',
|
||||
'ก.ค.',
|
||||
'ส.ค.',
|
||||
'ก.ย.',
|
||||
'ต.ค.',
|
||||
'พ.ย.',
|
||||
'ธ.ค.'
|
||||
]
|
||||
let dstYear = 0
|
||||
if (date.getFullYear() > 2500) {
|
||||
dstYear = date.getFullYear()
|
||||
} else {
|
||||
dstYear = date.getFullYear() + 543
|
||||
}
|
||||
let dstMonth = ''
|
||||
if (isFullMonth) {
|
||||
dstMonth = fullMonthThai[date.getMonth()]
|
||||
} else {
|
||||
dstMonth = abbrMonthThai[date.getMonth()]
|
||||
}
|
||||
return dstMonth + ' ' + dstYear
|
||||
}
|
||||
|
||||
function dateToISO(date: Date) {
|
||||
return (
|
||||
date.getFullYear() +
|
||||
'-' +
|
||||
appendLeadingZeroes(date.getMonth() + 1) +
|
||||
'-' +
|
||||
appendLeadingZeroes(date.getDate())
|
||||
)
|
||||
}
|
||||
|
||||
function appendLeadingZeroes(n: Number) {
|
||||
if (n <= 9) return '0' + n
|
||||
return n
|
||||
}
|
||||
|
||||
function textToPhone(n: string) {
|
||||
const p = n.substr(0, 3) + '-' + n.substr(3, 3) + '-' + n.substr(6, 4)
|
||||
return p
|
||||
}
|
||||
|
||||
function textToFax(n: string) {
|
||||
const p = n.substr(0, 2) + '-' + n.substr(2, 3) + '-' + n.substr(5, 4)
|
||||
return p
|
||||
}
|
||||
|
||||
const success = (q: any, val: string) => {
|
||||
// useQuasar ไม่สามารถใช้นอกไฟล์ .vue
|
||||
if (val !== '') {
|
||||
return q.notify({
|
||||
message: val,
|
||||
color: 'primary',
|
||||
icon: 'mdi-information',
|
||||
position: 'bottom-right',
|
||||
multiLine: true,
|
||||
timeout: 1000,
|
||||
badgeColor: 'positive',
|
||||
classes: 'my-notif-class'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function notify(q: any, val: string) {
|
||||
if (val !== '') {
|
||||
q.notify({
|
||||
color: 'teal-10',
|
||||
message: val,
|
||||
icon: 'mdi-information',
|
||||
position: 'bottom-right',
|
||||
multiLine: true,
|
||||
timeout: 7000,
|
||||
actions: [{ label: 'ปิด', color: 'white', handler: () => {} }]
|
||||
})
|
||||
}
|
||||
}
|
||||
function notifyError(q: any, val: string) {
|
||||
if (val !== '') {
|
||||
q.notify({
|
||||
color: 'negative',
|
||||
message: val,
|
||||
icon: 'mdi-alert-circle',
|
||||
position: 'top',
|
||||
multiLine: true,
|
||||
timeout: 12000,
|
||||
actions: [{ label: 'ปิด', color: 'white', handler: () => {} }]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const dateText = (val: Date) => {
|
||||
if (val != null) {
|
||||
return date2Thai(val)
|
||||
} else {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
const weekThai = (val: Number) => {
|
||||
switch (val) {
|
||||
case 0:
|
||||
return 'วันอาทิตย์'
|
||||
case 1:
|
||||
return 'วันจันทร์'
|
||||
case 2:
|
||||
return 'วันอังคาร'
|
||||
case 3:
|
||||
return 'วันพุธ'
|
||||
case 4:
|
||||
return 'วันพฤหัสบดี'
|
||||
case 5:
|
||||
return 'วันศุกร์'
|
||||
case 6:
|
||||
return 'วันเสาร์'
|
||||
default:
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
const genColor15 = (val: number) => {
|
||||
val = val % 15
|
||||
switch (val) {
|
||||
case 1:
|
||||
return 'pink'
|
||||
case 2:
|
||||
return 'purple'
|
||||
case 3:
|
||||
return 'deep-purple'
|
||||
case 4:
|
||||
return 'indigo'
|
||||
case 5:
|
||||
return 'blue'
|
||||
case 6:
|
||||
return 'light-blue'
|
||||
case 7:
|
||||
return 'cyan'
|
||||
case 8:
|
||||
return 'teal'
|
||||
case 9:
|
||||
return 'green'
|
||||
case 10:
|
||||
return 'light-green'
|
||||
case 11:
|
||||
return 'amber'
|
||||
case 12:
|
||||
return 'orange'
|
||||
case 13:
|
||||
return 'deep-orange'
|
||||
case 14:
|
||||
return 'brown'
|
||||
case 0:
|
||||
return 'blue-grey'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* แปลงช่วงวันที่ถ้า2ค่าเป็นวันเดียวกันจะโชววันเดียวแต่ถ้าไม่เท่ากันจะแสดงเป็นช่วง
|
||||
* @param val ช่วงวันที่
|
||||
*/
|
||||
const dateThaiRange = (val: [Date, Date]) => {
|
||||
if (val === null) {
|
||||
return ''
|
||||
} else if (date2Thai(val[0]) === date2Thai(val[1])) {
|
||||
return `${date2Thai(val[0])}`
|
||||
} else {
|
||||
return `${date2Thai(val[0])} - ${date2Thai(val[1])}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* แปลงช่วงเดือนถ้า2ค่าเป็นวันเดียวกันจะโชววันเดียวแต่ถ้าไม่เท่ากันจะแสดงเป็นช่วง
|
||||
* @param val ช่วงเดือน
|
||||
*/
|
||||
const monthThaiRange = (val: DataDateMonthObject[]) => {
|
||||
if (val === null || val[0] === null || val[1] === null) {
|
||||
return ''
|
||||
} else if (
|
||||
monthYear2Thai(val[0].month, val[0].year) === monthYear2Thai(val[1].month, val[1].year)
|
||||
) {
|
||||
return `${monthYear2Thai(val[0].month, val[0].year)}`
|
||||
} else {
|
||||
return `${monthYear2Thai(val[0].month, val[0].year)} - ${monthYear2Thai(
|
||||
val[1].month,
|
||||
val[1].year
|
||||
)}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
calAge,
|
||||
date2Thai,
|
||||
dateToISO,
|
||||
notify,
|
||||
notifyError,
|
||||
dateText,
|
||||
monthYear2Thai,
|
||||
success,
|
||||
weekThai,
|
||||
genColor15,
|
||||
textToPhone,
|
||||
textToFax,
|
||||
dateThaiRange,
|
||||
monthThaiRange
|
||||
}
|
||||
})
|
||||
17
src/views/Error404NotFound.vue
Normal file
17
src/views/Error404NotFound.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Error404NotFound'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fullscreen bg-blue-10 text-white text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
<div class="text-h1">ไม่พบหน้าที่ต้องการ</div>
|
||||
<div class="text-h2">(404 Not Found)</div>
|
||||
<!-- <q-btn class="q-mt-xl" color="white" text-color="blue" unelevated to="/" label="กลับไปหน้าหลัก" no-caps /> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
import { ref } from 'vue'
|
||||
// import keycloak from '@/plugins/keycloak'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { menuList, options, notiList } from '../interface/main/index'
|
||||
import keycloak from '@/plugins/keycloak'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const $q = useQuasar()
|
||||
const miniState = ref<boolean>(false)
|
||||
const active = ref<number>(0)
|
||||
const drawerL = ref<boolean>(false)
|
||||
|
|
@ -37,19 +40,15 @@ const activeMenu = (path: string) => {
|
|||
* confirm ก่อนออกจากระบบ
|
||||
*/
|
||||
const doLogout = () => {
|
||||
|
||||
// this.$q
|
||||
// .dialog({
|
||||
// title: "ยืนยันการออกจากระบบ",
|
||||
// message: `ต้องการออกจากระบบใช้หรือไม่?`,
|
||||
// cancel: "ยกเลิก",
|
||||
// ok: "ยืนยัน",
|
||||
// persistent: true,
|
||||
// })
|
||||
// .onOk(() => {
|
||||
// // this.$router.push("/logout")
|
||||
// keycloak.logout()
|
||||
// });
|
||||
$q.dialog({
|
||||
title: 'ยืนยันการออกจากระบบ',
|
||||
message: `ต้องการออกจากระบบใช้หรือไม่?`,
|
||||
cancel: 'ยกเลิก',
|
||||
ok: 'ยืนยัน',
|
||||
persistent: true
|
||||
}).onOk(() => {
|
||||
keycloak.logout()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,12 +63,12 @@ const doLogout = () => {
|
|||
<template>
|
||||
<!-- แบบเก่า design แรก -->
|
||||
<!-- <q-layout view="lHh Lpr lff"> -->
|
||||
<!-- ปรับให้กับหน้า รายละเอียดทะเบียนประวัติ -->
|
||||
<q-layout view="lHh LpR lff" @scroll="onScroll">
|
||||
<!-- ปรับให้กับหน้า รายละเอียดรายการสอบทั้งหมด -->
|
||||
<q-layout view="lHh LpR lff">
|
||||
<!-- header -->
|
||||
<q-header flat class="bg-grey-2 text-dark" height-hint="7">
|
||||
<q-toolbar style="padding: 0 2%">
|
||||
<q-btn
|
||||
<!-- <q-btn
|
||||
size="13px"
|
||||
class="bg-grey-3"
|
||||
flat
|
||||
|
|
@ -83,7 +82,7 @@ const doLogout = () => {
|
|||
size="20px"
|
||||
color="grey-7"
|
||||
/>
|
||||
</q-btn>
|
||||
</q-btn> -->
|
||||
|
||||
<q-space />
|
||||
|
||||
|
|
@ -93,12 +92,12 @@ const doLogout = () => {
|
|||
<q-icon name="mdi-magnify" size="20px" color="grey-7" />
|
||||
</template>
|
||||
</q-input> -->
|
||||
<q-btn class="bg-grey-3" flat dense round>
|
||||
<!-- <q-btn class="bg-grey-3" flat dense round>
|
||||
<q-icon name="mdi-magnify" size="20px" color="grey-7" />
|
||||
</q-btn>
|
||||
</q-btn> -->
|
||||
|
||||
<!-- Notification -->
|
||||
<q-btn
|
||||
<!-- <q-btn
|
||||
round
|
||||
dense
|
||||
flat
|
||||
|
|
@ -146,7 +145,6 @@ const doLogout = () => {
|
|||
<q-item v-close-popup class="q-pa-none">
|
||||
<q-item-section avatar>
|
||||
<q-avatar color="grey-3">
|
||||
<!-- <img src="https://cdn.quasar.dev/img/avatar.png" /> -->
|
||||
<q-icon name="mdi-account" size="22px" color="grey-7" />
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
|
|
@ -165,7 +163,6 @@ const doLogout = () => {
|
|||
<div class="column items-center col-12 q-py-md" color="grey-3">
|
||||
<q-avatar size="72px" color="grey-4">
|
||||
<q-icon name="mdi-account" color="grey-7" />
|
||||
<!-- <img :src="require('@/assets/logo.png')" /> -->
|
||||
</q-avatar>
|
||||
<div class="text-subtitle2 q-mt-md q-mb-xs text-center">
|
||||
{{ fullname }}
|
||||
|
|
@ -177,14 +174,13 @@ const doLogout = () => {
|
|||
size="sm"
|
||||
v-close-popup
|
||||
@click="doLogout"
|
||||
/><!-- -->
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="column col-12">
|
||||
<q-separator />
|
||||
<div class="column q-pb-md justify-center">
|
||||
<div class="text-overline text-grey q-px-md q-pt-sm">เลือกโหมด</div>
|
||||
<!-- <q-option-group v-model="group" :options="options" color="primary"/> -->
|
||||
<q-list dense v-for="op in options" :key="op.label">
|
||||
<q-item clickable>
|
||||
<q-item-section avatar>
|
||||
|
|
@ -203,13 +199,13 @@ const doLogout = () => {
|
|||
</div>
|
||||
</div>
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
</div> -->
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<!-- end header -->
|
||||
|
||||
<!-- drawer -->
|
||||
<q-drawer
|
||||
<!-- <q-drawer
|
||||
side="left"
|
||||
bordered
|
||||
class="text-white"
|
||||
|
|
@ -249,33 +245,6 @@ const doLogout = () => {
|
|||
<q-separator inset color="grey-9" />
|
||||
<q-list padding>
|
||||
<div v-for="(menuItem, index) in menuList" :key="index">
|
||||
<!-- <q-expansion-item
|
||||
group="somegroup"
|
||||
class="menuSub"
|
||||
expand-icon="mdi-menu-right"
|
||||
expanded-icon="mdi-menu-down"
|
||||
v-if="menuItem.key == 3 || menuItem.key == 5"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<q-item-section avatar>
|
||||
<q-avatar :icon="menuItem.icon" size="md" font-size="20px" />
|
||||
</q-item-section>
|
||||
<q-item-section>{{ menuItem.label }}</q-item-section>
|
||||
</template>
|
||||
<q-item
|
||||
dense
|
||||
class="menuSubHover"
|
||||
active-class="text-primary active-item text-weight-bold menuSubAct"
|
||||
clickable
|
||||
v-for="subMenu in menuItem.children"
|
||||
:key="subMenu.key"
|
||||
:to="{ name: `${subMenu.path}` }"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ subMenu.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-expansion-item> -->
|
||||
<q-item
|
||||
class="text-weight-medium menu"
|
||||
active-class="text-primary active-item text-weight-bold menuActive"
|
||||
|
|
@ -286,7 +255,6 @@ const doLogout = () => {
|
|||
dense
|
||||
exact
|
||||
>
|
||||
<!-- v-else -->
|
||||
<q-item-section avatar>
|
||||
<q-avatar size="md" font-size="20px">
|
||||
<q-icon :name="menuItem.key === active ? menuItem.activeIcon : menuItem.icon" />
|
||||
|
|
@ -300,7 +268,7 @@ const doLogout = () => {
|
|||
</div>
|
||||
</q-list>
|
||||
</q-scroll-area>
|
||||
</q-drawer>
|
||||
</q-drawer> -->
|
||||
|
||||
<q-page-container class="bg-grey-2">
|
||||
<q-page style="padding: 0 2%">
|
||||
|
|
@ -366,7 +334,6 @@ const doLogout = () => {
|
|||
.toptitle {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
|
||||
.q-field--outlined .q-field__control {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vue({
|
||||
template: { transformAssetUrls }
|
||||
}),
|
||||
quasar({
|
||||
sassVariables: 'src/style/quasar-variables.sass'
|
||||
}),
|
||||
|
|
@ -18,5 +20,8 @@ export default defineConfig({
|
|||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: 'esnext'
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue