mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-07 13:44:24 +00:00
feat(frontend): jalali calendar + drop legacy moment-jalali
- Wire Calendar Type setting to a real Jalali datepicker via vue3-persian-datetime-picker, gated by useDatepicker composable - DateTimePicker wrapper swaps between AD-Vue and Persian picker; keeps dayjs v-model contract so existing forms/setters work unchanged - Theme picker popup explicitly per body.dark / data-theme=ultra-dark (AD-Vue 4 doesn't expose CSS vars, so var() fallbacks defaulted to white); fix invisible disabled days, SVG arrow fills, popup clipping via append-to="body" - Replace stray moment() calls in dbinbound/inbound models with dayjs; the legacy global was undefined under ESM and broke the inbounds list whenever any inbound had expiryTime > 0 - Remove legacy moment-jalali / persian-datepicker / aPersianDatepicker assets — replaced by the Vue 3 picker Note: dark/ultra background of the date popup still renders white in some cases — pending follow-up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
085a12e469
commit
cbb35f73ed
15 changed files with 568 additions and 94 deletions
122
frontend/package-lock.json
generated
122
frontend/package-lock.json
generated
|
|
@ -17,7 +17,8 @@
|
||||||
"qrious": "^4.0.2",
|
"qrious": "^4.0.2",
|
||||||
"qs": "^6.13.1",
|
"qs": "^6.13.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.4"
|
"vue-i18n": "^11.1.4",
|
||||||
|
"vue3-persian-datetime-picker": "^1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.6",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
|
|
@ -961,8 +962,7 @@
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
@ -974,7 +974,6 @@
|
||||||
"version": "1.1.14",
|
"version": "1.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
|
@ -1069,8 +1068,7 @@
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/core-js": {
|
"node_modules/core-js": {
|
||||||
"version": "3.49.0",
|
"version": "3.49.0",
|
||||||
|
|
@ -1549,6 +1547,11 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|
@ -1606,6 +1609,26 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
|
@ -1720,6 +1743,21 @@
|
||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
|
@ -1755,6 +1793,11 @@
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/jalaali-js": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A=="
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
@ -2142,7 +2185,6 @@
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
|
|
@ -2158,6 +2200,28 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment-jalaali": {
|
||||||
|
"version": "0.9.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.9.6.tgz",
|
||||||
|
"integrity": "sha512-v8wXjQplvk5ez+sUqgsWIrafwIf1BEXXvzTYwsg1wHcqh27nSgKPCJ6FnZRrCz03MoNyB9N31L0oms+vE8Rq7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"jalaali-js": "^1.1.0",
|
||||||
|
"moment": "^2.22.2",
|
||||||
|
"moment-timezone": "^0.5.21",
|
||||||
|
"rimraf": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
@ -2215,6 +2279,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
|
|
@ -2294,6 +2366,14 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
|
@ -2419,6 +2499,21 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rimraf": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rimraf": "bin.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rolldown": {
|
"node_modules/rolldown": {
|
||||||
"version": "1.0.0-rc.18",
|
"version": "1.0.0-rc.18",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
|
||||||
|
|
@ -2879,6 +2974,14 @@
|
||||||
"vue": "^3.0.0"
|
"vue": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue3-persian-datetime-picker": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue3-persian-datetime-picker/-/vue3-persian-datetime-picker-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-d7nkj5vgtUvEXZboSdRmP1uwBfXvXgXqdvsOOMQb34jiMZU/aBDrTYWTEe1N+XKF9pvTTJn8Rws9ttJmyhK/hw==",
|
||||||
|
"dependencies": {
|
||||||
|
"moment-jalaali": "^0.9.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/warning": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
|
@ -2911,6 +3014,11 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@
|
||||||
"qrious": "^4.0.2",
|
"qrious": "^4.0.2",
|
||||||
"qs": "^6.13.1",
|
"qs": "^6.13.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-i18n": "^11.1.4"
|
"vue-i18n": "^11.1.4",
|
||||||
|
"vue3-persian-datetime-picker": "^1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.6",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
|
|
|
||||||
383
frontend/src/components/DateTimePicker.vue
Normal file
383
frontend/src/components/DateTimePicker.vue
Normal file
|
|
@ -0,0 +1,383 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import PersianDatePicker from 'vue3-persian-datetime-picker';
|
||||||
|
import { useDatepicker } from '@/composables/useDatepicker.js';
|
||||||
|
|
||||||
|
// Drop-in replacement for <a-date-picker> that swaps to a real Jalali
|
||||||
|
// calendar (vue3-persian-datetime-picker, backed by moment-jalaali)
|
||||||
|
// when the panel's "Calendar Type" setting is `jalalian`.
|
||||||
|
//
|
||||||
|
// The v-model contract matches AD-Vue: the parent works with a dayjs
|
||||||
|
// object (or null). For the persian picker we serialize to/from the
|
||||||
|
// `YYYY-MM-DD HH:mm:ss` string it expects so callers don't need to
|
||||||
|
// know which renderer is active.
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: { type: [Object, null], default: null },
|
||||||
|
showTime: { type: Boolean, default: true },
|
||||||
|
format: { type: String, default: 'YYYY-MM-DD HH:mm:ss' },
|
||||||
|
placeholder: { type: String, default: '' },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const { datepicker } = useDatepicker();
|
||||||
|
const isJalali = computed(() => datepicker.value === 'jalalian');
|
||||||
|
|
||||||
|
const ISO_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
// Persian picker's display format — `j…` tokens come from moment-jalaali
|
||||||
|
// and render Jalali year/month/day.
|
||||||
|
const persianDisplayFormat = computed(() =>
|
||||||
|
props.showTime ? 'jYYYY/jMM/jDD HH:mm:ss' : 'jYYYY/jMM/jDD',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Persian picker stores the date as a Gregorian string in the format
|
||||||
|
// it was given via `format`. We normalize on `YYYY-MM-DD HH:mm:ss` so
|
||||||
|
// dayjs(...) round-trips cleanly.
|
||||||
|
const stringValue = computed({
|
||||||
|
get() {
|
||||||
|
const v = props.value;
|
||||||
|
if (!v) return '';
|
||||||
|
return dayjs.isDayjs(v) ? v.format(ISO_FORMAT) : dayjs(v).format(ISO_FORMAT);
|
||||||
|
},
|
||||||
|
set(next) {
|
||||||
|
if (!next) {
|
||||||
|
emit('update:value', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsed = dayjs(next, ISO_FORMAT);
|
||||||
|
emit('update:value', parsed.isValid() ? parsed : null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onAntChange(next) {
|
||||||
|
emit('update:value', next || null);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PersianDatePicker
|
||||||
|
v-if="isJalali"
|
||||||
|
v-model="stringValue"
|
||||||
|
:format="ISO_FORMAT"
|
||||||
|
:display-format="persianDisplayFormat"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
color="#1677ff"
|
||||||
|
auto-submit
|
||||||
|
append-to="body"
|
||||||
|
input-class="ant-input persian-datepicker-input"
|
||||||
|
class="jalali-datepicker"
|
||||||
|
/>
|
||||||
|
<a-date-picker
|
||||||
|
v-else
|
||||||
|
:value="value"
|
||||||
|
:show-time="showTime ? { format: 'HH:mm:ss' } : false"
|
||||||
|
:format="format"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:style="{ width: '100%' }"
|
||||||
|
@update:value="onAntChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.jalali-datepicker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Theme overrides for the picker. AD-Vue 4 doesn't expose CSS variables
|
||||||
|
by default (its tokens live in JS), so we hardcode hexes per theme
|
||||||
|
class — `body.dark` for the navy theme, `[data-theme="ultra-dark"]`
|
||||||
|
for the neutral ultra-dark variant. The popup stays inside the
|
||||||
|
wrapper's subtree (no teleport) so global selectors reach it cleanly. -->
|
||||||
|
<style>
|
||||||
|
/* ===== Light (default) =================================================== */
|
||||||
|
|
||||||
|
.persian-datepicker-input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px 11px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persian-datepicker-input:hover {
|
||||||
|
border-color: #4096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persian-datepicker-input:focus {
|
||||||
|
border-color: #1677ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light theme keeps the picker's brand-blue calendar button (set via
|
||||||
|
* inline style on .vpd-icon-btn) — only its border + corner radius are
|
||||||
|
* normalized so it sits flush with the input. Dark/ultra-dark themes
|
||||||
|
* below override the inline blue so the control matches the form. */
|
||||||
|
.vpd-main .vpd-icon-btn {
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match the input's left edge (no rounded left, no double border at the
|
||||||
|
* seam) so it sits flush against the icon-btn. */
|
||||||
|
.persian-datepicker-input {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-clear-btn {
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-content {
|
||||||
|
background: #fff;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||||
|
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||||
|
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid rgba(5, 5, 5, 0.06);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-header {
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-header .vpd-year-label,
|
||||||
|
.vpd-main .vpd-header .vpd-date,
|
||||||
|
.vpd-main .vpd-header .vpd-locales li {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-body {
|
||||||
|
background: #fff;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-body .vpd-month-label,
|
||||||
|
.vpd-main .vpd-body .vpd-month-label > span {
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-body .vpd-week,
|
||||||
|
.vpd-main .vpd-body .vpd-weekday {
|
||||||
|
color: rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-body .vpd-controls .vpd-next,
|
||||||
|
.vpd-main .vpd-body .vpd-controls .vpd-prev {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The picker's <arrow> component renders an inline SVG with a hardcoded
|
||||||
|
* `fill="#000"` attribute. Override the path fill via CSS so the arrow
|
||||||
|
* is visible in every theme. */
|
||||||
|
.vpd-main .vpd-next svg path,
|
||||||
|
.vpd-main .vpd-prev svg path {
|
||||||
|
fill: rgba(0, 0, 0, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-body .vpd-controls .vpd-next:hover svg path,
|
||||||
|
.vpd-main .vpd-body .vpd-controls .vpd-prev:hover svg path {
|
||||||
|
fill: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The picker paints disabled days as `darken(#fff, 20%)` (~#cccccc) which
|
||||||
|
* is invisible on white and dark themes alike. Reset the day text color
|
||||||
|
* across all states so days are always readable. */
|
||||||
|
.vpd-main .vpd-day,
|
||||||
|
.vpd-main .vpd-day .vpd-day-text {
|
||||||
|
color: rgba(0, 0, 0, 0.88) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-day[disabled='true'],
|
||||||
|
.vpd-main .vpd-day[disabled='true'] .vpd-day-text {
|
||||||
|
color: rgba(0, 0, 0, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-day:not([disabled='true']):hover .vpd-day-text,
|
||||||
|
.vpd-main .vpd-day.vpd-selected .vpd-day-text {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-actions button {
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-actions button:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-addon-list,
|
||||||
|
.vpd-main .vpd-addon-list-content {
|
||||||
|
background: #fff;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-addon-list-item {
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-addon-list-item.vpd-selected,
|
||||||
|
.vpd-main .vpd-addon-list-item:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vpd-main .vpd-close-addon {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Dark (navy) ======================================================= */
|
||||||
|
|
||||||
|
body.dark .persian-datepicker-input {
|
||||||
|
background: #142340;
|
||||||
|
border-color: #1f3358;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .persian-datepicker-input:hover {
|
||||||
|
border-color: #4096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .persian-datepicker-input:focus {
|
||||||
|
border-color: #1677ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-icon-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
border: 1px solid #1f3358 !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-radius: 6px 0 0 6px !important;
|
||||||
|
color: rgba(255, 255, 255, 0.75) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-content {
|
||||||
|
background: #1a2c4d;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.32),
|
||||||
|
0 3px 6px -4px rgba(0, 0, 0, 0.48),
|
||||||
|
0 9px 28px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-body {
|
||||||
|
background: #1a2c4d;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-month-label,
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-month-label > span {
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-week,
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-weekday {
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-controls .vpd-next,
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-controls .vpd-prev {
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-next svg path,
|
||||||
|
body.dark .vpd-main .vpd-prev svg path {
|
||||||
|
fill: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-controls .vpd-next:hover svg path,
|
||||||
|
body.dark .vpd-main .vpd-body .vpd-controls .vpd-prev:hover svg path {
|
||||||
|
fill: #4096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-day,
|
||||||
|
body.dark .vpd-main .vpd-day .vpd-day-text {
|
||||||
|
color: rgba(255, 255, 255, 0.88) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-day[disabled='true'],
|
||||||
|
body.dark .vpd-main .vpd-day[disabled='true'] .vpd-day-text {
|
||||||
|
color: rgba(255, 255, 255, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-actions button {
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-actions button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-addon-list,
|
||||||
|
body.dark .vpd-main .vpd-addon-list-content {
|
||||||
|
background: #1a2c4d;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-addon-list-item {
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-addon-list-item.vpd-selected,
|
||||||
|
body.dark .vpd-main .vpd-addon-list-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .vpd-main .vpd-close-addon {
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Ultra-dark (neutral black) ======================================= */
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] .persian-datepicker-input {
|
||||||
|
background: #0a0a0a;
|
||||||
|
border-color: #303030;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] .vpd-main .vpd-icon-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
border: 1px solid #303030 !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-radius: 6px 0 0 6px !important;
|
||||||
|
color: rgba(255, 255, 255, 0.75) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] .vpd-main .vpd-content {
|
||||||
|
background: #141414;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] .vpd-main .vpd-body {
|
||||||
|
background: #141414;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='ultra-dark'] .vpd-main .vpd-addon-list,
|
||||||
|
html[data-theme='ultra-dark'] .vpd-main .vpd-addon-list-content {
|
||||||
|
background: #141414;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
frontend/src/composables/useDatepicker.js
Normal file
45
frontend/src/composables/useDatepicker.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Module-scoped reactive ref for the panel's "Calendar Type" setting.
|
||||||
|
// Loaded from /panel/setting/defaultSettings on first use, so any
|
||||||
|
// component (modals, inbound forms, future pages) can read the same
|
||||||
|
// value without prop-drilling and without re-fetching.
|
||||||
|
//
|
||||||
|
// useInbounds (which already reads defaultSettings for its own state)
|
||||||
|
// calls setDatepicker() after its fetch so we don't issue a second
|
||||||
|
// HTTP round-trip on the inbounds page.
|
||||||
|
|
||||||
|
import { readonly, ref } from 'vue';
|
||||||
|
import { HttpUtil } from '@/utils';
|
||||||
|
|
||||||
|
const datepicker = ref('gregorian');
|
||||||
|
let fetched = false;
|
||||||
|
let pending = null;
|
||||||
|
|
||||||
|
async function loadOnce() {
|
||||||
|
if (fetched) return;
|
||||||
|
if (pending) {
|
||||||
|
await pending;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pending = (async () => {
|
||||||
|
try {
|
||||||
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
|
if (msg?.success) {
|
||||||
|
datepicker.value = msg.obj?.datepicker || 'gregorian';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fetched = true;
|
||||||
|
pending = null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
await pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDatepicker(value) {
|
||||||
|
fetched = true;
|
||||||
|
datepicker.value = value || 'gregorian';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDatepicker() {
|
||||||
|
loadOnce();
|
||||||
|
return { datepicker: readonly(datepicker) };
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils';
|
import { ObjectUtil, NumberFormatter, SizeFormatter } from '@/utils';
|
||||||
import { Inbound, Protocols } from './inbound.js';
|
import { Inbound, Protocols } from './inbound.js';
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ export class DBInbound {
|
||||||
if (this.expiryTime === 0) {
|
if (this.expiryTime === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return moment(this.expiryTime);
|
return dayjs(this.expiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
set _expiryTime(t) {
|
set _expiryTime(t) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { ObjectUtil, RandomUtil, Base64, NumberFormatter, SizeFormatter, Wireguard } from '@/utils';
|
import { ObjectUtil, RandomUtil, Base64, NumberFormatter, SizeFormatter, Wireguard } from '@/utils';
|
||||||
|
|
||||||
export const Protocols = {
|
export const Protocols = {
|
||||||
|
|
@ -2523,7 +2524,7 @@ Inbound.ClientBase = class extends XrayCommonClass {
|
||||||
if (this.expiryTime < 0) {
|
if (this.expiryTime < 0) {
|
||||||
return this.expiryTime / -86400000;
|
return this.expiryTime / -86400000;
|
||||||
}
|
}
|
||||||
return moment(this.expiryTime);
|
return dayjs(this.expiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
set _expiryTime(t) {
|
set _expiryTime(t) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
USERS_SECURITY,
|
USERS_SECURITY,
|
||||||
TLS_FLOW_CONTROL,
|
TLS_FLOW_CONTROL,
|
||||||
} from '@/models/inbound.js';
|
} from '@/models/inbound.js';
|
||||||
|
import DateTimePicker from '@/components/DateTimePicker.vue';
|
||||||
|
|
||||||
// Bulk-add up to 500 clients in one go. The legacy panel offers five
|
// Bulk-add up to 500 clients in one go. The legacy panel offers five
|
||||||
// generation modes — this component preserves them all:
|
// generation modes — this component preserves them all:
|
||||||
|
|
@ -250,8 +251,7 @@ async function submit() {
|
||||||
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
||||||
}}</a-tooltip>
|
}}</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker v-model:value="expiryDate" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<DateTimePicker v-model:value="expiryDate" />
|
||||||
:style="{ width: '100%' }" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item v-if="form.expiryTime !== 0">
|
<a-form-item v-if="form.expiryTime !== 0">
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
ColorUtils,
|
ColorUtils,
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import { Inbound, Protocols, USERS_SECURITY, TLS_FLOW_CONTROL } from '@/models/inbound.js';
|
import { Inbound, Protocols, USERS_SECURITY, TLS_FLOW_CONTROL } from '@/models/inbound.js';
|
||||||
|
import DateTimePicker from '@/components/DateTimePicker.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|
@ -363,8 +364,7 @@ const title = computed(() =>
|
||||||
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
||||||
}}</a-tooltip>
|
}}</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker v-model:value="expiryDate" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<DateTimePicker v-model:value="expiryDate" />
|
||||||
:style="{ width: '100%' }" />
|
|
||||||
<a-tag v-if="mode === 'edit' && isExpired" color="red">{{ t('depleted') }}</a-tag>
|
<a-tag v-if="mode === 'edit' && isExpired" color="red">{{ t('depleted') }}</a-tag>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from '@/models/inbound.js';
|
} from '@/models/inbound.js';
|
||||||
import { DBInbound } from '@/models/dbinbound.js';
|
import { DBInbound } from '@/models/dbinbound.js';
|
||||||
import FinalMaskForm from '@/components/FinalMaskForm.vue';
|
import FinalMaskForm from '@/components/FinalMaskForm.vue';
|
||||||
|
import DateTimePicker from '@/components/DateTimePicker.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|
@ -572,8 +573,7 @@ watch(
|
||||||
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
<a-tooltip :title="t('pages.inbounds.leaveBlankToNeverExpire')">{{ t('pages.inbounds.expireDate')
|
||||||
}}</a-tooltip>
|
}}</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-date-picker v-model:value="expiryDate" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<DateTimePicker v-model:value="expiryDate" />
|
||||||
:style="{ width: '100%' }" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
@ -667,8 +667,7 @@ watch(
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="Expiry">
|
<a-form-item label="Expiry">
|
||||||
<a-date-picker v-model:value="clientExpiryDate" :show-time="{ format: 'HH:mm:ss' }"
|
<DateTimePicker v-model:value="clientExpiryDate" />
|
||||||
format="YYYY-MM-DD HH:mm:ss" :style="{ width: '100%' }" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { computed, ref, shallowRef } from 'vue';
|
||||||
import { HttpUtil, ObjectUtil } from '@/utils';
|
import { HttpUtil, ObjectUtil } from '@/utils';
|
||||||
import { DBInbound } from '@/models/dbinbound.js';
|
import { DBInbound } from '@/models/dbinbound.js';
|
||||||
import { Protocols } from '@/models/inbound.js';
|
import { Protocols } from '@/models/inbound.js';
|
||||||
|
import { setDatepicker } from '@/composables/useDatepicker.js';
|
||||||
|
|
||||||
const ONLINE_GRACE_MS = 60_000;
|
const ONLINE_GRACE_MS = 60_000;
|
||||||
|
|
||||||
|
|
@ -146,6 +147,9 @@ export function useInbounds() {
|
||||||
pageSize.value = s.pageSize ?? 0;
|
pageSize.value = s.pageSize ?? 0;
|
||||||
remarkModel.value = s.remarkModel || '-ieo';
|
remarkModel.value = s.remarkModel || '-ieo';
|
||||||
datepicker.value = s.datepicker || 'gregorian';
|
datepicker.value = s.datepicker || 'gregorian';
|
||||||
|
// Mirror into the global composable so date-pickers in modals can
|
||||||
|
// pick the right calendar without re-fetching the settings.
|
||||||
|
setDatepicker(datepicker.value);
|
||||||
ipLimitEnable.value = !!s.ipLimitEnable;
|
ipLimitEnable.value = !!s.ipLimitEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,14 @@ export default defineConfig({
|
||||||
if (id.includes('dayjs')) return 'vendor-dayjs';
|
if (id.includes('dayjs')) return 'vendor-dayjs';
|
||||||
if (id.includes('qrious')) return 'vendor-qrious';
|
if (id.includes('qrious')) return 'vendor-qrious';
|
||||||
if (id.includes('axios')) return 'vendor-axios';
|
if (id.includes('axios')) return 'vendor-axios';
|
||||||
|
// The persian datepicker pulls in moment + moment-jalaali; bundle
|
||||||
|
// the trio together so unrelated pages don't pay the cost.
|
||||||
|
if (
|
||||||
|
id.includes('vue3-persian-datetime-picker')
|
||||||
|
|| id.includes('moment-jalaali')
|
||||||
|
|| id.includes('jalaali-js')
|
||||||
|
|| id.includes('/node_modules/moment/')
|
||||||
|
) return 'vendor-jalali';
|
||||||
return 'vendor';
|
return 'vendor';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1
web/assets/moment/moment-jalali.min.js
vendored
1
web/assets/moment/moment-jalali.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,73 +0,0 @@
|
||||||
{{define "component/persianDatepickerTemplate"}}
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
|
|
||||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
|
||||||
:placeholder="placeholder">
|
|
||||||
<template #addonAfter>
|
|
||||||
<a-icon type="calendar" :style="{ fontSize: '14px', opacity: '0.5' }" />
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "component/aPersianDatepicker"}}
|
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
|
|
||||||
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
|
|
||||||
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
|
|
||||||
<script>
|
|
||||||
const persianDatepicker = {};
|
|
||||||
|
|
||||||
Vue.component('a-persian-datepicker', {
|
|
||||||
props: {
|
|
||||||
'format': {
|
|
||||||
type: undefined,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'value': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
'placeholder': {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
template: `{{template "component/persianDatepickerTemplate" .}}`,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
date: '',
|
|
||||||
persianDatepicker,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
value: function(date) {
|
|
||||||
this.date = this.convertToJalalian(date)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.date = this.convertToJalalian(this.value)
|
|
||||||
this.listenToDatepicker()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
convertToGregorian(date) {
|
|
||||||
return date ? moment(moment(date, 'jYYYY/jMM/jDD HH:mm:ss').format('YYYY-MM-DD HH:mm:ss')) :
|
|
||||||
null
|
|
||||||
},
|
|
||||||
convertToJalalian(date) {
|
|
||||||
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null
|
|
||||||
},
|
|
||||||
listenToDatepicker() {
|
|
||||||
jalaliDatepicker.startWatch({
|
|
||||||
time: true,
|
|
||||||
zIndex: '9999',
|
|
||||||
hideAfterChange: true,
|
|
||||||
useDropDownYears: false,
|
|
||||||
changeMonthRotateYear: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
Loading…
Reference in a new issue