init
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
BIN
.github/images/preview.png
vendored
Normal file
After Width: | Height: | Size: 11 KiB |
71
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm i
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm i
|
||||
|
||||
- name: TypeCheck
|
||||
run: pnpm run type-check
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node_version: [18.x]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
build_cmd: [build, 'build:mp-weixin', 'build:app']
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node version to ${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm i
|
||||
|
||||
- name: Build
|
||||
run: pnpm run ${{ matrix.build_cmd }}
|
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
3
.npmrc
Normal file
@ -0,0 +1,3 @@
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
shamefully-hoist=true
|
11
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"antfu.vite",
|
||||
"antfu.iconify",
|
||||
"antfu.unocss",
|
||||
"vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorConfig.editorConfig",
|
||||
"uni-helper.uni-helper-vscode"
|
||||
]
|
||||
}
|
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug h5",
|
||||
"type": "chrome",
|
||||
"runtimeArgs": [
|
||||
"--remote-debugging-port=9222"
|
||||
],
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"preLaunchTask": "uni:h5"
|
||||
}
|
||||
]
|
||||
}
|
86
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{
|
||||
"rule": "style/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-indent",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spacing",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spaces",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-order",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-dangle",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-newline",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*quotes",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*semi",
|
||||
"severity": "off"
|
||||
}
|
||||
],
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
],
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"vite.config.*": "pages.config.*, manifest.config.*, uno.config.*, volar.config.*, *.env, .env.*"
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/uni_modules": true,
|
||||
"**/build": true,
|
||||
"**/dist": true,
|
||||
"**/.git": true,
|
||||
"**/.vscode": true
|
||||
},
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/uni_modules/tmui/locale",
|
||||
"src/uni_modules/wot-design-uni/locale",
|
||||
"src/uni_modules/wot-design-uni/locale/lang",
|
||||
"src/uni_modules/tmui/tool/dayjs/locale",
|
||||
"src/uni_modules/uni-popup/components/uni-popup/i18n",
|
||||
"src/uni_modules/z-paging/components/z-paging/i18n",
|
||||
"src/uni_modules/tmui/tool/dayjs/esm/locale"
|
||||
],
|
||||
"vue-i18n.i18nPaths": "src\\uni_modules\\tmui\\locale,src\\uni_modules\\wot-design-uni\\locale,src\\uni_modules\\wot-design-uni\\locale\\lang,src\\uni_modules\\tmui\\tool\\dayjs\\locale,src\\uni_modules\\uni-popup\\components\\uni-popup\\i18n,src\\uni_modules\\z-paging\\components\\z-paging\\i18n,src\\uni_modules\\tmui\\tool\\dayjs\\esm\\locale"
|
||||
}
|
16
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "uni:h5",
|
||||
"type": "npm",
|
||||
"script": "dev --devtools",
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$vite",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
8
env/.env.dev
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'dev'
|
||||
# 是否显示console
|
||||
VITE_SHOW_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
# baseUrl
|
||||
VITE_BASEURL = 'http://warehouse.szjixun.cn/oa_backend'
|
8
env/.env.prod
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'prod'
|
||||
# 是否显示console
|
||||
VITE_SHOW_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
# baseUrl
|
||||
VITE_BASEURL = 'https://oa-a.szjixun.cn/api'
|
8
env/.env.test
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'test'
|
||||
# 是否显示console
|
||||
VITE_SHOW_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
# baseUrl
|
||||
VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
|
22
index.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="static/logo.svg">
|
||||
<style>
|
||||
</style>
|
||||
<script>
|
||||
const coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)')
|
||||
|| CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${
|
||||
coverSupport ? ', viewport-fit=cover' : ''}" />`)
|
||||
</script>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
65
package.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "unihelper",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.14.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test:h5": "uni --mode test",
|
||||
"prod:h5": "uni --mode prod",
|
||||
"build:h5:test": "uni build --mode test",
|
||||
"build:h5:prod": "uni build --mode prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-components": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-h5": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4000020240111001",
|
||||
"@uni-helper/axios-adapter": "^1.5.2",
|
||||
"@uni-helper/localforage-adapter": "^1.0.2",
|
||||
"@uni-helper/uni-use": "^0.19.12",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"axios": "^1.7.2",
|
||||
"dayjs": "^1.11.12",
|
||||
"nzh": "^1.0.13",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.3.8",
|
||||
"vue-i18n": "^9.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.7",
|
||||
"@dcloudio/uni-automator": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-vue-devtools": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-4000020240111001",
|
||||
"@iconify-json/carbon": "^1.1.27",
|
||||
"@types/node": "^20.11.4",
|
||||
"@uni-helper/uni-app-types": "^0.5.12",
|
||||
"@uni-helper/uni-env": "^0.1.1",
|
||||
"@uni-helper/unocss-preset-uni": "^0.2.9",
|
||||
"@uni-helper/volar-service-uni-pages": "^0.2.14",
|
||||
"@uni-ku/root": "^0.0.1",
|
||||
"@vue/runtime-core": "^3.3.8",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"lint-staged": "^15.2.0",
|
||||
"pinia": "2.0.36",
|
||||
"sass": "^1.77.8",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"typescript": "^5.3.3",
|
||||
"unocss": "^0.58.9",
|
||||
"unocss-applet": "^0.8.2",
|
||||
"vite": "^5.0.11",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
}
|
9983
pnpm-lock.yaml
Normal file
30
src/App.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import {useStatus} from "@/store/status";
|
||||
const {statusBarHeight}= useStatus()
|
||||
const root = document.documentElement
|
||||
root.style.setProperty('--statusBarHeight',`${statusBarHeight.value}px`)
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "@/static/css/color.scss";
|
||||
/* #ifdef APP-NVUE */
|
||||
@import '@/uni_modules/tmui/scss/nvue.css';
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
@import '@/uni_modules/tmui/scss/noNvue.css';
|
||||
/* #endif */
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/*解决阅览图片关闭按钮会显示在状态栏区域的问题*/
|
||||
#u-a-p>div>div{
|
||||
margin-top:var(--statusBarHeight)
|
||||
}
|
||||
/*不显示滚动条的类*/
|
||||
.no-scroll {
|
||||
-ms-overflow-style: none; /* IE 和 Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.no-scroll::-webkit-scrollbar {
|
||||
display: none; /* Webkit 浏览器 */
|
||||
}
|
||||
</style>
|
15
src/api/login/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import request from '@/service/index.js'
|
||||
export const login = (data) => {
|
||||
return request({
|
||||
url: '/oa/login',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
export const send = (data) => {
|
||||
return request({
|
||||
url: '/oa/send',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
29
src/components/avatar-module/index.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import TmImage from "@/uni_modules/tmui/components/tm-image/tm-image.vue";
|
||||
import {useAuth} from "@/store/auth";
|
||||
import { useClockIn } from "@/store/clockIn/index.js";
|
||||
const {userInfo}=useAuth()
|
||||
const {workingTimeInfoData,actionTypeData} = useClockIn()
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex-shrink-0 pl-[16rpx] pr-[40rpx] flex items-center rounded-[8rpx] w-[686rpx] h-[154rpx] bg-white">
|
||||
<div class="rounded-full overflow-hidden w-[96rpx] h-[96rpx]">
|
||||
<tm-image preview :width="96" :height="96" :src="userInfo.Avatar"></tm-image>
|
||||
</div>
|
||||
<div class="ml-[20rpx]">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[32rpx] text-black">{{ userInfo.NickName }}</div>
|
||||
<div class="mx-[14rpx] h-[30rpx] w-[1rpx] bg-[#F7F7F7]"></div>
|
||||
<div class="w-[40rpx] h-[40rpx]">
|
||||
<img v-if="actionTypeData.isWorkDay ===1" class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/zu3275@3x.png" alt="">
|
||||
<img v-else class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/rest3275@2x.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[5rpx] flex">
|
||||
<div class="text-[24rpx] text-[#999999]">{{ workingTimeInfoData.WorkTimeTemplateName }}</div>
|
||||
<div class="text-[#46299D] text-[24rpx]">(考勤规则)</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</template>
|
28
src/components/page-animation/index.css
Normal file
@ -0,0 +1,28 @@
|
||||
/* #ifdef H5 */
|
||||
uni-page {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
uni-page.animation-before {
|
||||
/* 在页面上使用 transform 会导致页面内的 fixed 定位渲染为 absolute,需要在动画完成后移除 */
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
uni-page.animation-leave {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
uni-page.animation-enter {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
uni-page.animation-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
uni-page.animation-after {
|
||||
/* 在页面上使用 transform 会导致页面内的 fixed 定位渲染为 absolute,需要在动画完成后移除 */
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* #endif */
|
37
src/components/page-animation/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script>
|
||||
import './index.css'
|
||||
export default {
|
||||
// #ifdef H5
|
||||
onLaunch: function() {
|
||||
this.show()
|
||||
this.$router.beforeEach((to, from, next) => {
|
||||
this.hide(next)
|
||||
})
|
||||
this.$router.afterEach(() => {
|
||||
setTimeout(this.show, 50)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
hide(callback) {
|
||||
const classList = document.querySelector('uni-page').classList
|
||||
classList.add('animation-before', 'animation-leave')
|
||||
classList.remove('animation-show')
|
||||
setTimeout(() => {
|
||||
classList.remove('animation-before', 'animation-leave')
|
||||
callback && callback()
|
||||
}, 200)
|
||||
},
|
||||
show() {
|
||||
const classList = document.querySelector('uni-page').classList
|
||||
classList.add('animation-before')
|
||||
setTimeout(() => {
|
||||
classList.add('animation-enter', 'animation-after', 'animation-show')
|
||||
setTimeout(() => {
|
||||
classList.remove('animation-before', 'animation-after', 'animation-enter')
|
||||
}, 200)
|
||||
}, 20)
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
</script>
|
155
src/components/x-calendar/index.vue
Normal file
@ -0,0 +1,155 @@
|
||||
|
||||
<script setup>
|
||||
import {nextTick, ref,computed,watch} from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useCalendar} from "@/store/calendar";
|
||||
const {generateCalendarData}= useCalendar()
|
||||
const current = ref(1);
|
||||
const swiperItems = [0, 1, 2];
|
||||
const showDays=(item)=>{
|
||||
if (current.value===item){
|
||||
return currentDays.value
|
||||
}else{
|
||||
if (current.value-item===1 ||
|
||||
current.value - item === -2){
|
||||
return preDays.value
|
||||
}else{
|
||||
return nextDays.value
|
||||
}
|
||||
}
|
||||
}
|
||||
const props=defineProps({
|
||||
value:{
|
||||
type:String,
|
||||
default:''
|
||||
}
|
||||
})
|
||||
const swiperHeight=()=>{
|
||||
return `${showDays(current.value)?.length/7 * 72}rpx`
|
||||
}
|
||||
const emit = defineEmits(['update:value','change-dates']);
|
||||
|
||||
const currentDays = ref([])
|
||||
const preDays = ref([])
|
||||
const nextDays = ref([])
|
||||
const nextMonth = computed(()=>{
|
||||
return dayjs(props.value, 'YYYY-MM').add(1, 'month').format('YYYY-MM')
|
||||
})
|
||||
const preMonth = computed(()=>{
|
||||
return dayjs(props.value, 'YYYY-MM').subtract(1, 'month').format('YYYY-MM')
|
||||
})
|
||||
const initDates=()=>{
|
||||
currentDays.value=generateCalendarData(props.value)
|
||||
preDays.value=generateCalendarData(preMonth.value)
|
||||
nextDays.value=generateCalendarData(nextMonth.value)
|
||||
emit('change-dates',showDays(current.value))
|
||||
}
|
||||
watch(()=>props.value,()=>{
|
||||
initDates()
|
||||
})
|
||||
const initLoad=()=>{
|
||||
initDates()
|
||||
}
|
||||
initLoad()
|
||||
function updateMonthsData(direction) {
|
||||
if (direction === "prev") {
|
||||
emit('update:value',preMonth.value)
|
||||
} else if (direction === "next") {
|
||||
emit('update:value',nextMonth.value)
|
||||
}
|
||||
nextTick(()=>{
|
||||
initDates()
|
||||
})
|
||||
}
|
||||
function handleSwiperChange(e) {
|
||||
const pre = current.value;
|
||||
const current2 = e.detail.current;
|
||||
/* 根据前一个减去目前的值我们可以判断是下一个月/周还是上一个月/周
|
||||
*current - pre === 1, -2时是下一个月/周
|
||||
*current -pre === -1, 2时是上一个月或者上一周
|
||||
*/
|
||||
current.value = current2;
|
||||
if (current2 - pre === 1 || current2 - pre === -2) {
|
||||
updateMonthsData('next');
|
||||
} else {
|
||||
updateMonthsData('prev');
|
||||
}
|
||||
|
||||
}
|
||||
const contract=ref(false)
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="x-calendar">
|
||||
<div class="content1">
|
||||
<div class="wrap1" v-for="day in ['日', '一', '二', '三', '四', '五', '六']" :key="day">
|
||||
{{ day }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content4"></div>
|
||||
<div class="content2"></div>
|
||||
<swiper :duration="500" :current="current" :style="{height: contract?'72rpx':swiperHeight(),transition: `height 0.2s ease`}" :indicator-dots="false" :circular="true" @change="handleSwiperChange">
|
||||
<swiper-item v-for="(item, index) in swiperItems" :key="item">
|
||||
<div class="content3">
|
||||
<div class="wrap1" v-for="(day,index1) in showDays(item)" :key="day.date">
|
||||
<slot name="cell" :data="day" :index="index1" ></slot>
|
||||
</div>
|
||||
</div>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<div class="content5" @click="contract=!contract">
|
||||
<div :class="`triangle ${contract?'rotate':''}`"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.x-calendar {
|
||||
.content5{
|
||||
padding-top: 22rpx;
|
||||
padding-bottom: 22rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 14rpx solid transparent;
|
||||
border-right: 14rpx solid transparent;
|
||||
border-bottom: 12rpx solid #C2C2C2;
|
||||
&.rotate{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.content4{
|
||||
margin-top: 32rpx;
|
||||
width: 100%;
|
||||
height: 1rpx;
|
||||
background-color: #EFEFF5;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.content3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.wrap1 {
|
||||
flex: 1 0 calc(100% / 7);
|
||||
}
|
||||
}
|
||||
|
||||
.content1 {
|
||||
margin-top: 46rpx;
|
||||
display: flex;
|
||||
.wrap1 {
|
||||
font-size: 28rpx;
|
||||
color: #191919;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
.content2 {
|
||||
height: 4rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
49
src/components/x-captcha/index.scss
Normal file
@ -0,0 +1,49 @@
|
||||
.ayi-captcha {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
&__input {
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
opacity: 0;
|
||||
}
|
||||
&__code {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
&__item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
&__value {
|
||||
font-size: 40rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
&__cursor {
|
||||
height: 40rpx;
|
||||
width: 4rpx;
|
||||
background-color: #000;
|
||||
animation: flash 1s infinite ease;
|
||||
}
|
||||
/* &.is-border {
|
||||
.ayi-captcha__item {
|
||||
border: 2rpx solid $uni-color-primary;
|
||||
}
|
||||
}*/
|
||||
@keyframes flash {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
63
src/components/x-captcha/index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="ayi-captcha" :class="{ 'is-border': border }">
|
||||
<input class="ayi-captcha__input" v-model="value" type="number" :focus="focus" :maxlength="len" @focus="onFocus" @blur="onBlur" @input="onInput" />
|
||||
<div class="ayi-captcha__code">
|
||||
<div class="ayi-captcha__item" :style="{ height: setRpx(height), margin: `0 ${setRpx(gutter)}`, backgroundColor }" v-for="(_, index) in list" :key="index">
|
||||
<span class="ayi-captcha__value">{{ value[index] }}</span>
|
||||
<div class="ayi-captcha__cursor" v-if="value.length == index && focus"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { setRpx } from "./tools.js"
|
||||
const props = defineProps({
|
||||
modelValue: String,
|
||||
focus: Boolean,
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 140
|
||||
},
|
||||
len: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
border: Boolean,
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#ebecee"
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(["update:modelValue", "done"])
|
||||
const value = ref<string>(props.modelValue || "")
|
||||
const focus = ref<boolean>(false)
|
||||
const list = computed(() => new Array(props.len).fill(1))
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val: any) => {
|
||||
value.value = val
|
||||
}
|
||||
)
|
||||
function onFocus() {
|
||||
focus.value = true
|
||||
}
|
||||
function onBlur() {
|
||||
focus.value = false
|
||||
}
|
||||
function onInput(e: any) {
|
||||
const val = e.detail.value
|
||||
emit("update:modelValue", val)
|
||||
if (val.length === props.len) {
|
||||
emit("done", val)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./index.scss";
|
||||
</style>
|
25
src/components/x-captcha/tools.js
Normal file
@ -0,0 +1,25 @@
|
||||
function getTag(value) {
|
||||
if (value == null) {
|
||||
return value === undefined ? "[object Undefined]" : "[object Null]"
|
||||
}
|
||||
return toString.call(value)
|
||||
}
|
||||
|
||||
function isObjectLike(value) {
|
||||
return typeof value === "object" && value !== null
|
||||
}
|
||||
|
||||
export function isNumber(value) {
|
||||
return typeof value === "number" || (isObjectLike(value) && getTag(value) == "[object Number]")
|
||||
}
|
||||
|
||||
export function isBoolean(value) {
|
||||
return typeof value === "boolean"
|
||||
}
|
||||
|
||||
export function isArray(value) {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
export function setRpx(val ) {
|
||||
return isArray(val) ? val.map(setRpx).join(" ") : isNumber(val) ? `${val}rpx` : val
|
||||
}
|
50
src/components/x-confirm/index.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<script setup>
|
||||
import {ref,nextTick} from 'vue'
|
||||
import WdPopup from "@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue";
|
||||
import TmButton from "@/uni_modules/tmui/components/tm-button/tm-button.vue";
|
||||
const confirmState=ref(false)
|
||||
const cancel=ref(true)
|
||||
let onConfirm=null
|
||||
let onCancel=null
|
||||
const confirm=ref(true)
|
||||
const contentText=ref('')
|
||||
const sendCancel=()=>{
|
||||
confirmState.value=false
|
||||
if (typeof onCancel==='function'){
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
const sendConfirm=()=>{
|
||||
confirmState.value=false
|
||||
if (typeof onConfirm==='function'){
|
||||
onConfirm()
|
||||
}
|
||||
}
|
||||
const showConfirm=({content,onConfirm:confirm,onCancel:cancel})=>{
|
||||
confirmState.value=true
|
||||
contentText.value=content
|
||||
onConfirm=confirm
|
||||
onCancel=cancel
|
||||
}
|
||||
defineExpose({
|
||||
showConfirm
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<wd-popup custom-style="border-radius: 16rpx;" modal-style="background-color: rgba(0,0,0,0.3);" v-model="confirmState">
|
||||
<div class="flex flex-col w-[640rpx] h-[402rpx]">
|
||||
<div class="flex justify-center items-center h-[288rpx] text-[32rpx] font-bold text-[#1A1A1A]">
|
||||
{{contentText}}
|
||||
</div>
|
||||
<div class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]">
|
||||
<div class="flex justify-center items-center text-[#1A1A1A]">
|
||||
<tm-button @click="sendCancel" :width="319" @touchstart="cancel=false" @touchend="cancel=true" :fontSize="32" :height="112" :margin="[0]" :font-color="'#1A1A1A'" :transprent="cancel" text label="取消"></tm-button>
|
||||
</div>
|
||||
<div class="h-[112rpx] w-[1rpx] bg-[#E7E7E7]"></div>
|
||||
<div class="flex justify-center items-center text-[#CF3050]">
|
||||
<tm-button @click="sendConfirm" @touchstart="confirm=false" @touchend="confirm=true" :width="319" :fontSize="32" :transprent="confirm" :height="112" :margin="[0]" :font-color="'#46299D'" text label="确定"></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wd-popup>
|
||||
</template>
|
19
src/components/x-confirm/useConfirm.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { createApp } from 'vue';
|
||||
import confirmPopup from '@/components/x-confirm/index.vue'
|
||||
|
||||
export default function useConfirm() {
|
||||
function showConfirm(obj) {
|
||||
const instance = createApp(confirmPopup,{
|
||||
//监听消息关闭事件
|
||||
onAfterLeave:()=>{
|
||||
instance.unmount();
|
||||
document.body.removeChild(mountNode);
|
||||
}
|
||||
});
|
||||
const mountNode = document.createElement('div');
|
||||
document.body.appendChild(mountNode);
|
||||
const vm = instance.mount(mountNode);
|
||||
vm.showConfirm(obj)
|
||||
}
|
||||
return { showConfirm };
|
||||
}
|
53
src/components/x-date-select/index.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {theme} from "@/config/theme";
|
||||
const props = defineProps({
|
||||
format: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showSuffix:{
|
||||
type: Object,
|
||||
default: () => {
|
||||
return{ year: '年', month: '月', day: '日', hour: '时', minute: '分', second: '秒' }
|
||||
}
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onConfirm: Function,
|
||||
})
|
||||
|
||||
const showDetail = computed(() => ({
|
||||
year: props.format.includes('YYYY'),
|
||||
month: props.format.includes('MM'),
|
||||
day: props.format.includes('DD'),
|
||||
hour: props.format.includes('HH'),
|
||||
minute: props.format.includes('mm'),
|
||||
second: props.format.includes('ss'),
|
||||
ampm: props.format.includes('A'),
|
||||
}))
|
||||
|
||||
const defaultValue = computed(() => dayjs(`${props.value}-01-01`).valueOf());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tm-time-picker
|
||||
:showDetail="showDetail"
|
||||
:show="show"
|
||||
:model-str="value"
|
||||
:default-value="defaultValue"
|
||||
:color="theme.colors.primary"
|
||||
v-bind="{...$props,...$attrs}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
55
src/components/x-date-select/pickerService.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { createApp, h, ref } from 'vue';
|
||||
import xDateSelect from './index.vue';
|
||||
import dayjs from "dayjs";
|
||||
//根据传入的日期格式和日期 生成对应的时间戳
|
||||
function generateTimestamp(format, date) {
|
||||
// 直接使用传入的 format 字符串作为解析格式
|
||||
const dateStr = dayjs(date, format);
|
||||
|
||||
if (!dateStr.isValid()) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
|
||||
return dateStr.valueOf(); // 返回时间戳
|
||||
}
|
||||
let datePickerApp = null;
|
||||
let container=null
|
||||
export function openDatePicker(options = {}) {
|
||||
// 如果已经存在一个日期选择器实例,先销毁它
|
||||
if (datePickerApp) {
|
||||
datePickerApp.unmount();
|
||||
datePickerApp = null; // 防止重复销毁
|
||||
}
|
||||
if (container){
|
||||
//清除dom
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
|
||||
// 创建日期选择器实例
|
||||
datePickerApp = createApp({
|
||||
setup() {
|
||||
const showdate = ref(true)
|
||||
// 处理确认事件
|
||||
const handleConfirm = (value) => {
|
||||
options?.onConfirm?.(dayjs(value).format(options.format)); // 使用可选链调用
|
||||
showdate.value = false; // 关闭日期选择器
|
||||
};
|
||||
return () => h(xDateSelect, {
|
||||
...options,
|
||||
defaultValue: generateTimestamp(options.format, options.value),
|
||||
'onUpdate:show': (newValue) => {
|
||||
showdate.value = newValue;
|
||||
if (!newValue) {
|
||||
datePickerApp?.unmount(); // 确保不会重复销毁
|
||||
datePickerApp = null;
|
||||
}
|
||||
},
|
||||
onConfirm: handleConfirm,
|
||||
show: showdate.value,
|
||||
});
|
||||
}
|
||||
})
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
datePickerApp.mount(container);
|
||||
}
|
16
src/components/x-loaderror/index.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
/*setTimeout(()=>{
|
||||
location.reload()
|
||||
},2000)*/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>加载错误加载错误加载错误加载错误加载错误加载错误加载错误加载错误</div>
|
||||
<div>加载错误加载错误加载错误加载错误加载错误加载错误加载错误加载错误</div>
|
||||
<div>加载错误加载错误加载错误加载错误加载错误加载错误加载错误加载错误</div>
|
||||
<div>加载错误加载错误加载错误加载错误加载错误加载错误加载错误加载错误</div>
|
||||
<div>加载错误加载错误加载错误加载错误加载错误加载错误加载错误加载错误</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
39
src/components/x-loading/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { createVNode, render } from 'vue'
|
||||
import Loading from './index.vue'
|
||||
|
||||
const vnode = createVNode(Loading)
|
||||
|
||||
export const vLoading = {
|
||||
mounted(el, binding) {
|
||||
render(vnode, el)
|
||||
formatterClass(el, binding)
|
||||
},
|
||||
updated(el, binding) {
|
||||
if (binding.value) {
|
||||
vnode?.component?.exposed.show()
|
||||
} else {
|
||||
vnode?.component?.exposed.hide()
|
||||
}
|
||||
formatterClass(el, binding)
|
||||
},
|
||||
unmounted() {
|
||||
vnode?.component?.exposed.hide()
|
||||
},
|
||||
}
|
||||
|
||||
function formatterClass(el, binding) {
|
||||
const classStr = el.className
|
||||
const hasTargetClass = classStr.includes('loading-parent')
|
||||
|
||||
if (binding.value) {
|
||||
if (!hasTargetClass) {
|
||||
el.classList.add('loading-parent')
|
||||
el.style.position = 'relative'
|
||||
}
|
||||
} else {
|
||||
if (hasTargetClass) {
|
||||
el.classList.remove('loading-parent')
|
||||
el.style.position = ''
|
||||
}
|
||||
}
|
||||
}
|
57
src/components/x-loading/index.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<!-- -->
|
||||
<template>
|
||||
<div v-if="isShow" class="absolute inset-0 w-full h-full overflow-hidden z-[3] bg-[rgba(255,255,255,0.8)]">
|
||||
<div class="flex flex-col justify-center items-center absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
<wd-loading :color="theme.colors.primary" />
|
||||
<div class="mt-[2rpx] text-[25rpx]">
|
||||
{{tip}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import WdLoading from "@/uni_modules/wot-design-uni/components/wd-loading/wd-loading.vue";
|
||||
import {theme} from "@/config/theme";
|
||||
|
||||
const props = defineProps({
|
||||
tip: {
|
||||
type: String,
|
||||
default() {
|
||||
return "加载中...";
|
||||
},
|
||||
},
|
||||
maskBackground: {
|
||||
type: String,
|
||||
default() {
|
||||
return "rgba(255, 255, 255, 0.8)";
|
||||
},
|
||||
},
|
||||
loadingColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return "rgba(255, 255, 255, 1)";
|
||||
},
|
||||
},
|
||||
textColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return "rgba(255, 255, 255, 1)";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isShow = ref(false);
|
||||
const show = () => {
|
||||
isShow.value = true;
|
||||
};
|
||||
const hide = () => {
|
||||
isShow.value = false;
|
||||
};
|
||||
defineExpose({
|
||||
show,
|
||||
hide,
|
||||
isShow,
|
||||
});
|
||||
</script>
|
||||
|
52
src/components/x-message/index.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import message from './message/index.vue'
|
||||
import { ref } from 'vue';
|
||||
const visible = ref(false);
|
||||
const messageText = ref('');
|
||||
const messageType = ref('');
|
||||
|
||||
const emit = defineEmits(['after-leave']);
|
||||
const onAfterLeave=()=>{
|
||||
emit('after-leave');
|
||||
}
|
||||
const showMessage=({ type = 'warning', message, duration = 2000 })=> {
|
||||
messageText.value = message;
|
||||
messageType.value = type;
|
||||
visible.value = true;
|
||||
setTimeout(() => {
|
||||
hideMessage()
|
||||
}, duration);
|
||||
}
|
||||
const hideMessage=()=> {
|
||||
visible.value = false;
|
||||
}
|
||||
defineExpose({
|
||||
showMessage,
|
||||
hideMessage
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<transition name="fade" @after-leave="onAfterLeave">
|
||||
<message v-if="visible" :text="messageText" :type="messageType" class="message-popup"></message>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.1s;
|
||||
}
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.message-popup {
|
||||
position: fixed;
|
||||
top: 184rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 99999;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
68
src/components/x-message/message/index.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
const props=defineProps({
|
||||
type:{
|
||||
type:String,
|
||||
default:'success'
|
||||
},
|
||||
text:{
|
||||
type:String,
|
||||
default:''
|
||||
}
|
||||
})
|
||||
|
||||
const typeList=ref({
|
||||
success:{
|
||||
imgSrc:new URL(`@/static/image/login/xzsu@3x.png`, import.meta.url).href,
|
||||
borderColor:'#C5E7D5',
|
||||
bgColor:'#EDF7F2'
|
||||
},
|
||||
error:{
|
||||
imgSrc:new URL(`@/static/image/login/gth@3x.png`, import.meta.url).href,
|
||||
borderColor:'#F3CBD3',
|
||||
bgColor:'#FBEEF1'
|
||||
},
|
||||
warning:{
|
||||
imgSrc:new URL(`@/static/image/login/warn@3x.png`, import.meta.url).href,
|
||||
borderColor:'#FAE0B5',
|
||||
bgColor:'#FEF7ED'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="content4" :style="{border:`2rpx solid ${typeList[type].borderColor}`,backgroundColor:typeList[type].bgColor}">
|
||||
<div class="wrap1">
|
||||
<img :src="typeList[type].imgSrc" alt="">
|
||||
</div>
|
||||
<div class="wrap2">{{text}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content4{
|
||||
filter: drop-shadow(0px 0px 5px rgba(0,0,0,0.1));
|
||||
box-sizing: border-box;
|
||||
padding-left: 30rpx;
|
||||
padding-right: 30rpx;
|
||||
width: 686rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.wrap2{
|
||||
color: #000;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.wrap1{
|
||||
margin-right: 18rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
img{
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
19
src/components/x-message/useMessagePopup.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { createApp } from 'vue';
|
||||
import MessagePopup from '@/components/x-message/index.vue'
|
||||
|
||||
export default function useMessagePopup() {
|
||||
function showMessage(obj) {
|
||||
const messageInstance = createApp(MessagePopup,{
|
||||
//监听消息关闭事件
|
||||
onAfterLeave:()=>{
|
||||
messageInstance.unmount();
|
||||
document.body.removeChild(mountNode);
|
||||
}
|
||||
});
|
||||
const mountNode = document.createElement('div');
|
||||
document.body.appendChild(mountNode);
|
||||
const vm = messageInstance.mount(mountNode);
|
||||
vm.showMessage(obj)
|
||||
}
|
||||
return { showMessage };
|
||||
}
|
11
src/components/x-navbar/index.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue';
|
||||
import {useStatus} from "@/store/status"
|
||||
const {currentNavbar} = useStatus()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tm-navbar :hideBack="false" hideHome :title="currentNavbar.title"/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
</style>
|
20
src/components/x-paging/index.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
const pagingRef=ref(null)
|
||||
defineExpose({
|
||||
pagingRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<z-paging ref="pagingRef" :show-scrollbar="false" :refresher-end-bounce-enabled="false" :refresher-complete-duration="500" :refresher-complete-delay="500" :refresher-fps="60" show-refresher-update-time use-virtual-list :fixed="false" v-bind="{ ...$attrs, ...$props}">
|
||||
<template v-for="(slot, name) in $slots" :key="name" #[name]>
|
||||
<slot :name="name"></slot>
|
||||
</template>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
19
src/components/x-tabbar/components/tabbar-item/index.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
79
src/components/x-tabbar/index.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup>
|
||||
import { theme } from '@/config/theme'
|
||||
import tabbarItem from './components/tabbar-item/index.vue'
|
||||
const emit = defineEmits(['update:active'])
|
||||
const props=defineProps({
|
||||
active:{
|
||||
type:Number,
|
||||
default:0
|
||||
},
|
||||
list:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
})
|
||||
const clickItem = (item) => {
|
||||
emit('update:active',item.value)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="tabbar-container">
|
||||
<div class="tabbar">
|
||||
<tabbar-item v-for="item in list" :key="item.url" @click="clickItem(item)">
|
||||
<div class="tab-content">
|
||||
<img :src="item.value === active ? item.selectedIconPath : item.iconPath" :style="{width:item.iconWidth?item.iconWidth:'34rpx',height:item.iconHeight?item.iconHeight:'40rpx'}" class="tab-icon">
|
||||
<div class="tab-text" :style="{ color: active === item.value ? theme.colors.primary : '#666666' }">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</tabbar-item>
|
||||
</div>
|
||||
<!--底部安全区-->
|
||||
<div class="content-placeholder"></div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.tabbar-container {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
.tabbar {
|
||||
display: flex;
|
||||
padding-top: 20rpx;
|
||||
height: 104rpx;
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.tab-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
margin-top: 10rpx;
|
||||
font-size: 20rpx;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-placeholder {
|
||||
height: 58rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
1
src/config/index.js
Normal file
@ -0,0 +1 @@
|
||||
export const baiduMapAK='hMPtCMLVem8gOeuFud9CEXC8k7woOWiq'
|
58
src/config/tabbar/index.js
Normal file
@ -0,0 +1,58 @@
|
||||
// 图片资源导入
|
||||
import clockInIcon from '@/static/image/tabbar/wz@3x1.png' // 打卡图标
|
||||
import clockInSelectedIcon from '@/static/image/tabbar/zu3499@3x.png' // 打卡选中图标
|
||||
import attendanceIcon from '@/static/image/tabbar/attendance2.png' // 考勤图标
|
||||
import attendanceSelectedIcon from '@/static/image/tabbar/kaoqin.png' // 考勤选中图标
|
||||
import myIcon from '@/static/image/tabbar/my1.png' // 我的图标
|
||||
import mySelectedIcon from '@/static/image/tabbar/my2.png' // 我的选中图标
|
||||
import applyIcon from '@/static/image/apply/zu3809@3x.png' // 申请图标
|
||||
import applySelectedIcon from '@/static/image/apply/zu3808@3x.png' // 申请选中图标
|
||||
import spIcon from '@/static/image/apply/sp.png' // 审批图标
|
||||
import spSelectedIcon from '@/static/image/apply/zu3812@3x.png' // 审批选中图标
|
||||
|
||||
// Tabbar 配置
|
||||
export const tabbar = [
|
||||
{
|
||||
text: "打卡",
|
||||
iconPath: clockInIcon,
|
||||
selectedIconPath: clockInSelectedIcon,
|
||||
value: 0,
|
||||
iconWidth: '33.32rpx',
|
||||
iconHeight: '40rpx',
|
||||
url: '/pages/clockIn/index'
|
||||
},
|
||||
{
|
||||
text: "考勤",
|
||||
iconPath: attendanceIcon,
|
||||
selectedIconPath: attendanceSelectedIcon,
|
||||
value: 1,
|
||||
url: '/pages/attendance/index'
|
||||
},
|
||||
{
|
||||
text: "我的",
|
||||
iconPath: myIcon,
|
||||
selectedIconPath: mySelectedIcon,
|
||||
value: 3,
|
||||
url: '/pages/mine/index'
|
||||
}
|
||||
]
|
||||
|
||||
// 审批页面 Tabbar 配置
|
||||
export const approveTabbar = [
|
||||
{
|
||||
text: "申请",
|
||||
iconPath: applyIcon,
|
||||
selectedIconPath: applySelectedIcon,
|
||||
value: 0,
|
||||
iconWidth: '33.32rpx',
|
||||
iconHeight: '40rpx'
|
||||
},
|
||||
{
|
||||
text: "审批中心",
|
||||
iconPath: spIcon,
|
||||
selectedIconPath: spSelectedIcon,
|
||||
value: 1,
|
||||
iconWidth: '37rpx',
|
||||
iconHeight: '40rpx'
|
||||
}
|
||||
]
|
12
src/config/theme/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
export const theme = {
|
||||
colors: {
|
||||
primary: '#46299D',
|
||||
secondary: '#35495e',
|
||||
accent: '#ff4081',
|
||||
error: '#f44336',
|
||||
warning: '#ff9800',
|
||||
info: '#2196f3',
|
||||
success: '#4caf50'
|
||||
}
|
||||
};
|
8
src/config/tmui/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
//config.ts
|
||||
import {theme} from "@/config/theme/index.js";
|
||||
|
||||
export const config = {
|
||||
theme:{
|
||||
primary:theme.colors.primary
|
||||
}
|
||||
}
|
51
src/main.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import dayjs from "dayjs";
|
||||
import 'virtual:uno.css'
|
||||
import VConsole from "vconsole";
|
||||
import '@/utils/uni.webview.js'
|
||||
import tmui from "@/uni_modules/tmui"
|
||||
import {config} from "@/config/tmui/index.js";
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import xLoaderror from '@/components/x-loaderror/index.vue'
|
||||
import { vLoading } from "@/components/x-loading/index.js"
|
||||
import messagePopup from '@/components/x-message/useMessagePopup'
|
||||
import pageAnimation from '@/components/page-animation/index.vue'
|
||||
const {showMessage}=messagePopup()
|
||||
dayjs.locale('zh-cn')
|
||||
if (import.meta.env.VITE_SHOW_CONSOLE){
|
||||
new VConsole()
|
||||
}
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.use(tmui,{...config})
|
||||
app.directive("loading", vLoading)
|
||||
app.mixin(pageAnimation)
|
||||
app.component('x-loaderror',xLoaderror)
|
||||
app.directive('no-space', {
|
||||
mounted(el) {
|
||||
el.addEventListener('input', (e) => {
|
||||
const originalValue = e.target.value;
|
||||
const newValue = originalValue.replace(/\s/g, '');
|
||||
if (originalValue !== newValue) {
|
||||
e.target.value = newValue;
|
||||
e.target.dispatchEvent(new Event('input'));
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
window.message = ['success', 'error', 'warning'].reduce((acc, type) => {
|
||||
acc[type] = (message) => {
|
||||
if (typeof message === 'string') {
|
||||
showMessage({ type, message });
|
||||
} else if (typeof message === 'object') {
|
||||
showMessage({ type, ...message });
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
return {
|
||||
app,
|
||||
}
|
||||
}
|
||||
createApp()
|
101
src/manifest.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "oa-host",
|
||||
"appid": "",
|
||||
"description": "",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
"modules": {},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {},
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true,
|
||||
"darkmode": true,
|
||||
"themeLocation": "theme.json"
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"h5": {
|
||||
"darkmode": true,
|
||||
"template": "index.html",
|
||||
"async": {
|
||||
"loading": "AsyncLoading",
|
||||
"error": "AsyncError",
|
||||
"delay": 0,
|
||||
"timeout": 60000
|
||||
},
|
||||
"sdkConfigs": {
|
||||
// 使用地图或位置相关功能必须填写其一
|
||||
"maps": {
|
||||
"qqmap": {
|
||||
// 腾讯地图秘钥 https://lbs.qq.com/dev/console/key/manage
|
||||
"key": ""
|
||||
},
|
||||
"google": {
|
||||
// 谷歌地图秘钥(HBuilderX 3.2.10+)https://developers.google.com/maps/documentation/javascript/get-api-key
|
||||
"key": ""
|
||||
},
|
||||
"amap": {
|
||||
// 高德地图秘钥(HBuilderX 3.6.0+)https://console.amap.com/dev/key/app
|
||||
"key": "",
|
||||
// 高德地图安全密钥(HBuilderX 3.6.0+)https://console.amap.com/dev/key/app
|
||||
"securityJsCode": "",
|
||||
// 高德地图安全密钥代理服务器地址(HBuilderX 3.6.0+)https://lbs.amap.com/api/jsapi-v2/guide/abc/prepare
|
||||
"serviceHost": ""
|
||||
},
|
||||
"bmap": {
|
||||
// 百度地图秘钥(HBuilderX 3.99+)http://lbsyun.baidu.com/apiconsole/key#/home
|
||||
"key": "T4vteUoFUBsv6a5cxtw6kOInWb5nloxc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
src/pages.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"easycom":{
|
||||
"autoscan": true,
|
||||
"custom":{
|
||||
"^tm-(.*)": "@/tmui/components/tm-$1/tm-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"type": "page"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"type": "page",
|
||||
"style": {}
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
"globalStyle": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextstyle": "black",
|
||||
"navigationBarTitleText":""
|
||||
|
||||
},
|
||||
"subPackages": []
|
||||
}
|
17
src/pages/index/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
import XTabbar from "@/components/x-tabbar/index.vue"
|
||||
import { tabbar } from '@/config/tabbar/index.js'
|
||||
import {useStatus} from "@/store/status"
|
||||
|
||||
const {tabBarIndex}= useStatus()
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col h-[100vh]" >
|
||||
|
||||
123
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
367
src/pages/login/index.vue
Normal file
@ -0,0 +1,367 @@
|
||||
<script setup>
|
||||
|
||||
import {theme} from "@/config/theme";
|
||||
import xCaptcha from '@/components/x-captcha/index.vue'
|
||||
import {ref} from 'vue'
|
||||
import {login, send} from "@/api/login";
|
||||
import {useAuth} from "@/store/auth";
|
||||
const { token, refreshToken ,userInfo}=useAuth()
|
||||
const formData=ref({
|
||||
telNum: '',
|
||||
password: '',
|
||||
code: '',
|
||||
})
|
||||
const currentLoginType=ref('password')
|
||||
const showPassword=ref(true)
|
||||
const changeType=()=>{
|
||||
currentLoginType.value=currentLoginType.value==='password'?'code':'password'
|
||||
}
|
||||
const changeType1=()=>{
|
||||
currentLoginType.value=currentLoginType.value==='password'?'code':'password'
|
||||
verificationCode.value=false
|
||||
}
|
||||
const agreement=ref(false)
|
||||
const sendLogin= async ()=>{
|
||||
const res=await login(formData.value)
|
||||
if (res.status===0){
|
||||
userInfo.value=res.data.AccountInfo
|
||||
token.value=res.data.Token
|
||||
refreshToken.value = res.data.RefreshToken;
|
||||
message.success('登录成功')
|
||||
uni.navigateTo({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
const checkPassWordAndTelNum = () => {
|
||||
const { telNum, password } = formData.value;
|
||||
if (!telNum || !password) {
|
||||
message.warning(!telNum ? '请输入手机号' : '请输入密码')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const checkTelNum = () => {
|
||||
const { telNum } = formData.value;
|
||||
if (!telNum) {
|
||||
message.warning('请输入手机号' )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const sendCode=async ()=>{
|
||||
const res=await send({telNum:formData.value.telNum})
|
||||
if (res.status===0){
|
||||
message.success('发送成功')
|
||||
}
|
||||
}
|
||||
const countdown = ref(0);
|
||||
const isCounting = ref(false);
|
||||
const loginClick=async ()=>{
|
||||
if (currentLoginType.value==='password'){
|
||||
if (!checkPassWordAndTelNum()){
|
||||
return
|
||||
}
|
||||
loading.value=true
|
||||
await sendLogin()
|
||||
loading.value=false
|
||||
}else if (currentLoginType.value==='code'){
|
||||
if (!checkTelNum()){
|
||||
return
|
||||
}
|
||||
loading.value=true
|
||||
await sendCode()
|
||||
startCountdown()
|
||||
loading.value=false
|
||||
verificationCode.value=true
|
||||
}
|
||||
}
|
||||
const verificationCode=ref(false)
|
||||
const loading=ref(false)
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60;
|
||||
isCounting.value = true;
|
||||
const interval = setInterval(() => {
|
||||
countdown.value -= 1;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(interval);
|
||||
isCounting.value = false;
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
const sendCodeClick=()=>{
|
||||
startCountdown()
|
||||
}
|
||||
const updateCode=async (e)=>{
|
||||
if (e.length===6){
|
||||
await sendLogin()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div class="content1">
|
||||
<div class="wrap1">
|
||||
登录 <span class="wrap1_1">OA考勤</span>
|
||||
</div>
|
||||
<div class="wrap2">使用你的手机号登录</div>
|
||||
</div>
|
||||
<template v-if="!verificationCode">
|
||||
<div class="content2">
|
||||
<div class="wrap1">
|
||||
<div class="wrap1_1">手机号</div>
|
||||
<div class="wrap1_2">
|
||||
<input type="number" style="width: 310rpx" placeholder-style="color: #B8B8B8;" v-model="formData.telNum" placeholder="请输入手机号">
|
||||
</div>
|
||||
<div v-if="formData.telNum" class="wrap1_3" @click="formData.telNum=''">
|
||||
<image src="@/static/image/login/check-circle-filled@3x.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap2" v-show="currentLoginType==='password'">
|
||||
<div class="wrap2_1">密码</div>
|
||||
<div class="wrap2_2">
|
||||
<input :password="showPassword" style="width: 310rpx" placeholder-style="color: #B8B8B8;" v-model="formData.password" placeholder="请输入密码">
|
||||
</div>
|
||||
<div class="wrap2_3">
|
||||
<div class="wrap2_3_1" @click="formData.password=''">
|
||||
<img v-show="formData.password" src="@/static/image/login/check-circle-filled@3x.png"/>
|
||||
</div>
|
||||
<div class="wrap2_3_2" @click="showPassword=!showPassword" >
|
||||
<img v-show="showPassword" class="wrap2_3_2_1" src="@/static/image/login/browse@3x.png"/>
|
||||
<img v-show="!showPassword" class="wrap2_3_2_2" src="@/static/image/login/browse-off@3x.png"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap3">
|
||||
<tm-button :fontSize="28" :fontColor="theme.colors.primary" transprent text @click="changeType" >{{currentLoginType==='password'?'验证码登录':'密码登录'}}</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content3">
|
||||
<div class="wrap1">
|
||||
<div class="wrap1_1">
|
||||
<tm-checkbox :color="theme.colors.primary" v-model="agreement" :margin="[0,0]" class="wrap1_1_1" :size="30" :round="10"></tm-checkbox>
|
||||
</div>
|
||||
<div class="wrap1_2">
|
||||
已阅读并同意
|
||||
<div class="wrap1_2_1">《用户服务协议》</div>
|
||||
<div class="wrap1_2_2">《隐私政策》</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrap2">
|
||||
<tm-button v-if="agreement" @click="loginClick" linear="right" :color="theme.colors.primary" :loading="loading" icon="tmicon-tongzhifill" :height="96" block>{{currentLoginType==='password'?'登录':'获取验证码'}}</tm-button>
|
||||
<tm-button v-if="!agreement" fontColor="#fff" color="#D9D9DB" :height="96" block>{{currentLoginType==='password'?'登录':'获取验证码'}}</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="verificationCode">
|
||||
<div class="content4">
|
||||
<div class="wrap1">
|
||||
<div class="wrap1_1">已发送验证码至</div>
|
||||
<div class="wrap1_2">{{formData.telNum}}</div>
|
||||
</div>
|
||||
<div class="wrap2">
|
||||
<x-captcha @done="updateCode" v-model:model-value="formData.code" :len="6" :focus="true" :gutter="10" :height="100"></x-captcha>
|
||||
</div>
|
||||
<div class="wrap3">
|
||||
<div class="wrap3_1">
|
||||
<tm-button :fontSize="28" :fontColor="isCounting?'#BDBDBD':theme.colors.primary" transprent text @click="sendCodeClick" >重新发送 <span class="wrap3_1_1">{{countdown===0?'':`(${countdown})`}}</span></tm-button>
|
||||
</div>
|
||||
<div class="wrap3_2">
|
||||
<tm-button :fontSize="28" :fontColor="theme.colors.primary" transprent text @click="changeType1" >密码登录</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.outer-layer{
|
||||
box-sizing: border-box;
|
||||
background-image: url("@/static/image/login/gdz@3x.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding-right: 56rpx;
|
||||
padding-left: 56rpx;
|
||||
.content4{
|
||||
margin-top: 84rpx;
|
||||
.wrap3{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.wrap3_1{
|
||||
.wrap3_1_1{
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrap2{
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.wrap1{
|
||||
display: flex;
|
||||
.wrap1_2{
|
||||
font-size: 28rpx;
|
||||
color: #191818;
|
||||
}
|
||||
.wrap1_1{
|
||||
margin-right: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #BDBDBD;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content3{
|
||||
margin-top: 46rpx;
|
||||
.wrap2{
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
.wrap1{
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.wrap1_1{
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
.wrap1_2{
|
||||
display: flex;
|
||||
font-size: 28rpx;
|
||||
.wrap1_2_1{
|
||||
color: $theme-primary;
|
||||
}
|
||||
.wrap1_2_2{
|
||||
color: $theme-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.content2{
|
||||
margin-top: 136rpx;
|
||||
.wrap3{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.wrap2{
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
background-color: #F9F9F9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 96rpx;
|
||||
padding-right: 22rpx;
|
||||
.wrap2_4{
|
||||
margin-left: 20rpx;
|
||||
img{
|
||||
width: 36rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
}
|
||||
.wrap2_3{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.wrap2_3_1{
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
img{
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
}
|
||||
.wrap2_3_2{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 20rpx;
|
||||
margin-right: auto;
|
||||
.wrap2_3_2_2{
|
||||
width: 36rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
.wrap2_3_2_1{
|
||||
width: 36rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrap2_2{
|
||||
padding-left: 38rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
input{
|
||||
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
.wrap2_1{
|
||||
border-right: 1rpx solid #D1D1D1;
|
||||
color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 174rpx;
|
||||
}
|
||||
}
|
||||
.wrap1{
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
padding-top: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
background-color: #F9F9F9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 96rpx;
|
||||
.wrap1_3{
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
image{
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap1_2{
|
||||
padding-left: 38rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
input{
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
.wrap1_1{
|
||||
border-right: 1rpx solid #D1D1D1;
|
||||
color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 174rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content1{
|
||||
margin-top: 300rpx;
|
||||
.wrap1{
|
||||
line-height: 90rpx;
|
||||
font-weight: bold;
|
||||
font-size: 64rpx;
|
||||
color: #191818;
|
||||
.wrap1_1{
|
||||
color: $theme-primary;
|
||||
}
|
||||
}
|
||||
.wrap2{
|
||||
font-size: 28rpx;
|
||||
color: #191818;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
91
src/service/index.js
Normal file
@ -0,0 +1,91 @@
|
||||
import Request from '@/service/request/index.js'
|
||||
import {useAuth} from "@/store/auth";
|
||||
const { token ,refreshToken,userInfo}=useAuth()
|
||||
let isRefreshing = false;
|
||||
let refreshSubscribers = [];
|
||||
const request = new Request({
|
||||
baseURL: import.meta.env.VITE_BASEURL,
|
||||
timeout: 1000 * 60 * 5,
|
||||
interceptors: {
|
||||
//实例的请求拦截器
|
||||
requestInterceptors: (config) => {
|
||||
config.headers['Content-Type'] = config.method === 'get' ?
|
||||
'application/x-www-form-urlencoded' :
|
||||
'application/json';
|
||||
config.headers['Authorization'] = token.value
|
||||
if (config.isFormData) {
|
||||
config.headers['Content-Type'] = 'multipart/form-data';
|
||||
}
|
||||
return config;
|
||||
},
|
||||
//实例的响应拦截器
|
||||
responseInterceptors: async (res) => {
|
||||
if(res.data.status===1){
|
||||
message.warning(res.data.msg)
|
||||
}
|
||||
if (res.data.status === 401) {
|
||||
return getRefreshToken(res);
|
||||
// uni.navigateTo({
|
||||
// url:'/pages/login/index'
|
||||
// })
|
||||
}
|
||||
if ([200, 201, 204].includes(res.status)) {
|
||||
return res.config.responseType === 'blob' ? res : res;
|
||||
} else {
|
||||
/* message.error(res.data.msg || 'An error occurred.');*/
|
||||
return Promise.reject(new Error(res.data.msg || 'An error occurred.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
async function getRefreshToken(response) {
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
const refreshTokenT = refreshToken.value;
|
||||
if (refreshTokenT) {
|
||||
try {
|
||||
const data = { refreshToken:refreshTokenT };
|
||||
const res = await request.instance.post('/user/refresh/token', data);
|
||||
if (res.code === 200) {
|
||||
token.value = res.data.Token;
|
||||
refreshToken.value = res.data.RefreshToken;
|
||||
userInfo.value = res.data.AccountInfo
|
||||
response.config.headers['Authorization'] = res.data.Token;
|
||||
uni.navigateTo({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
return request.request(response.config);
|
||||
} else {
|
||||
message.error(res.message || res.msg);
|
||||
throw new Error(res.message || res.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.navigateTo({
|
||||
url:'/pages/login/index'
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
refreshSubscribers.forEach(callback => callback());
|
||||
refreshSubscribers = [];
|
||||
}
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url:'/pages/login/index'
|
||||
})
|
||||
throw new Error('No refresh token available.');
|
||||
}
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
refreshSubscribers.push(() => resolve(request.request(response.config)));
|
||||
});
|
||||
}
|
||||
}
|
||||
const fontRequest = (config) => {
|
||||
if (['get', 'GET'].includes(config.method)) {
|
||||
config.params = config.data;
|
||||
}
|
||||
return request.request(config);
|
||||
};
|
||||
|
||||
export default fontRequest;
|
80
src/service/request/index.js
Normal file
@ -0,0 +1,80 @@
|
||||
import axios from 'axios';
|
||||
import { createUniAppAxiosAdapter } from '@uni-helper/axios-adapter'
|
||||
axios.defaults.adapter = createUniAppAxiosAdapter()
|
||||
|
||||
class Request {
|
||||
// axios 实例
|
||||
instance;
|
||||
// 拦截器对象
|
||||
interceptorsObj;
|
||||
// * 存放取消请求控制器Map
|
||||
abortControllerMap;
|
||||
constructor(config) {
|
||||
this.instance = axios.create(config);
|
||||
// * 初始化存放取消请求控制器Map
|
||||
this.abortControllerMap = new Map();
|
||||
this.interceptorsObj = config.interceptors;
|
||||
// 拦截器执行顺序 接口请求 -> 实例请求 -> 全局请求 -> 实例响应 -> 全局响应 -> 接口响应
|
||||
this.instance.interceptors.request.use((res) => {
|
||||
const controller = new AbortController();
|
||||
const url = res.url || '';
|
||||
res.signal = controller.signal;
|
||||
this.abortControllerMap.set(url, controller);
|
||||
return res;
|
||||
}, (err) => err);
|
||||
// 使用实例拦截器
|
||||
this.instance.interceptors.request.use(this.interceptorsObj?.requestInterceptors, this.interceptorsObj?.requestInterceptorsCatch);
|
||||
this.instance.interceptors.response.use(this.interceptorsObj?.responseInterceptors, this.interceptorsObj?.responseInterceptorsCatch);
|
||||
// 全局响应拦截器保证最后执行
|
||||
this.instance.interceptors.response.use(
|
||||
// 因为我们接口的数据都在res.data下,所以我们直接返回res.data
|
||||
(res) => {
|
||||
const url = res.config.url || '';
|
||||
this.abortControllerMap.delete(url);
|
||||
return res.data;
|
||||
}, (err) => err);
|
||||
}
|
||||
request(config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 如果我们为单个请求设置拦截器,这里使用单个请求的拦截器
|
||||
if (config.interceptors?.requestInterceptors) {
|
||||
config = config.interceptors.requestInterceptors(config);
|
||||
}
|
||||
this.instance
|
||||
.request(config)
|
||||
.then((res) => {
|
||||
// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
|
||||
if (config.interceptors?.responseInterceptors) {
|
||||
res = config.interceptors.responseInterceptors(res);
|
||||
}
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
// .finally(() => {})
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 取消全部请求
|
||||
*/
|
||||
cancelAllRequest() {
|
||||
for (const [, controller] of this.abortControllerMap) {
|
||||
controller.abort();
|
||||
}
|
||||
this.abortControllerMap.clear();
|
||||
}
|
||||
/**
|
||||
* 取消指定的请求
|
||||
* @param url 待取消的请求URL
|
||||
*/
|
||||
cancelRequest(url) {
|
||||
const urlList = Array.isArray(url) ? url : [url];
|
||||
for (const _url of urlList) {
|
||||
this.abortControllerMap.get(_url)?.abort();
|
||||
this.abortControllerMap.delete(_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
export default Request;
|
||||
|
1
src/static/css/color.scss
Normal file
@ -0,0 +1 @@
|
||||
$theme-primary: #46299D;
|
96
src/static/css/index.scss
Normal file
@ -0,0 +1,96 @@
|
||||
#app{
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.loader {
|
||||
position: relative;
|
||||
width: 154px;
|
||||
height: 154px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.loader div {
|
||||
width: 8%;
|
||||
height: 24%;
|
||||
background: $theme-primary;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30%;
|
||||
opacity: 0;
|
||||
border-radius: 50px;
|
||||
|
||||
animation: fade458 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes fade458 {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
.loader .bar1 {
|
||||
transform: rotate(0deg) translate(0, -130%);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.loader .bar2 {
|
||||
transform: rotate(30deg) translate(0, -130%);
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
|
||||
.loader .bar3 {
|
||||
transform: rotate(60deg) translate(0, -130%);
|
||||
animation-delay: -1s;
|
||||
}
|
||||
|
||||
.loader .bar4 {
|
||||
transform: rotate(90deg) translate(0, -130%);
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
|
||||
.loader .bar5 {
|
||||
transform: rotate(120deg) translate(0, -130%);
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
|
||||
.loader .bar6 {
|
||||
transform: rotate(150deg) translate(0, -130%);
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
|
||||
.loader .bar7 {
|
||||
transform: rotate(180deg) translate(0, -130%);
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
|
||||
.loader .bar8 {
|
||||
transform: rotate(210deg) translate(0, -130%);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
.loader .bar9 {
|
||||
transform: rotate(240deg) translate(0, -130%);
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
|
||||
.loader .bar10 {
|
||||
transform: rotate(270deg) translate(0, -130%);
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
.loader .bar11 {
|
||||
transform: rotate(300deg) translate(0, -130%);
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
|
||||
.loader .bar12 {
|
||||
transform: rotate(330deg) translate(0, -130%);
|
||||
animation-delay: -0.1s;
|
||||
}
|
29
src/static/error.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
<div>导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用导入使用</div>
|
||||
</body>
|
||||
</html>
|
BIN
src/static/image/apply/sp.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/static/image/apply/zu3808@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/static/image/apply/zu3809@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/static/image/apply/zu3812@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/static/image/apply/zu3843@3x.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/static/image/clockIn/32px@3x.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
src/static/image/clockIn/32px@3x1.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/static/image/clockIn/chevron-left@2x.png
Normal file
After Width: | Height: | Size: 394 B |
BIN
src/static/image/clockIn/location@3x.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/image/clockIn/rest3275@2x.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/image/clockIn/sq@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/static/image/clockIn/statistics@2x.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
src/static/image/clockIn/z3280@3x.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
src/static/image/clockIn/zu3275@3x.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/static/image/login/browse-off@3x.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
src/static/image/login/browse@3x.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/static/image/login/check-circle-filled@3x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/static/image/login/gdz@3x.png
Normal file
After Width: | Height: | Size: 495 KiB |
BIN
src/static/image/login/gth@3x.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/static/image/login/warn@3x.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/static/image/login/xzsu@3x.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/static/image/mine/1111.png
Normal file
After Width: | Height: | Size: 362 KiB |
BIN
src/static/image/mine/background.png
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
src/static/image/mine/card.png
Normal file
After Width: | Height: | Size: 994 B |
BIN
src/static/image/mine/chevron-down.png
Normal file
After Width: | Height: | Size: 727 B |
BIN
src/static/image/mine/chevron-down@3x.png
Normal file
After Width: | Height: | Size: 719 B |
BIN
src/static/image/mine/chevron-down@3x1.png
Normal file
After Width: | Height: | Size: 745 B |
BIN
src/static/image/mine/e@3x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/static/image/mine/help.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/static/image/mine/maillist@3x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/static/image/mine/ming001@3x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/static/image/mine/plus@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/static/image/mine/refresh@3x.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src/static/image/mine/scan@3x.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
src/static/image/mine/setting.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/static/image/tabbar/attendance2.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/static/image/tabbar/kaoqin.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/static/image/tabbar/message1.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/static/image/tabbar/message2.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/static/image/tabbar/my1.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/static/image/tabbar/my2.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/static/image/tabbar/wz@3x1.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/image/tabbar/zu3499@3x.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
src/static/logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
1
src/static/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="512" height="512" viewBox="0 0 512 512"><defs><clipPath id="master_svg0_25_97"><rect x="0" y="0" width="512" height="512" rx="0"/></clipPath><clipPath id="master_svg1_25_11"><rect x="11" y="39" width="490" height="435" rx="0"/></clipPath></defs><g style="mix-blend-mode:passthrough" clip-path="url(#master_svg0_25_97)"><g clip-path="url(#master_svg1_25_11)"><g><path d="M51.4931,294.222767578125L205.214,437.551767578125C211.594,443.498767578125,220.016,446.812767578125,228.778,446.812767578125C237.54,446.812767578125,245.962,443.498767578125,252.342,437.551767578125L254.554,435.512767578125C238.306,411.638767578125,228.778,382.751767578125,228.778,351.655767578125C228.778,269.073767578125,295.812,202.124767578125,378.5,202.124767578125C402.575,202.124767578125,425.288,207.817767578125,445.45,217.842767578125C446.215,212.320767578125,446.556,206.797767578125,446.556,201.19076757812502L446.556,196.262767578125C446.556,136.874967578125,403.595,86.238267578125,344.983,76.467747578125C306.191,70.010717578125,266.719,82.669897578125,238.986,110.36716757812499L228.778,120.562467578125L218.569,110.36716757812499C190.837,82.669897578125,151.365,70.010717578125,112.573,76.467747578125C53.9601,86.238267578125,11,136.874967578125,11,196.262767578125L11,201.19076757812502C11,236.448767578125,25.6319,270.17876757812496,51.4931,294.222767578125ZM378.5,473.999767578125C446.13,473.999767578125,501,419.199767578125,501,351.655767578125C501,284.112767578125,446.13,229.312767578125,378.5,229.312767578125C310.87,229.312767578125,256,284.112767578125,256,351.655767578125C256,419.199767578125,310.87,473.999767578125,378.5,473.999767578125Z" fill="#2B9939" fill-opacity="1"/></g><g style="mix-blend-mode:passthrough"><path d="M322,415L441,415L441,293.5L419,293.5L419,393L344.5,393L344.5,293.5L322,293.5L322,415Z" fill="#FFFFFF" fill-opacity="1"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
20
src/store/auth/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
import {createGlobalState,useStorage} from '@vueuse/core'
|
||||
import {uniStorage} from "@/utils/uniStorage.js"
|
||||
|
||||
import {ref} from 'vue'
|
||||
export const useAuth = createGlobalState(() => {
|
||||
const token = useStorage('token', '', uniStorage)
|
||||
const refreshToken = useStorage('refreshToken', '', uniStorage)
|
||||
const userInfo = useStorage('userInfo', {}, uniStorage)
|
||||
const leaderList = useStorage('leaderList', [], uniStorage)
|
||||
const isLeader=ref(false)
|
||||
// const leaderList=ref([])
|
||||
|
||||
return {
|
||||
leaderList,
|
||||
|
||||
userInfo,
|
||||
token,
|
||||
refreshToken,
|
||||
}
|
||||
})
|
15
src/store/status/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import {ref} from 'vue'
|
||||
import {createGlobalState, useStorage} from "@vueuse/core";
|
||||
import {uniStorage} from "@/utils/uniStorage";
|
||||
export const useStatus =createGlobalState(()=>{
|
||||
const currentNavbar=ref({title:'',url:''})
|
||||
const applyTabbarIndex=ref(0)
|
||||
const statusBarHeight = ref(window?.plus?.navigator?.getStatusbarHeight() ?? 0)
|
||||
const tabBarIndex = useStorage('tabBarIndex', 0, uniStorage)
|
||||
return {
|
||||
statusBarHeight,
|
||||
applyTabbarIndex,
|
||||
currentNavbar,
|
||||
tabBarIndex
|
||||
}
|
||||
})
|
12
src/uni_modules/tmui/.npmignore
Normal file
@ -0,0 +1,12 @@
|
||||
full.scss
|
||||
full.min.css
|
||||
full.css
|
||||
mian.css
|
||||
mian.scss
|
||||
scss/fonts
|
||||
scss/colors.scss
|
||||
scss/fuzhu.scss
|
||||
scss/fuzhu-full.scss
|
||||
scss/theme.scss
|
||||
package.json
|
||||
readme.md
|