feat: 初始化仓库
15
.eslintignore
Normal file
@@ -0,0 +1,15 @@
|
||||
*.sh
|
||||
node_modules
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
/public
|
||||
/docs
|
||||
.husky
|
||||
.local
|
||||
/bin
|
||||
Dockerfile
|
||||
.hbuilderx
|
||||
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.eslintcache
|
||||
report.html
|
||||
vite.config.*.timestamp*
|
||||
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
npm-debug.log*
|
||||
.pnpm-error.log*
|
||||
.pnpm-debug.log
|
||||
tests/**/coverage/
|
||||
|
||||
.hbuilderx
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
tsconfig.tsbuildinfo
|
||||
25
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"antfu",
|
||||
"dcloudio",
|
||||
"demi",
|
||||
"iconify",
|
||||
"miniprogram",
|
||||
"Pinia",
|
||||
"postprocess",
|
||||
"unocss",
|
||||
"unplugin",
|
||||
"vite",
|
||||
"weixin"
|
||||
],
|
||||
"i18n-ally.localesPaths": [],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/node_modules": true
|
||||
}
|
||||
}
|
||||
21
index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="/src/static/favicon.ico" />
|
||||
<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>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
91
package.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "color-timetable",
|
||||
"version": "2.1.0",
|
||||
"scripts": {
|
||||
"dev:app": "uni -p app",
|
||||
"dev:custom": "uni -p",
|
||||
"dev:h5": "uni",
|
||||
"dev:h5:ssr": "uni --ssr",
|
||||
"dev:mp-alipay": "uni -p mp-alipay",
|
||||
"dev:mp-baidu": "uni -p mp-baidu",
|
||||
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||
"dev:mp-lark": "uni -p mp-lark",
|
||||
"dev:mp-qq": "uni -p mp-qq",
|
||||
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||
"build:app": "uni build -p app",
|
||||
"build:custom": "uni build -p",
|
||||
"build:h5": "uni build",
|
||||
"build:h5:ssr": "uni build --ssr",
|
||||
"build:mp-alipay": "uni build -p mp-alipay",
|
||||
"build:mp-baidu": "uni build -p mp-baidu",
|
||||
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||
"build:mp-lark": "uni build -p mp-lark",
|
||||
"build:mp-qq": "uni build -p mp-qq",
|
||||
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-app-harmony": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-components": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-h5": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-ui": "^1.5.6",
|
||||
"@fullcalendar/core": "^6.1.15",
|
||||
"@fullcalendar/daygrid": "^6.1.15",
|
||||
"@fullcalendar/interaction": "^6.1.15",
|
||||
"@fullcalendar/list": "^6.1.15",
|
||||
"@fullcalendar/timegrid": "^6.1.15",
|
||||
"@fullcalendar/vue3": "^6.1.15",
|
||||
"dayjs": "^1.11.13",
|
||||
"less": "^4.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.2.2",
|
||||
"pinia-plugin-persistedstate": "^3.2.3",
|
||||
"preact": "^10.23.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"vue": "^3.4.38",
|
||||
"vue-demi": "^0.13.11",
|
||||
"vue-i18n": "^9.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.27.0",
|
||||
"@dcloudio/types": "^3.4.12",
|
||||
"@dcloudio/uni-automator": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4020420240722002",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4020420240722002",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4020420240722002",
|
||||
"@iconify-json/carbon": "^1.1.37",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^18.19.47",
|
||||
"@vue/runtime-core": "^3.4.38",
|
||||
"eslint": "8.57.0",
|
||||
"postcss": "8.4.41",
|
||||
"sass": "^1.77.8",
|
||||
"sass-loader": "10.1.1",
|
||||
"terser": "^5.31.6",
|
||||
"typescript": "4.8.3",
|
||||
"unocss": "^0.60.2",
|
||||
"unocss-applet": "^0.6.0",
|
||||
"unocss-preset-extra": "^0.5.3",
|
||||
"unplugin-auto-import": "^0.11.5",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
5
renovate.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
198
src/App.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
const { setPageConfig } = usePageStore()
|
||||
|
||||
const { darkMode, statusBarHeight, menuButtonBounding } = storeToRefs(
|
||||
useAppStore(),
|
||||
)
|
||||
|
||||
onLaunch(() => {
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
// the systemInfo.theme is only support dark mode in WeChat and QQ
|
||||
darkMode.value = systemInfo?.theme === 'dark'
|
||||
statusBarHeight.value = systemInfo!.statusBarHeight || 44
|
||||
menuButtonBounding.value = uni.getMenuButtonBoundingClientRect()
|
||||
|
||||
uni.onThemeChange(
|
||||
(res: UniApp.OnThemeChangeCallbackResult) =>
|
||||
(darkMode.value = res.theme === 'dark'),
|
||||
)
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
darkMode.value = colorScheme.matches
|
||||
colorScheme.addEventListener(
|
||||
'change',
|
||||
(e: MediaQueryListEvent) => (darkMode.value = e.matches),
|
||||
)
|
||||
// The data is obtained from iPhone13 miniprogram but statusBarHeight, top and bottom values are subtracted from the statusBarHeight value
|
||||
statusBarHeight.value = 0
|
||||
menuButtonBounding.value = {
|
||||
width: 87,
|
||||
height: 32,
|
||||
left: 281,
|
||||
top: 4,
|
||||
right: 368,
|
||||
bottom: 36,
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP
|
||||
const appSystemInfo = uni.getWindowInfo()
|
||||
uni.onThemeChange(
|
||||
(res: UniApp.OnThemeChangeCallbackResult) =>
|
||||
(darkMode.value = res.theme === 'dark'),
|
||||
)
|
||||
statusBarHeight.value = appSystemInfo.statusBarHeight
|
||||
menuButtonBounding.value = {
|
||||
width: appSystemInfo.screenWidth,
|
||||
height: appSystemInfo.statusBarHeight,
|
||||
left: appSystemInfo.safeArea.left,
|
||||
top: appSystemInfo.statusBarHeight * 2,
|
||||
right: appSystemInfo.safeArea.right,
|
||||
bottom: appSystemInfo.statusBarHeight,
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
onShow(() => {
|
||||
setPageConfig({ showNavBar: false })
|
||||
})
|
||||
onHide(() => {})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loader {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.loader {
|
||||
top: 72vh;
|
||||
left: 50vw;
|
||||
transform: rotate(165deg);
|
||||
}
|
||||
|
||||
.loader:after,
|
||||
.loader:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: block;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
border-radius: 0.25em;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
animation: before 2s infinite;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
animation: after 2s infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes before {
|
||||
0% {
|
||||
width: 0.5em;
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
width: 2.5em;
|
||||
box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75),
|
||||
0 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 0.5em;
|
||||
box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes before {
|
||||
0% {
|
||||
width: 0.5em;
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
width: 2.5em;
|
||||
box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75),
|
||||
0 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 0.5em;
|
||||
box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes after {
|
||||
0% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
height: 2.5em;
|
||||
box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75),
|
||||
-0.5em 0 rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em 1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes after {
|
||||
0% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
height: 2.5em;
|
||||
box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75),
|
||||
-0.5em 0 rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em 1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
188
src/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
const mapStores: typeof import('pinia')['mapStores']
|
||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
|
||||
const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onError: typeof import('@dcloudio/uni-app')['onError']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onHide: typeof import('@dcloudio/uni-app')['onHide']
|
||||
const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
|
||||
const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
|
||||
const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
|
||||
const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
|
||||
const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
|
||||
const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
|
||||
const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
|
||||
const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
|
||||
const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
|
||||
const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
|
||||
const onReady: typeof import('@dcloudio/uni-app')['onReady']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onResize: typeof import('@dcloudio/uni-app')['onResize']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
|
||||
const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
|
||||
const onShow: typeof import('@dcloudio/uni-app')['onShow']
|
||||
const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
|
||||
const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
|
||||
const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
|
||||
const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveDirective: typeof import('vue')['resolveDirective']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const stores: typeof import('./stores/index')['default']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for vue template auto import
|
||||
import { UnwrapRef } from 'vue'
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
|
||||
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
|
||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
|
||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
|
||||
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
|
||||
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
|
||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
|
||||
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
|
||||
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
|
||||
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
|
||||
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
|
||||
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
|
||||
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
|
||||
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
|
||||
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
|
||||
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
|
||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
|
||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
|
||||
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
|
||||
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
|
||||
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
|
||||
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
|
||||
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
|
||||
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||
readonly resolveDirective: UnwrapRef<typeof import('vue')['resolveDirective']>
|
||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||
readonly stores: UnwrapRef<typeof import('./stores/index')['default']>
|
||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||
}
|
||||
}
|
||||
83
src/components/UnoUI/UBasePage/UBasePage.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { UNotifyOptions } from '../UNotify/types'
|
||||
import type { UToastOptions } from '../UToast/types'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
const { darkMode, customBarHeight, statusBarHeight } = storeToRefs(
|
||||
useAppStore(),
|
||||
)
|
||||
const { pageReset } = usePageStore()
|
||||
const {
|
||||
showNavBar,
|
||||
showBackAction,
|
||||
showCustomAction,
|
||||
pageTitle,
|
||||
notifyRef: _notifyRef,
|
||||
toastRef: _toastRef,
|
||||
} = storeToRefs(usePageStore())
|
||||
|
||||
const handleNavigateBack = () => uni.navigateBack({})
|
||||
|
||||
const notifyRef = ref<{ handleShowNotify: (options: UNotifyOptions) => {} }>()
|
||||
const toastRef = ref<{ handleShowToast: (options: UToastOptions) => {} }>()
|
||||
|
||||
onMounted(() => {
|
||||
_notifyRef.value = notifyRef.value
|
||||
_toastRef.value = toastRef.value
|
||||
})
|
||||
|
||||
onUnmounted(() => pageReset())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="darkMode ? 'dark' : ''">
|
||||
<view class="bg-base color-base text-base relative">
|
||||
<!-- custom navigation bar -->
|
||||
<view
|
||||
v-if="showNavBar"
|
||||
class="bg-primary text-white w-full top-0 z-200 fixed font-bold"
|
||||
:style="{ height: `${customBarHeight}px` }"
|
||||
>
|
||||
<view
|
||||
:style="{
|
||||
'padding-top': `${statusBarHeight}px`,
|
||||
'height': `${customBarHeight - statusBarHeight}px`,
|
||||
}"
|
||||
>
|
||||
<view class="h-full text-center px-6 relative">
|
||||
<view
|
||||
v-if="showBackAction || showCustomAction"
|
||||
class="flex h-full text-xl left-4 absolute justify-center items-center"
|
||||
>
|
||||
<slot name="navAction">
|
||||
<view
|
||||
v-if="showBackAction && !showCustomAction"
|
||||
class="i-carbon-chevron-left"
|
||||
@click="handleNavigateBack"
|
||||
/>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="flex h-full text-lg justify-center items-center">
|
||||
<slot name="navContent">
|
||||
{{ pageTitle }}
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<UNotify ref="notifyRef" />
|
||||
<UToast ref="toastRef" />
|
||||
<!-- page container -->
|
||||
<view
|
||||
class="overflow-auto"
|
||||
:style="{
|
||||
'height': `calc(100vh - ${customBarHeight}px)`,
|
||||
'padding-top': `${customBarHeight}px`,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
44
src/components/UnoUI/UButton/UButton.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, defineProps, withDefaults } from 'vue'
|
||||
interface Props {
|
||||
type?: 'default' | 'success' | 'error' | 'warning' | 'primary'
|
||||
bg?: string
|
||||
icon?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
type: () => 'default',
|
||||
bg: () => '',
|
||||
icon: () => '',
|
||||
disabled: () => false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const handleTap = () => emit('click')
|
||||
|
||||
const bgColor = {
|
||||
default: 'bg-gray-5',
|
||||
success: 'bg-green-5',
|
||||
error: 'bg-red-5',
|
||||
warning: 'bg-orange-5',
|
||||
primary: 'bg-blue-5',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="rounded-lg text-white text-base flex justify-center items-center shadow py-2"
|
||||
:class="[bg ? bg : bgColor[type], icon ? 'gap-1' : '']" hover-class="grayscale-20" :hover-stay-time="150"
|
||||
@tap="handleTap"
|
||||
>
|
||||
<slot name="icon">
|
||||
<view v-if="icon" :class="icon" />
|
||||
</slot>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
49
src/components/UnoUI/UNotify/UNotify.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { UNotifyOptions, UNotifyType } from './types'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
const { customBarHeight } = storeToRefs(useAppStore())
|
||||
|
||||
const timer = ref<number | undefined>(undefined)
|
||||
const show = ref(false)
|
||||
const notifyType = ref<UNotifyType>('default')
|
||||
const message = ref('')
|
||||
|
||||
const handleShowNotify = (options: UNotifyOptions) => {
|
||||
const { type = 'default', message: _message = 'Unable to connect to the server.', duration = 2000 } = options
|
||||
clearTimeout(timer.value)
|
||||
show.value = true
|
||||
notifyType.value = type
|
||||
message.value = _message
|
||||
timer.value = setTimeout(() => {
|
||||
show.value = false
|
||||
clearTimeout(timer.value)
|
||||
timer.value = undefined
|
||||
}, duration) as unknown as number
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleShowNotify,
|
||||
})
|
||||
|
||||
const bgColor = {
|
||||
default: 'bg-gray-5',
|
||||
success: 'bg-green-4',
|
||||
error: 'bg-red-5',
|
||||
warning: 'bg-orange-5',
|
||||
primary: 'bg-blue-5',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="flex h-8 text-white w-full py-1 px-2 transition-all z-100 justify-center items-center fixed"
|
||||
:class="bgColor[notifyType]"
|
||||
:style="{ top: show ? `${customBarHeight}px` : '-100%' }"
|
||||
>
|
||||
{{ message }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
7
src/components/UnoUI/UNotify/types.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export type UNotifyType = 'default' | 'success' | 'error' | 'warning' | 'primary'
|
||||
|
||||
export interface UNotifyOptions {
|
||||
type?: UNotifyType
|
||||
message: string
|
||||
duration?: number
|
||||
}
|
||||
50
src/components/UnoUI/UToast/UToast.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { UToastOptions, UToastType } from './types'
|
||||
const timer = ref<number | undefined>(undefined)
|
||||
const show = ref(false)
|
||||
const notifyType = ref<UToastType>('default')
|
||||
const message = ref('')
|
||||
|
||||
const handleShowToast = (options: UToastOptions) => {
|
||||
const { type = 'default', message: _message = 'Unable to connect to the server.', duration = 2000 } = options
|
||||
clearTimeout(timer.value)
|
||||
show.value = true
|
||||
notifyType.value = type
|
||||
message.value = _message
|
||||
timer.value = setTimeout(() => {
|
||||
show.value = false
|
||||
clearTimeout(timer.value)
|
||||
timer.value = undefined
|
||||
}, duration) as unknown as number
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleShowToast,
|
||||
})
|
||||
|
||||
const ToastClass = {
|
||||
default: 'bg-gray-5 border-gray-2',
|
||||
success: 'bg-green-5 border-green-3',
|
||||
error: 'bg-red-5 border-red-3',
|
||||
warning: 'bg-orange-5 border-orange-3',
|
||||
primary: 'bg-blue-5 border-blue-3',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="z-100 fixed flex justify-center items-center top-0 bottom-0 left-0 right-0"
|
||||
>
|
||||
<view
|
||||
class="flex justify-center items-center py-2 px-4 rounded-lg border animate-fade-in-up animate-duration-200 text-white"
|
||||
:class="ToastClass[notifyType]"
|
||||
>
|
||||
{{ message }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
7
src/components/UnoUI/UToast/types.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export type UToastType = 'default' | 'success' | 'error' | 'warning' | 'primary'
|
||||
|
||||
export interface UToastOptions {
|
||||
type?: UToastType
|
||||
message: string
|
||||
duration?: number
|
||||
}
|
||||
432
src/components/cus-selects-fan.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
valueType: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
}
|
||||
},
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
searchType: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
},
|
||||
noDataText: {
|
||||
type: String,
|
||||
default: '暂无数据',
|
||||
},
|
||||
arrLeft: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 240,
|
||||
},
|
||||
closeSelect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
readonly: true,
|
||||
isClick: false,
|
||||
totalArr: [],
|
||||
showData: [],
|
||||
selLabel: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterable: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(news) {
|
||||
this.readonly = !news
|
||||
},
|
||||
},
|
||||
data: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(news) {
|
||||
this.showData = news
|
||||
this.totalArr = news
|
||||
},
|
||||
},
|
||||
value: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(news) {
|
||||
if (news) {
|
||||
const index = this.data.findIndex(
|
||||
ite => ite[this.valueType.value] == news,
|
||||
)
|
||||
if (index == -1) {
|
||||
uni.showToast({
|
||||
title: '传入的value不存在',
|
||||
icon: 'none',
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.selLabel = this.data[index][this.valueType.label]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
closeSelect: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler(news) {
|
||||
this.show = news
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {},
|
||||
beforeUnmount() {},
|
||||
methods: {
|
||||
openSelect() {
|
||||
this.show = !this.show
|
||||
this.$emit('update:closeSelect', this.show)
|
||||
this.isClick = !this.isClick
|
||||
},
|
||||
change(item) {
|
||||
if (this.value != item[this.valueType]) {
|
||||
this.$emit('input', item[this.valueType.value])
|
||||
this.$emit('change', item[this.valueType.value])
|
||||
}
|
||||
this.selLabel = item[this.valueType.label]
|
||||
this.show = false
|
||||
this.$emit('update:closeSelect', this.show)
|
||||
this.isClick = false
|
||||
this.showData = this.data
|
||||
},
|
||||
clearItem() {
|
||||
if (this.clearable) {
|
||||
this.$emit('input', '')
|
||||
this.$emit('change', '')
|
||||
}
|
||||
this.selLabel = ''
|
||||
},
|
||||
selectData(e) {
|
||||
const sel = e.detail.value
|
||||
if (sel) {
|
||||
const arrCons = []
|
||||
const selVal = []
|
||||
this.data.forEach((item) => {
|
||||
arrCons.push(item)
|
||||
})
|
||||
arrCons.forEach((item) => {
|
||||
if (this.searchType == 1) {
|
||||
if (item[this.valueType.label].includes(sel))
|
||||
selVal.push(item)
|
||||
}
|
||||
else {
|
||||
if (item[this.valueType.label] == sel)
|
||||
selVal.push(item)
|
||||
}
|
||||
})
|
||||
this.show = true
|
||||
this.$emit('update:closeSelect', this.show)
|
||||
|
||||
this.showData = selVal
|
||||
}
|
||||
else {
|
||||
this.showData = this.data
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="select_wrap" style="width: 80%">
|
||||
<view
|
||||
ref="select-input"
|
||||
class="select_input"
|
||||
:class="[isClick ? 'select_input_select' : '']"
|
||||
>
|
||||
<view v-if="!readonly" class="input_info" @click.stop="openSelect">
|
||||
<input
|
||||
placeholder-style="font-size: 14px;color: #a0a9b0;"
|
||||
:focus="isClick"
|
||||
:value="selLabel"
|
||||
type="text"
|
||||
:readonly="readonly"
|
||||
:disabled="readonly"
|
||||
autocomplete="off"
|
||||
:placeholder="placeholder"
|
||||
class="text_tips"
|
||||
@input="selectData"
|
||||
>
|
||||
</view>
|
||||
<view v-else class="input_info" @click.stop="openSelect">
|
||||
<view :placeholder="placeholder" class="text_tips">
|
||||
{{ selLabel }}
|
||||
<text v-if="!selLabel">
|
||||
{{ placeholder }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="icon_arrow" @click="clearItem">
|
||||
<view
|
||||
v-if="
|
||||
(!value && !clearable)
|
||||
|| (value && !clearable)
|
||||
|| (!value && clearable && !filterable)
|
||||
"
|
||||
class="arrow"
|
||||
:class="[show ? 'arrow_down' : 'arrow_up']"
|
||||
/>
|
||||
<view v-if="value && clearable" class="arrow-clear">
|
||||
x
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="show"
|
||||
class="select_modal_con"
|
||||
@touchmove.stop.prevent="() => {}"
|
||||
>
|
||||
<scroll-view scroll-y="true" class="select_modal select_modal_scroll">
|
||||
<view ref="select_content" class="select_content">
|
||||
<view
|
||||
v-for="(item, index) in showData"
|
||||
:key="index"
|
||||
class="select_content_li"
|
||||
:class="{ selected: value == item[valueType.value] }"
|
||||
@click="change(item)"
|
||||
>
|
||||
{{ item[valueType.label] }}
|
||||
</view>
|
||||
<view v-if="!showData.length" class="select_content_li">
|
||||
{{ noDataText }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- #ifndef H5 -->
|
||||
<!-- <scroll-view scroll-y="true" class="select_modal select_modal_scroll">
|
||||
<view class="select_content" ref="select_content">
|
||||
<view v-for="(item, index) in showData" :key="index" class="select_content_li" :class="{'selected': value == item[valueType.value]}" @click="change(item)">{{item[valueType.label]}}</view>
|
||||
<view class="select_content_li" v-if="!showData.length">{{noDataText}}</view>
|
||||
|
||||
</view>
|
||||
</scroll-view> -->
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- <view class="select_modal">
|
||||
<view class="select_content" ref="select_content">
|
||||
<view v-for="(item, index) in showData" :key="index" class="select_content_li" :class="{'selected': value == item[valueType.value]}" @click="change(item)">{{item[valueType.label]}}</view>
|
||||
<view class="select_content_li" v-if="!showData.length">{{noDataText}}</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- #endif -->
|
||||
<view class="cons_arrow" :style="{ left: `${arrLeft}px` }" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select_wrap {
|
||||
width: 240px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.select_input {
|
||||
-webkit-appearance: none;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
color: #606266;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
outline: none;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
width: 100%;
|
||||
padding-right: 30px;
|
||||
|
||||
.input_info {
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.text_tips {
|
||||
height: 100%;
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #a0a9b0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon_arrow {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 40px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
.arrow {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: transparent;
|
||||
/* 模块背景为透明 */
|
||||
border-color: #c0c4cc;
|
||||
border-style: solid;
|
||||
border-width: 1px 1px 0 0;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s;
|
||||
box-sizing: border-box;
|
||||
/*箭头方向可以自由切换角度*/
|
||||
}
|
||||
.arrow_down {
|
||||
transform: rotate(-45deg);
|
||||
margin-top: 5px;
|
||||
}
|
||||
.arrow_up {
|
||||
transform: rotate(135deg);
|
||||
margin-top: -5px;
|
||||
}
|
||||
.arrow-clear {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid #e4e7ed;
|
||||
color: #e4e7ed;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.select_input_select {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.select_modal_con {
|
||||
width: 100%;
|
||||
transform-origin: center top;
|
||||
z-index: 2062;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
box-sizing: border-box;
|
||||
margin-top: 12px;
|
||||
|
||||
.cons_arrow {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
top: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #ebeef5;
|
||||
}
|
||||
|
||||
.cons_arrow:after {
|
||||
content: ' ';
|
||||
border-width: 6px;
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
top: 1px;
|
||||
margin-left: -6px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.select_modal {
|
||||
overflow: scroll;
|
||||
height: 160px;
|
||||
.select_content {
|
||||
list-style: none;
|
||||
padding: 6px 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
.select_content_li {
|
||||
font-size: 14px;
|
||||
padding: 0 20px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #606266;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
&.selected {
|
||||
color: #409eff;
|
||||
font-weight: 700;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.select_content_li:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
.select_modal_scroll {
|
||||
overflow: hidden;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
// .select_content {
|
||||
// background-color: #fff;
|
||||
|
||||
// .select_content_li {
|
||||
// padding: 12rpx;
|
||||
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
111
src/components/timetable/CourseActionSheet.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { CourseModel } from '~/stores/modules/course'
|
||||
import { useCourseStore, weekTitle } from '~/stores/modules/course'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ showActionSheet: boolean; courseItem: CourseModel }>(),
|
||||
{
|
||||
showActionSheet: false,
|
||||
courseItem: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits(['cancel'])
|
||||
|
||||
const courseStore = useCourseStore()
|
||||
|
||||
const courseList = computed(() =>
|
||||
courseStore.getConflictCourse(props.courseItem),
|
||||
)
|
||||
function getCourseTime(item: CourseModel) {
|
||||
const { week, start, duration } = item
|
||||
return `${weekTitle[week - 1]} 第${start}-${start + duration - 1}节`
|
||||
}
|
||||
|
||||
function navigateToDetail(courseItem: CourseModel) {
|
||||
closeActionSheet()
|
||||
uni.navigateTo({
|
||||
url: `/pages/course/detail?id=${courseItem.id}`,
|
||||
})
|
||||
}
|
||||
|
||||
function closeActionSheet() {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view @touchmove.prevent>
|
||||
<view
|
||||
class="bg-base w-full min-h-10 z-100 fixed"
|
||||
transition="all duration-300 ease-in-out"
|
||||
:class="
|
||||
showActionSheet && courseList?.length ? 'bottom-0' : '-bottom-full'
|
||||
"
|
||||
>
|
||||
<view class="py-6" flex="~ col gap6">
|
||||
<!-- <view v-if="courseList?.length" class="font-medium text-xl px-4">
|
||||
{{ courseTime }}
|
||||
</view> -->
|
||||
<template v-for="(course, index) of courseList" :key="index">
|
||||
<view
|
||||
class="px-4"
|
||||
flex="~ col gap-2"
|
||||
@click="navigateToDetail(course)"
|
||||
>
|
||||
<view
|
||||
class="flex mb-1 w-full gap-2 justify-start items-center relative"
|
||||
>
|
||||
<view
|
||||
class="rounded-full h-5 w-1 inline-block"
|
||||
:style="`background-color:${course.color}`"
|
||||
/>
|
||||
<view class="font-medium text-lg">
|
||||
{{ course.title }}
|
||||
</view>
|
||||
<view
|
||||
class="text-xl top-0 right-4 bottom-0 z-20 absolute"
|
||||
:class="index ? 'i-carbon-up-to-top' : ''"
|
||||
@click.stop="courseStore.setCourseItemTop(course)"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex gap-1 justify-start items-center">
|
||||
<view class="i-carbon-book" />
|
||||
{{ course.teacher || '' }}
|
||||
</view>
|
||||
<view class="flex gap-1 justify-start items-center">
|
||||
<view class="i-carbon-location" />
|
||||
{{ course.location }}
|
||||
</view>
|
||||
<view class="flex gap-1 justify-start items-center">
|
||||
<view class="i-carbon-alarm" />
|
||||
{{ getCourseTime(course) }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view
|
||||
class="flex pb-safe h-12"
|
||||
text="center lg dark:!white"
|
||||
b="t-4 gray-200 dark:op-20"
|
||||
justify-center
|
||||
items-center
|
||||
hover-class="bg-gray-200 bg-opacity-50"
|
||||
:hover-stay-time="150"
|
||||
@click="closeActionSheet"
|
||||
>
|
||||
关闭
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="bg-dark-100 bg-opacity-50 transition-all top-0 right-0 bottom-0 left-0 z-90 fixed"
|
||||
:class="
|
||||
showActionSheet && courseList?.length
|
||||
? 'opacity-100 visible'
|
||||
: 'opacity-0 invisible'
|
||||
"
|
||||
@click="closeActionSheet"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
51
src/components/timetable/TimetableAction.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
|
||||
const props = defineProps<{ showCourseAction: boolean }>()
|
||||
|
||||
const { parsedCourseList, originalWeekIndex, currentWeekIndex } = storeToRefs(useCourseStore())
|
||||
|
||||
const { setCurrentWeekIndex } = useCourseStore()
|
||||
|
||||
const scrollTo = ref('week0')
|
||||
|
||||
watch(
|
||||
() => +props.showCourseAction + currentWeekIndex.value,
|
||||
() => {
|
||||
if (props.showCourseAction)
|
||||
scrollTo.value = `week${currentWeekIndex.value - 1}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<scroll-view
|
||||
class="transition-height duration-300 overflow-scroll whitespace-nowrap"
|
||||
:class="showCourseAction ? 'h-20' : 'h-0'" scroll-x scroll-with-animation :scroll-into-view="scrollTo"
|
||||
>
|
||||
<template v-for="(weeksTimetable, weeksIndex) of parsedCourseList" :key="weeksIndex">
|
||||
<view
|
||||
:id="`week${weeksIndex + 1}`" class="py-1 px-2 inline-block"
|
||||
@click="setCurrentWeekIndex(weeksIndex)"
|
||||
>
|
||||
<view
|
||||
class="rounded-lg py-1 px-2"
|
||||
:class="originalWeekIndex === weeksIndex ? 'bg-gray-400/50 dark:!bg-op60' : currentWeekIndex === weeksIndex ? 'bg-gray-300 bg-op80 dark:!bg-op20' : ''"
|
||||
>
|
||||
<view class="text-xs text-center mb-1">
|
||||
{{ `第${weeksIndex + 1}周` }}
|
||||
</view>
|
||||
<view class="h-10 w-10" grid="~ flow-col cols-5 rows-6">
|
||||
<template v-for="(weekWeekTimetable, weekWeekIndex) of weeksTimetable" :key="weekWeekIndex">
|
||||
<template v-if="weekWeekIndex < 8">
|
||||
<template v-for="(item, _idx) of weekWeekTimetable" :key="_idx">
|
||||
<view class="rounded-full mx-auto h-1.5 w-1.5" :class="item ? 'bg-light-blue-500' : 'bg-gray-200'" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</scroll-view>
|
||||
</template>
|
||||
146
src/components/timetable/TimetableContent.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import TimetableAction from './TimetableAction.vue'
|
||||
import TimetableHeader from './TimetableHeader.vue'
|
||||
import type { CourseModel } from '~/stores/modules/course'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
|
||||
withDefaults(defineProps<{ showCourseAction: boolean }>(), {
|
||||
showCourseAction: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['courseItemClick'])
|
||||
|
||||
const { customBarHeight, totalWeeks, timeSetting } = storeToRefs(useAppStore())
|
||||
const { weekCourseList, currentWeekIndex, originalWeekIndex } = storeToRefs(
|
||||
useCourseStore(),
|
||||
)
|
||||
const { hasConflictCourseByMap, setCurrentWeekIndex } = useCourseStore()
|
||||
|
||||
// delete a course when course at the same time
|
||||
const deleteWeekCourse = computed(() => {
|
||||
const weekCourse = Array.from(weekCourseList.value)
|
||||
if (weekCourse.length <= 1)
|
||||
return weekCourse
|
||||
for (let i = 1; i < weekCourse.length; i++) {
|
||||
const { start, week } = weekCourse[i]
|
||||
const { start: prevStart, week: prevWeek } = weekCourse[i - 1]
|
||||
if (start === prevStart && week === prevWeek) {
|
||||
weekCourse.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
return weekCourse
|
||||
})
|
||||
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
const towardsX = ref(0)
|
||||
const towardsY = ref(0)
|
||||
|
||||
function resetTouchStatus() {
|
||||
startX.value = 0
|
||||
startY.value = 0
|
||||
towardsX.value = 0
|
||||
towardsY.value = 0
|
||||
}
|
||||
|
||||
function handleTouchStart(e: TouchEvent) {
|
||||
startX.value = e.touches[0].clientX
|
||||
startY.value = e.touches[0].clientY
|
||||
}
|
||||
|
||||
function handleTouchMove(e: TouchEvent) {
|
||||
towardsX.value = e.touches[0].clientX - startX.value
|
||||
towardsY.value = e.touches[0].clientY - startY.value
|
||||
}
|
||||
|
||||
function handleTouchEnd() {
|
||||
let currentWeekIndexTemp = currentWeekIndex.value
|
||||
if (towardsX.value === 0 || Math.abs(towardsY.value) > 50)
|
||||
return
|
||||
if (towardsX.value > 50) {
|
||||
if (currentWeekIndexTemp === 0)
|
||||
return
|
||||
currentWeekIndexTemp--
|
||||
}
|
||||
else if (towardsX.value < -50) {
|
||||
if (currentWeekIndexTemp === totalWeeks.value - 1)
|
||||
return
|
||||
currentWeekIndexTemp++
|
||||
}
|
||||
setCurrentWeekIndex(currentWeekIndexTemp)
|
||||
resetTouchStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* get course position
|
||||
* @param item course item
|
||||
* @returns css style
|
||||
*/
|
||||
function getCoursePosition(item: CourseModel) {
|
||||
return {
|
||||
'grid-row': `${item.start} / ${item.start + item.duration}`,
|
||||
'grid-column': `${item.week + 1} / ${item.week + 1 + 1}`,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="overflow-y-scroll relative bg-base" :style="{ height: `calc(100vh - ${customBarHeight}px)` }">
|
||||
<view class="w-full top-0 z-10 fixed bg-base" :style="{ 'padding-top': `${customBarHeight}px` }">
|
||||
<TimetableAction :show-course-action="showCourseAction" />
|
||||
<TimetableHeader />
|
||||
</view>
|
||||
<view
|
||||
class="min-h-max pb-safe p-1 transition-all z-20 duration-300 bg-base bg-[linear-gradient(-225deg,_#FFFEFF_0%,_#D7FFFE_100%)]"
|
||||
grid="~ flow-col rows-12 cols-[0.7fr_repeat(7,1fr)] gap-1" :class="showCourseAction ? 'pt-31' : 'pt-11'"
|
||||
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd"
|
||||
>
|
||||
<template v-for="item in timeSetting" :key="item.index">
|
||||
<view class="text-sm min-h-18" flex="~ col" justify-evenly items-center>
|
||||
<view class="font-medium">
|
||||
{{ item.index }}
|
||||
</view>
|
||||
<view class="px-0.5 text-10px">
|
||||
{{ item.start }}<br>{{ item.end }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-for="(courseItem, _courseIndex) of deleteWeekCourse" :key="_courseIndex">
|
||||
<view
|
||||
class="rounded-lg p-0.5 relative dark:bg-op40" b="white 2 !op-50" :style="[
|
||||
getCoursePosition(courseItem),
|
||||
`background-color:${hasConflictCourseByMap(courseItem)[0].color}`,
|
||||
]" @click="emit('courseItemClick', courseItem)"
|
||||
>
|
||||
<view class="h-full w-full" text="center white xs" flex="~ col" justify-center items-center>
|
||||
<view class="font-medium break-all">
|
||||
{{ hasConflictCourseByMap(courseItem)[0].title || '' }}
|
||||
</view>
|
||||
<view class="break-all">
|
||||
@{{ hasConflictCourseByMap(courseItem)[0].teacher || '' }}
|
||||
</view>
|
||||
<view class="break-all">
|
||||
<view class="text-8px i-carbon-location-current" />
|
||||
{{ hasConflictCourseByMap(courseItem)[0].location || '' }}
|
||||
</view>
|
||||
<view
|
||||
v-if="hasConflictCourseByMap(courseItem).length > 1"
|
||||
class="rounded h-1 top-1 left-1 right-1 absolute bg-white/80"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view
|
||||
class="bg-primary fixed top-40% z-30 rounded-l-full transition-all duration-300" text="white sm"
|
||||
p="l-4 y-2 r-2" :class="
|
||||
originalWeekIndex !== currentWeekIndex ? 'right-0' : '-right-full'
|
||||
" @click="setCurrentWeekIndex(originalWeekIndex)"
|
||||
>
|
||||
返回本周
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
37
src/components/timetable/TimetableHeader.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { useCourseStore, weekTitle } from '~/stores/modules/course'
|
||||
|
||||
const {
|
||||
isStart, currentMonth, originalWeekIndex, currentWeekIndex, currentWeekDayArray,
|
||||
} = storeToRefs(useCourseStore())
|
||||
|
||||
const originalWeekWeekIndex = ref(new Date().getDay() === 0 ? 6 : new Date().getDay() - 1)
|
||||
const isCurrentWeek = (weekIndex: number) => {
|
||||
if (!isStart.value)
|
||||
return false
|
||||
if (!originalWeekIndex.value || !currentWeekIndex.value || !originalWeekWeekIndex.value)
|
||||
return false
|
||||
|
||||
return originalWeekIndex.value === currentWeekIndex.value && originalWeekWeekIndex.value === weekIndex
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-10 shadow-sm px-1 top-0" grid="~ cols-[0.7fr_repeat(7,1fr)] gap-1">
|
||||
<view class="flex font-medium text-sm items-center justify-center">
|
||||
{{ `${currentMonth}月` }}
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in currentWeekDayArray" :key="index"
|
||||
class="text-xs transition-all duration-300 !bg-op40"
|
||||
flex="~ col" justify-evenly items-center
|
||||
b="y-transparent x-none t-4 b-4"
|
||||
:class="isCurrentWeek(index) ? 'bg-light-blue-300 !b-b-light-blue-500' : ''"
|
||||
>
|
||||
<text class="font-medium">
|
||||
{{ weekTitle[index] }}
|
||||
</text>
|
||||
<text>{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
395
src/hybrid/html/calender.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>FullCalendar H5 Page</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" href="./images/favicon.ico" />
|
||||
<link rel="stylesheet" href="./css/index.css" />
|
||||
<script src="./js/vue.global.js"></script>
|
||||
<script src="./js/axios.js"></script>
|
||||
<script src="./js/element-plus-zh-cn.min.js"></script>
|
||||
<script src="./js/element-plus-index.full.min.js"></script>
|
||||
<script src="./js/index.js"></script>
|
||||
<style>
|
||||
.fc .fc-toolbar-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.fc-col-header-cell-cushion {
|
||||
font-size: 12px;
|
||||
}
|
||||
.fc .fc-toolbar.fc-header-toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.fc-timegrid-event .fc-event-main{
|
||||
font-size: 8px;
|
||||
}
|
||||
.el-card__body{
|
||||
padding: 5px;
|
||||
}
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.el-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.el-col {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.centered-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.centered-form {
|
||||
text-align: center;
|
||||
}
|
||||
.centered-form-item {
|
||||
text-align: center;
|
||||
}
|
||||
.form-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.card-item {
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.form-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.card-item {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<el-card style="height: 100vh" shadow="never">
|
||||
<el-card style="margin-top:20px;" shadow="never">
|
||||
|
||||
<el-form label-position="left" class="centered-form" label-width="80px">
|
||||
<el-row
|
||||
:gutter="20"
|
||||
justify="space-around"
|
||||
align="middle"
|
||||
class="form-row"
|
||||
>
|
||||
<el-text type="primary" size="large" style="text-align: center;">教室课表查询</el-text>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="card-item centered-content">
|
||||
<el-form-item label="校区" class="centered-form-item">
|
||||
<el-select
|
||||
v-model="campusId"
|
||||
placeholder="请选择需要查询的校区~"
|
||||
filterable
|
||||
placement="bottom"
|
||||
@change="getBuildList"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in campusList"
|
||||
:key="item"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="card-item centered-content">
|
||||
<el-form-item label="教学楼" class="centered-form-item">
|
||||
<el-select
|
||||
v-model="buildId"
|
||||
placeholder="请选择需要查询的教学楼~"
|
||||
filterable
|
||||
placement="bottom"
|
||||
@change="getClassroomList"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in buildList"
|
||||
:key="item"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="6">
|
||||
<div class="card-item centered-content">
|
||||
<el-form-item label="教室" class="centered-form-item">
|
||||
<el-select
|
||||
v-model="classroomId"
|
||||
placeholder="请选择需要查询的教室~"
|
||||
filterable
|
||||
placement="bottom"
|
||||
@change="getClassroomCourseList"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in classroomList"
|
||||
:key="item"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-header>
|
||||
<el-card shadow="never">
|
||||
<div id="calendar"></div>
|
||||
</el-card>
|
||||
</el-container>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
const app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
startDate: '',
|
||||
campusId: '',
|
||||
campusList: [],
|
||||
buildId: '',
|
||||
buildList: [],
|
||||
classroomId: '',
|
||||
classroomList: [],
|
||||
courseList: [],
|
||||
currentEvents: [],
|
||||
week: 0,
|
||||
calendarApi: null,
|
||||
// Your existing data properties here
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initCalendar() {
|
||||
const calendarEl = document.getElementById('calendar')
|
||||
this.calendarApi = new FullCalendar.Calendar(calendarEl, {
|
||||
// 自定义按钮
|
||||
customButtons: {
|
||||
preWeekCustom: {
|
||||
text: '上周',
|
||||
click: () => this.prevWeek(),
|
||||
},
|
||||
nextWeekCustom: {
|
||||
text: '下周',
|
||||
click: () => this.nextWeek(),
|
||||
},
|
||||
},
|
||||
/** 修改headerToolbar */
|
||||
headerToolbar: {
|
||||
left: 'preWeekCustom',
|
||||
center: 'title', // 显示日历标题
|
||||
right: 'nextWeekCustom',
|
||||
},
|
||||
dayHeaderFormat: {
|
||||
weekday: 'short', // 显示周几,如"周日"
|
||||
month: '2-digit', // 显示两位数的月份
|
||||
day: '2-digit', // 显示两位数的日期
|
||||
omitCommas: true, // 去除逗号
|
||||
},
|
||||
contentHeight: 500, // 动态计算高度
|
||||
/** 设置日历高度 */
|
||||
timeZone: 'Asia/Shanghai',
|
||||
/** 默认视图 (月:dayGridMonth,周:timeGridWeek,日:timeGridDay) */
|
||||
initialView: 'timeGridWeek', // 默认显示周视图
|
||||
firstDay: 1, // 一周的第一天,0表示星期天,1表示星期一
|
||||
slotMinTime: '08:00', // 最小时间段,08:00
|
||||
slotMaxTime: '22:10', // 最大时间段,22:10
|
||||
slotDuration: '00:15', // 时间间隔,5分钟
|
||||
slotLabelFormat: {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
omitZeroMinute: false, // 忽略零分钟
|
||||
hour12: false, // 24小时制
|
||||
meridiem: 'short',
|
||||
},
|
||||
eventTimeFormat: {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false, // 24小时制
|
||||
},
|
||||
eventColor: '#3BB2E3', // 全部日历日程背景色
|
||||
themeSystem: 'bootstrap', // 主题色(本地测试未能生效)
|
||||
events: [], // 事件列表
|
||||
editable: false, // 是否可以拖动修改事件
|
||||
allDaySlot: false, // 是否显示全天事件区域
|
||||
nowIndicator: true, // 是否显示当前时间线
|
||||
selectable: false, // 是否可以选择时间段
|
||||
selectMirror: false, // 是否在选择时间段时显示虚影
|
||||
handleWindowResize: true, // 是否在窗口大小变化时调整日历
|
||||
navLinks: false, // 是否可以通过点击日期导航
|
||||
fixedWeekCount: true, // 每月显示固定周数
|
||||
showNonCurrentDates: true, // 是否显示非当前月的日期
|
||||
dayMaxEvents: true, // 每天最大事件数,超过则显示更多按钮
|
||||
weekends: true, // 是否显示周末
|
||||
/** 切换语言 */
|
||||
locale: 'zh-cn', // 语言设置为简体中文
|
||||
buttonText: {
|
||||
today: '今天',
|
||||
week: '周视图',
|
||||
day: '日',
|
||||
list: '周列表',
|
||||
},
|
||||
// dateClick: (info) => {
|
||||
// // 点击日期时跳转到天视图
|
||||
// this.calendarApi.changeView('listDay', info.dateStr);
|
||||
// },
|
||||
eventContent(arg) {
|
||||
// 自定义事件内容
|
||||
const eventTitle = arg.event.title
|
||||
const customHtml = `
|
||||
<div style="text-align:center;">
|
||||
<strong>${arg.timeText}</strong><br />
|
||||
<span>${eventTitle}</span>
|
||||
</div>
|
||||
`
|
||||
return { html: customHtml }
|
||||
},
|
||||
eventClick(info) {
|
||||
let html = `
|
||||
<div style="text-align:center;">
|
||||
<div>上课时间: ${info.event.extendedProps.startTime.replace('T', ' ').replace('Z', '')}</div>
|
||||
<div>下课时间: ${info.event.extendedProps.endTime.replace('T', ' ').replace('Z', '')}</div>
|
||||
<div>课程名称: ${info.event.extendedProps.course}</div>
|
||||
<div>上课教师: ${info.event.extendedProps.teacher}</div>
|
||||
<div>上课教室: ${info.event.extendedProps.classroom}</div>
|
||||
<b>----------上课班级---------</b>
|
||||
</div>
|
||||
`
|
||||
info.event.extendedProps.classnames.forEach((element) => {
|
||||
html += `<div>${element}</div>`
|
||||
})
|
||||
// 处理点击事件
|
||||
ElementPlus.ElMessageBox.alert(html, '课程信息', {
|
||||
confirmButtonText: '确认',
|
||||
type: 'info',
|
||||
center: true,
|
||||
dangerouslyUseHTMLString: true,
|
||||
})
|
||||
// 你可以在这里实现更多逻辑,比如显示一个模态框或导航到一个新页面
|
||||
},
|
||||
})
|
||||
this.calendarApi.render()
|
||||
},
|
||||
prevWeek() {
|
||||
this.calendarApi.prev()
|
||||
this.getClassroomCourseList()
|
||||
},
|
||||
nextWeek() {
|
||||
this.calendarApi.next()
|
||||
this.getClassroomCourseList()
|
||||
},
|
||||
today() {
|
||||
this.calendarApi.today()
|
||||
},
|
||||
getTermSetting() {
|
||||
axios
|
||||
.get('http://101.200.126.63:9000/getTermSetting')
|
||||
.then((response) => {
|
||||
this.startDate = response.data.data.startDate
|
||||
localStorage.setItem('termSetting', JSON.stringify(response.data.data))
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
getCampusList() {
|
||||
axios.get('http://101.200.126.63:9000/getCampusList').then((response) => {
|
||||
if (response.data.code === 200) {
|
||||
this.campusList = response.data.data
|
||||
localStorage.setItem('campusSetting', JSON.stringify({ campusId: this.campusId, campusList: response.data.data }))
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
getBuildList() {
|
||||
if (!this.campusId)
|
||||
return
|
||||
axios.get(`http://101.200.126.63:9000/getBuilds/${this.campusId}`).then((response) => {
|
||||
if (response.data.code === 200) {
|
||||
this.buildList = response.data.data
|
||||
localStorage.setItem('buildSetting', JSON.stringify({ campusId: this.campusId, buildList: response.data.data }))
|
||||
}
|
||||
}).catch((error) => { console.error(error) })
|
||||
},
|
||||
getClassroomList() {
|
||||
if (!this.campusId || !this.buildId)
|
||||
return
|
||||
axios.get(`http://101.200.126.63:9000/getClassroomList/${this.buildId}`).then((response) => {
|
||||
if (response.data.code === 200) {
|
||||
this.classroomList = response.data.data
|
||||
localStorage.setItem('classroomSetting', JSON.stringify({ buildId: this.buildId, classroomList: response.data.data }))
|
||||
}
|
||||
}).catch((error) => { console.error(error) })
|
||||
},
|
||||
getClassroomCourseList() {
|
||||
const week = new Date(this.calendarApi.view.activeStart).getTime()
|
||||
this.week = Math.floor((week - new Date(this.startDate).getTime()) / (1000 * 60 * 60 * 24 * 7))
|
||||
if (!this.classroomId || !this.week)
|
||||
return
|
||||
axios.get('http://101.200.126.63:9000/getClassroomCourses', {
|
||||
params: { classroom_id: this.classroomId, week: this.week + 1 },
|
||||
}).then((response) => {
|
||||
this.calendarApi.removeAllEvents()
|
||||
if (response.data.code === 200) {
|
||||
this.currentEvents = response.data.data.map(data => ({
|
||||
id: data.id,
|
||||
title: `${data.course} - ${data.teacher}`,
|
||||
start: data.startTime.replace('T', ' ').replace('Z', ''),
|
||||
end: data.endTime.replace('T', ' ').replace('Z', ''),
|
||||
extendedProps: {
|
||||
...data,
|
||||
},
|
||||
}))
|
||||
localStorage.setItem('currentEventSetting', JSON.stringify({ classroomId: this.classroomId, week: this.week, currentEvents: this.currentEvents }))
|
||||
}
|
||||
// 清空现有事件,并添加新的事件源
|
||||
this.calendarApi.addEventSource(this.currentEvents)
|
||||
this.calendarApi.render()
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (localStorage.getItem('termSetting'))
|
||||
this.startDate = JSON.parse(localStorage.getItem('termSetting')).startDate
|
||||
|
||||
if (localStorage.getItem('campusSetting')) {
|
||||
this.campusId = JSON.parse(localStorage.getItem('campusSetting')).campusId
|
||||
this.campusList = JSON.parse(localStorage.getItem('campusSetting')).campusList
|
||||
}
|
||||
if (localStorage.getItem('buildSetting')) {
|
||||
this.campusId = JSON.parse(localStorage.getItem('buildSetting')).campusId
|
||||
this.buildList = JSON.parse(localStorage.getItem('buildSetting')).buildList
|
||||
}
|
||||
if (localStorage.getItem('classroomSetting')) {
|
||||
this.buildId = JSON.parse(localStorage.getItem('classroomSetting')).buildId
|
||||
this.classroomList = JSON.parse(localStorage.getItem('classroomSetting')).classroomList
|
||||
}
|
||||
if (localStorage.getItem('currentEventSetting')) {
|
||||
this.classroomId = JSON.parse(localStorage.getItem('currentEventSetting')).classroomId
|
||||
this.currentEvents = JSON.parse(localStorage.getItem('currentEventSetting')).currentEvents
|
||||
this.week = JSON.parse(localStorage.getItem('currentEventSetting')).week
|
||||
}
|
||||
this.initCalendar()
|
||||
this.getTermSetting()
|
||||
this.getCampusList()
|
||||
this.calendarApi.addEventSource(this.currentEvents)
|
||||
this.calendarApi.render()
|
||||
},
|
||||
})
|
||||
|
||||
// 使用 Element Plus 并指定语言
|
||||
app.use(ElementPlus, {
|
||||
locale: ElementPlusLocaleZhCn,
|
||||
})
|
||||
app.mount('#app')
|
||||
</script>
|
||||
</html>
|
||||
1
src/hybrid/html/css/index.css
Normal file
BIN
src/hybrid/html/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
2990
src/hybrid/html/js/axios.js
Normal file
78
src/hybrid/html/js/element-plus-index.full.min.js
vendored
Normal file
2
src/hybrid/html/js/element-plus-zh-cn.min.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! Element Plus v2.4.0 */(function(u,e){typeof exports=="object"&&typeof module!="undefined"?module.exports=e():typeof define=="function"&&define.amd?define(e):(u=typeof globalThis!="undefined"?globalThis:u||self,u.ElementPlusLocaleZhCn=e())})(this,function(){"use strict";var u={name:"zh-cn",el:{colorpicker:{confirm:"\u786E\u5B9A",clear:"\u6E05\u7A7A"},datepicker:{now:"\u6B64\u523B",today:"\u4ECA\u5929",cancel:"\u53D6\u6D88",clear:"\u6E05\u7A7A",confirm:"\u786E\u5B9A",selectDate:"\u9009\u62E9\u65E5\u671F",selectTime:"\u9009\u62E9\u65F6\u95F4",startDate:"\u5F00\u59CB\u65E5\u671F",startTime:"\u5F00\u59CB\u65F6\u95F4",endDate:"\u7ED3\u675F\u65E5\u671F",endTime:"\u7ED3\u675F\u65F6\u95F4",prevYear:"\u524D\u4E00\u5E74",nextYear:"\u540E\u4E00\u5E74",prevMonth:"\u4E0A\u4E2A\u6708",nextMonth:"\u4E0B\u4E2A\u6708",year:"\u5E74",month1:"1 \u6708",month2:"2 \u6708",month3:"3 \u6708",month4:"4 \u6708",month5:"5 \u6708",month6:"6 \u6708",month7:"7 \u6708",month8:"8 \u6708",month9:"9 \u6708",month10:"10 \u6708",month11:"11 \u6708",month12:"12 \u6708",weeks:{sun:"\u65E5",mon:"\u4E00",tue:"\u4E8C",wed:"\u4E09",thu:"\u56DB",fri:"\u4E94",sat:"\u516D"},months:{jan:"\u4E00\u6708",feb:"\u4E8C\u6708",mar:"\u4E09\u6708",apr:"\u56DB\u6708",may:"\u4E94\u6708",jun:"\u516D\u6708",jul:"\u4E03\u6708",aug:"\u516B\u6708",sep:"\u4E5D\u6708",oct:"\u5341\u6708",nov:"\u5341\u4E00\u6708",dec:"\u5341\u4E8C\u6708"}},select:{loading:"\u52A0\u8F7D\u4E2D",noMatch:"\u65E0\u5339\u914D\u6570\u636E",noData:"\u65E0\u6570\u636E",placeholder:"\u8BF7\u9009\u62E9"},cascader:{noMatch:"\u65E0\u5339\u914D\u6570\u636E",loading:"\u52A0\u8F7D\u4E2D",placeholder:"\u8BF7\u9009\u62E9",noData:"\u6682\u65E0\u6570\u636E"},pagination:{goto:"\u524D\u5F80",pagesize:"\u6761/\u9875",total:"\u5171 {total} \u6761",pageClassifier:"\u9875",page:"\u9875",prev:"\u4E0A\u4E00\u9875",next:"\u4E0B\u4E00\u9875",currentPage:"\u7B2C {pager} \u9875",prevPages:"\u5411\u524D {pager} \u9875",nextPages:"\u5411\u540E {pager} \u9875",deprecationWarning:"\u4F60\u4F7F\u7528\u4E86\u4E00\u4E9B\u5DF2\u88AB\u5E9F\u5F03\u7684\u7528\u6CD5\uFF0C\u8BF7\u53C2\u8003 el-pagination \u7684\u5B98\u65B9\u6587\u6863"},messagebox:{title:"\u63D0\u793A",confirm:"\u786E\u5B9A",cancel:"\u53D6\u6D88",error:"\u8F93\u5165\u7684\u6570\u636E\u4E0D\u5408\u6CD5!"},upload:{deleteTip:"\u6309 delete \u952E\u53EF\u5220\u9664",delete:"\u5220\u9664",preview:"\u67E5\u770B\u56FE\u7247",continue:"\u7EE7\u7EED\u4E0A\u4F20"},table:{emptyText:"\u6682\u65E0\u6570\u636E",confirmFilter:"\u7B5B\u9009",resetFilter:"\u91CD\u7F6E",clearFilter:"\u5168\u90E8",sumText:"\u5408\u8BA1"},tree:{emptyText:"\u6682\u65E0\u6570\u636E"},transfer:{noMatch:"\u65E0\u5339\u914D\u6570\u636E",noData:"\u65E0\u6570\u636E",titles:["\u5217\u8868 1","\u5217\u8868 2"],filterPlaceholder:"\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9",noCheckedFormat:"\u5171 {total} \u9879",hasCheckedFormat:"\u5DF2\u9009 {checked}/{total} \u9879"},image:{error:"\u52A0\u8F7D\u5931\u8D25"},pageHeader:{title:"\u8FD4\u56DE"},popconfirm:{confirmButtonText:"\u786E\u5B9A",cancelButtonText:"\u53D6\u6D88"}}};return u});
|
||||
//# sourceMappingURL=zh-cn.min.js.map
|
||||
6
src/hybrid/html/js/index.js
Normal file
15361
src/hybrid/html/js/vue.global.js
Normal file
19
src/main.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
// modules
|
||||
// 导入 pinia 实例
|
||||
import pinia from './stores'
|
||||
import 'uno.css'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// Configure store
|
||||
// https://pinia.vuejs.org/
|
||||
// 使用 pinia
|
||||
app.use(pinia)
|
||||
|
||||
return {
|
||||
app,
|
||||
}
|
||||
}
|
||||
91
src/manifest.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "智享通",
|
||||
"appid": "__UNI__F8BB51F",
|
||||
"description": "用于查询武昌校区,流芳校区课表,空教室,教室课表",
|
||||
"versionName": "1.0.2",
|
||||
"versionCode": 20240912,
|
||||
"transformPx": false,
|
||||
"mp-weixin": {
|
||||
"appid": "wxa38f446ef3aab736",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"minified": true,
|
||||
"postcss": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"darkmode": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"locale": "auto",
|
||||
"app-plus": {
|
||||
"distribute": {
|
||||
"icons": {
|
||||
"android": {
|
||||
"hdpi": "unpackage/res/icons/72x72.png",
|
||||
"xhdpi": "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi": "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi": "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios": {
|
||||
"appstore": "unpackage/res/icons/1024x1024.png",
|
||||
"ipad": {
|
||||
"app": "unpackage/res/icons/76x76.png",
|
||||
"app@2x": "unpackage/res/icons/152x152.png",
|
||||
"notification": "unpackage/res/icons/20x20.png",
|
||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x": "unpackage/res/icons/167x167.png",
|
||||
"settings": "unpackage/res/icons/29x29.png",
|
||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||
"spotlight": "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x": "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone": {
|
||||
"app@2x": "unpackage/res/icons/120x120.png",
|
||||
"app@3x": "unpackage/res/icons/180x180.png",
|
||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||
"notification@3x": "unpackage/res/icons/60x60.png",
|
||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||
"settings@3x": "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x": "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x": "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ios": {
|
||||
"dSYMs": false,
|
||||
"idfa": false
|
||||
},
|
||||
"sdkConfigs": {
|
||||
"ad": {}
|
||||
},
|
||||
"splashscreen": {
|
||||
"useOriginalMsgbox": false
|
||||
},
|
||||
"android": {
|
||||
"minSdkVersion": 21,
|
||||
"abiFilters": ["armeabi-v7a", "arm64-v8a", "x86"]
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"SQLite": {},
|
||||
"Webview-x5": {},
|
||||
"UIWebview": {}
|
||||
}
|
||||
},
|
||||
"h5": {
|
||||
"router": {
|
||||
"mode": "hash"
|
||||
},
|
||||
"devServer": {
|
||||
"https": false
|
||||
}
|
||||
},
|
||||
"mp-qq": {
|
||||
"appid": "1112318877"
|
||||
},
|
||||
"fallbackLocale": "zh-Hans"
|
||||
}
|
||||
166
src/modules/course.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import type { GetClassrommsParams } from '~/services/course'
|
||||
|
||||
export interface ClassRoom {
|
||||
_id: string
|
||||
campus: string
|
||||
name: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export interface CourseData {
|
||||
_id: string
|
||||
classTime: number
|
||||
classname_id: string
|
||||
classroom_id: string
|
||||
course: string
|
||||
day: number
|
||||
endStamp: number
|
||||
endTime: string
|
||||
startStamp: number
|
||||
startTime: string
|
||||
status: number
|
||||
teacher: string
|
||||
week: number
|
||||
}
|
||||
|
||||
export interface CourseInfo {
|
||||
courseName: string
|
||||
classroom: string
|
||||
teacher: string
|
||||
weeks: number[]
|
||||
day: number
|
||||
time: number[]
|
||||
id: string
|
||||
}
|
||||
|
||||
// 简单的 UUID 生成函数
|
||||
export const generateUUID = () => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
export const getCourseData = (courseData: CourseData[], classroomData: ClassRoom[]) => {
|
||||
// 创建一个 classroom_id 到 classroom_name 的映射
|
||||
const classroomMap = new Map()
|
||||
classroomData.forEach((item) => {
|
||||
classroomMap.set(item._id, item.name)
|
||||
})
|
||||
// 第一步:将 courseData 中的 classroom_id 替换为教室名称
|
||||
const result = courseData.map(item => ({
|
||||
...item,
|
||||
classroom: classroomMap.get(item.classroom_id) || '未知教室', // 替换 classroom_id 为教室名称
|
||||
}))
|
||||
// 转换为 DataFrame 样式数据结构
|
||||
const df = result
|
||||
// 第二步:根据指定字段去重并将 week 字段的值添加到 weeks 字段中
|
||||
const groupedData = {}
|
||||
df.forEach((item) => {
|
||||
const key = `${item.course}_${item.day}_${item.classroom}_${item.classTime}`
|
||||
if (!groupedData[key]) {
|
||||
groupedData[key] = {
|
||||
course: item.course,
|
||||
classroom: item.classroom,
|
||||
teacher: item.teacher,
|
||||
weeks: [item.week],
|
||||
day: item.day,
|
||||
classTime: item.classTime,
|
||||
startTime: item.startTime,
|
||||
endTime: item.endTime,
|
||||
startStamp: item.startStamp,
|
||||
endStamp: item.endStamp,
|
||||
}
|
||||
}
|
||||
else {
|
||||
groupedData[key].weeks.push(item.week)
|
||||
}
|
||||
})
|
||||
const dfUnique = Object.values(groupedData).map((item: any) => {
|
||||
item.weeks = [...new Set(item.weeks)] // 去重和排序 weeks
|
||||
item.weeks.sort()
|
||||
return item
|
||||
})
|
||||
// 第三步:再进一步的合并 classTime 数据
|
||||
const courseList = dfUnique.map(item => ({
|
||||
courseName: item.course,
|
||||
classroom: item.classroom,
|
||||
teacher: item.teacher,
|
||||
weeks: item.weeks,
|
||||
day: item.day,
|
||||
classTime: item.classTime,
|
||||
}))
|
||||
// 将 weeks 转为 tuple 用于分组
|
||||
const dfGroupedBy = {}
|
||||
courseList.forEach((item) => {
|
||||
const key = `${item.courseName}_${item.classroom}_${item.weeks.join(',')}_${
|
||||
item.day
|
||||
}`
|
||||
if (!dfGroupedBy[key]) {
|
||||
dfGroupedBy[key] = {
|
||||
courseName: item.courseName,
|
||||
classroom: item.classroom,
|
||||
teacher: item.teacher,
|
||||
weeks: item.weeks,
|
||||
day: item.day,
|
||||
time: [item.classTime],
|
||||
}
|
||||
}
|
||||
else {
|
||||
dfGroupedBy[key].time.push(item.classTime)
|
||||
}
|
||||
})
|
||||
const resultList: CourseInfo[] = Object.values(dfGroupedBy).map((item: any) => {
|
||||
const classTimeList = [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 5],
|
||||
[6, 7],
|
||||
[8, 9],
|
||||
[10, 11, 12],
|
||||
]
|
||||
const timeRange = item.time.reduce((acc, cur) => {
|
||||
acc.push(...classTimeList[cur])
|
||||
return acc
|
||||
}, [])
|
||||
item.weeks.sort((a, b) => a - b)
|
||||
timeRange.sort((a, b) => a - b)
|
||||
return {
|
||||
...item,
|
||||
time: [timeRange[0], timeRange[timeRange.length - 1]],
|
||||
day: item.day + 1,
|
||||
id: generateUUID(), // 生成唯一 ID
|
||||
}
|
||||
})
|
||||
|
||||
return resultList
|
||||
}
|
||||
|
||||
export const getEmptyClassroomList = (
|
||||
params: GetClassrommsParams,
|
||||
courseData: CourseData[],
|
||||
classroomData: ClassRoom[],
|
||||
) => {
|
||||
// 创建一个 classroom_id 到 classroom_name 的映射
|
||||
const classroomMap = new Map()
|
||||
const campusMap = new Map()
|
||||
classroomData.forEach((item) => {
|
||||
classroomMap.set(item._id, item.name)
|
||||
campusMap.set(item._id, item.campus)
|
||||
})
|
||||
// 第一步:将 courseData 中的 classroom_id 替换为教室名称
|
||||
const result = courseData.map(item => ({
|
||||
...item,
|
||||
classroom: classroomMap.get(item.classroom_id) || '', // 替换 classroom_id 为教室名称
|
||||
campus: campusMap.get(item.classroom_id) || '',
|
||||
}))
|
||||
result.forEach((course) => {
|
||||
classroomData = classroomData.filter(item => item.campus.includes(params.campus as string)).filter(item => item.name !== course.classroom)
|
||||
})
|
||||
const classroom: string[] = []
|
||||
classroomData.forEach((course) => {
|
||||
classroom.push(course.name)
|
||||
})
|
||||
return classroom
|
||||
}
|
||||
85
src/modules/http.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 添加拦截器:
|
||||
* 拦截 request 请求
|
||||
* 拦截 uploadFile 文件上传
|
||||
*
|
||||
* TODO:
|
||||
* 1. 非 http 开头需拼接地址
|
||||
* 2. 请求超时
|
||||
* 3. 添加小程序端请求头标识
|
||||
* 4. 添加 token 请求头标识
|
||||
*/
|
||||
|
||||
const baseURL = 'http://101.200.126.63:9000'
|
||||
// const baseURL = 'http://192.168.1.198:8080'
|
||||
|
||||
// 添加拦截器
|
||||
const httpInterceptor = {
|
||||
// 拦截前触发
|
||||
invoke(options: UniApp.RequestOptions) {
|
||||
// 1. 非 http 开头需拼接地址
|
||||
if (!options.url.startsWith('http'))
|
||||
options.url = baseURL + options.url
|
||||
|
||||
// 2. 请求超时, 默认 60s
|
||||
options.timeout = 60000
|
||||
// 3. 添加小程序端请求头标识
|
||||
options.header = {
|
||||
...options.header,
|
||||
}
|
||||
},
|
||||
}
|
||||
uni.addInterceptor('request', httpInterceptor)
|
||||
uni.addInterceptor('uploadFile', httpInterceptor)
|
||||
|
||||
/**
|
||||
* 请求函数
|
||||
* @param UniApp.RequestOptions
|
||||
* @returns Promise
|
||||
* 1. 返回 Promise 对象
|
||||
* 2. 获取数据成功
|
||||
* 2.1 提取核心数据 res.data
|
||||
* 2.2 添加类型,支持泛型
|
||||
* 3. 获取数据失败
|
||||
* 3.1 401错误 -> 清理用户信息,跳转到登录页
|
||||
* 3.2 其他错误 -> 根据后端错误信息轻提示
|
||||
* 3.3 网络错误 -> 提示用户换网络
|
||||
*/
|
||||
interface Data<T> {
|
||||
code: number
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
// 2.2 添加类型,支持泛型
|
||||
export const http = <T>(options: UniApp.RequestOptions) => {
|
||||
// 1. 返回 Promise 对象
|
||||
return new Promise<Data<T>>((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
// 响应成功
|
||||
success(res) {
|
||||
// 状态码 2xx, axios 就是这样设计的
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
// 2.1 提取核心数据 res.data
|
||||
resolve(res.data as Data<T>)
|
||||
}
|
||||
else {
|
||||
// 其他错误 -> 根据后端错误信息轻提示
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: (res.data as Data<T>).msg || '请求错误',
|
||||
})
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
// 响应失败
|
||||
fail(err) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '网络错误,换个网络试试',
|
||||
})
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
108
src/pages.json
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"globalStyle": {
|
||||
"backgroundColor": "@bgColor",
|
||||
"backgroundColorBottom": "@bgColorBottom",
|
||||
"backgroundColorTop": "@bgColorTop",
|
||||
"backgroundTextStyle": "@bgTxtStyle",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "@navTxtStyle",
|
||||
"navigationBarTitleText": "ColorTimetable",
|
||||
"navigationStyle": "custom"
|
||||
},
|
||||
"pages": [{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/course/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/course/course",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/classroom/course",
|
||||
"style": {
|
||||
"navigationBarTitleText": "教室课表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/classroom/classroom",
|
||||
"style": {
|
||||
"navigationBarTitleText": "空闲教室"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/classroom/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "空闲教室详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/setting/setting",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#333",
|
||||
"selectedColor": "#1296db",
|
||||
"backgroundColor": "#fff",
|
||||
"borderStyle": "white",
|
||||
"list": [{
|
||||
"iconPath": "static/tabs/home_default.png",
|
||||
"selectedIconPath": "static/tabs/home_selected.png",
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"iconPath": "static/tabs/course_default.png",
|
||||
"selectedIconPath": "static/tabs/course_selected.png",
|
||||
"pagePath": "pages/course/course",
|
||||
"text": "课程表"
|
||||
},
|
||||
{
|
||||
"iconPath": "/static/tabs/classroom_default.png",
|
||||
"pagePath": "pages/classroom/classroom",
|
||||
"selectedIconPath": "/static/tabs/classroom_selected.png",
|
||||
"text": "空教室"
|
||||
},
|
||||
{
|
||||
"iconPath": "/static/tabs/classroom_course_default.png",
|
||||
"pagePath": "pages/classroom/course",
|
||||
"selectedIconPath": "/static/tabs/classroom_course_selected.png",
|
||||
"text": "教室课表"
|
||||
},
|
||||
{
|
||||
"iconPath": "static/tabs/setting_default.png",
|
||||
"selectedIconPath": "static/tabs/setting_selected.png",
|
||||
"pagePath": "pages/setting/setting",
|
||||
"text": "设置"
|
||||
}
|
||||
]
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^U(.*)": "~/components/UnoUI/U$1/U$1.vue",
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
|
||||
}
|
||||
},
|
||||
"condition": { //模式配置,仅开发期间生效
|
||||
"current": 0, //当前激活的模式(list 的索引项)
|
||||
"list": [{
|
||||
"name": "教室课表", //模式名称
|
||||
"path": "pages/classroom/course", //启动页面,必选
|
||||
"query": "" //启动参数,在页面的onLoad函数里面得到
|
||||
}]
|
||||
}
|
||||
}
|
||||
514
src/pages/classroom/classroom.vue
Normal file
@@ -0,0 +1,514 @@
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { usePageStore } from '~/stores'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import cusSelects from '~/components/cus-selects-fan.vue'
|
||||
import UBasePage from '~/components/UnoUI/UBasePage/UBasePage.vue'
|
||||
import { type BuildInfo, getBuildListAPI } from '~/services/course'
|
||||
const { timeSetting, startDate, endDate, campusList, campusId } = storeToRefs(
|
||||
useAppStore(),
|
||||
)
|
||||
const { setPageConfig } = usePageStore()
|
||||
const { getEmptyClassroom } = useAppStore()
|
||||
const dateStr = ref<string>('')
|
||||
const selectDate = ref<string | Date>(new Date())
|
||||
const timeRange = ref<number[]>([1, 2])
|
||||
const campus = ref<string>('')
|
||||
const classroom = ref<string>('')
|
||||
const classroomList = ref<BuildInfo[]>([
|
||||
{
|
||||
name: '全部',
|
||||
id: '',
|
||||
campus: '',
|
||||
campus_id: '',
|
||||
},
|
||||
])
|
||||
|
||||
const getBuilds = async () => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getBuildListAPI(campus.value)
|
||||
if (res.code === 200) {
|
||||
classroomList.value = res.data
|
||||
classroomList.value.push(
|
||||
{
|
||||
name: '全部',
|
||||
id: '',
|
||||
campus: '',
|
||||
campus_id: '',
|
||||
},
|
||||
)
|
||||
classroom.value = ''
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功!' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
const onCampusSelectChange = async (e) => {
|
||||
campus.value = e
|
||||
await getBuilds()
|
||||
}
|
||||
const onClassroomSelectChange = async (e) => {
|
||||
classroom.value = e
|
||||
}
|
||||
/** 起始节数类别 -2未知 -1全天 0上午 1下午 2晚上 */
|
||||
const timeRangeType = ref<number>(-1)
|
||||
|
||||
/** 日历组件 */
|
||||
const calendarRef = ref()
|
||||
const timeList = [
|
||||
[
|
||||
'开始节次',
|
||||
'第1节',
|
||||
'第2节',
|
||||
'第3节',
|
||||
'第4节',
|
||||
'第5节',
|
||||
'第6节',
|
||||
'第7节',
|
||||
'第8节',
|
||||
'第9节',
|
||||
'第10节',
|
||||
'第11节',
|
||||
'第12节',
|
||||
],
|
||||
[
|
||||
'持续节次',
|
||||
'持续1节',
|
||||
'持续2节',
|
||||
'持续3节',
|
||||
'持续4节',
|
||||
'持续5节',
|
||||
'持续6节',
|
||||
'持续7节',
|
||||
'持续8节',
|
||||
'持续9节',
|
||||
'持续10节',
|
||||
'持续11节',
|
||||
'持续12节',
|
||||
],
|
||||
]
|
||||
|
||||
const showTimeActionSheet = ref(false)
|
||||
const timeValue = ref([1, 1])
|
||||
/** 设置起始时间段 */
|
||||
const setTimeRange = (e: number) => {
|
||||
const all = timeSetting.value.map(item => item.index).sort((a, b) => a - b)
|
||||
const morinig = timeSetting.value
|
||||
.filter(item => item.time === 0)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
const afternoon = timeSetting.value
|
||||
.filter(item => item.time === 1)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
const night = timeSetting.value
|
||||
.filter(item => item.time === 2)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
switch (e) {
|
||||
case 0:
|
||||
timeRange.value = [morinig[0], morinig.slice(-1)[0]]
|
||||
timeRangeType.value = 0
|
||||
return
|
||||
case 1:
|
||||
timeRange.value = [afternoon[0], afternoon.slice(-1)[0]]
|
||||
timeRangeType.value = 1
|
||||
return
|
||||
case 2:
|
||||
timeRange.value = [night[0], night.slice(-1)[0]]
|
||||
timeRangeType.value = 2
|
||||
return
|
||||
case 3:
|
||||
timeRange.value = [all[0], all.slice(-1)[0]]
|
||||
timeRangeType.value = 3
|
||||
return
|
||||
default:
|
||||
timeRangeType.value = -1
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取使用时间 */
|
||||
const getUseTime = (nowDate = new Date()) => {
|
||||
let year: string | number = nowDate.getFullYear()
|
||||
year = year.toString().slice(-2)
|
||||
let month: string | number = nowDate.getMonth() + 1
|
||||
if (month < 10)
|
||||
month = `0${month}`
|
||||
|
||||
let date: string | number = nowDate.getDate()
|
||||
if (date < 10)
|
||||
date = `0${date}`
|
||||
selectDate.value = `${nowDate.getFullYear()}-${month}-${date}`
|
||||
const day = nowDate.getDay()
|
||||
const dayList = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
const time = nowDate.getTime() - new Date(startDate.value).getTime()
|
||||
let nowWeek = time / 1000 / 60 / 60 / 24 / 7
|
||||
if (nowWeek < 0) {
|
||||
dateStr.value = `未开学-${dayList[day]}(${year}/${month}/${date})`
|
||||
return
|
||||
}
|
||||
if (nowDate > new Date(endDate.value)) {
|
||||
dateStr.value = `学期已结束-${dayList[day]}(${year}/${month}/${date})`
|
||||
return
|
||||
}
|
||||
nowWeek = Math.floor(nowWeek) + 1
|
||||
dateStr.value = `第${nowWeek}周-${dayList[day]}(${year}/${month}/${date})`
|
||||
}
|
||||
|
||||
/** 启动日历 */
|
||||
const onOpenCalender = () => {
|
||||
calendarRef.value.open()
|
||||
}
|
||||
|
||||
/** 日期变动 */
|
||||
const onCalenderConfirm = (e: any) => {
|
||||
selectDate.value = e.fulldate
|
||||
getUseTime(new Date(e.fulldate))
|
||||
}
|
||||
function handleConfirmTimeActionSheet() {
|
||||
showTimeActionSheet.value = false
|
||||
}
|
||||
|
||||
function handleTimeChange(e: any) {
|
||||
const value = e.detail.value
|
||||
const start = value[0] ? value[0] : 1
|
||||
const duration = value[1] ? value[1] : 1
|
||||
timeValue.value = [start, duration]
|
||||
const all = timeSetting.value.map(item => item.index).sort((a, b) => a - b)
|
||||
const morinig = timeSetting.value
|
||||
.filter(item => item.time === 0)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
const afternoon = timeSetting.value
|
||||
.filter(item => item.time === 1)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
const night = timeSetting.value
|
||||
.filter(item => item.time === 2)
|
||||
.map(item => item.index)
|
||||
.sort((a, b) => a - b)
|
||||
if (start + duration <= timeSetting.value.length)
|
||||
timeRange.value = [start, start + duration]
|
||||
else timeRange.value = [start, start]
|
||||
if (
|
||||
timeRange.value[0] === morinig[0]
|
||||
&& timeRange.value[1] === morinig.slice(-1)[0]
|
||||
)
|
||||
timeRangeType.value = 0
|
||||
else if (
|
||||
timeRange.value[0] === afternoon[0]
|
||||
&& timeRange.value[1] === afternoon.slice(-1)[0]
|
||||
)
|
||||
timeRangeType.value = 1
|
||||
else if (
|
||||
timeRange.value[0] === night[0]
|
||||
&& timeRange.value[1] === night.slice(-1)[0]
|
||||
)
|
||||
timeRangeType.value = 2
|
||||
else if (
|
||||
timeRange.value[0] === all[0]
|
||||
&& timeRange.value[1] === all.slice(-1)[0]
|
||||
)
|
||||
timeRangeType.value = 3
|
||||
else timeRangeType.value = -1
|
||||
}
|
||||
/** 查询空教室 */
|
||||
const queryClassroom = async () => {
|
||||
const startTime
|
||||
= new Date(
|
||||
`${selectDate.value}T${
|
||||
timeSetting.value[timeRange.value[0] - 1].start
|
||||
}:00+08:00`,
|
||||
).getTime() / 1000
|
||||
const endTime
|
||||
= new Date(
|
||||
`${selectDate.value}T${
|
||||
timeSetting.value[timeRange.value[1] - 1].end
|
||||
}:00+08:00`,
|
||||
).getTime() / 1000
|
||||
const flag = await getEmptyClassroom(
|
||||
{ campus_id: campus.value, build_id: classroom.value, startStamp: startTime, endStamp: endTime },
|
||||
dateStr.value,
|
||||
timeRange.value,
|
||||
)
|
||||
if (flag) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/classroom/detail',
|
||||
})
|
||||
}
|
||||
}
|
||||
/** 页面挂载 */
|
||||
onLoad(async () => {
|
||||
getUseTime()
|
||||
campus.value = campusId.value
|
||||
if (campus.value)
|
||||
await getBuilds()
|
||||
})
|
||||
onShow(() => {
|
||||
setPageConfig({ showNavBar: false, pageTitle: '空教室查询' })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<view class="title">
|
||||
<text>空闲教室查询</text>
|
||||
</view>
|
||||
<view class="container">
|
||||
<view class="query-form">
|
||||
<view class="query-form-item" @click="onOpenCalender">
|
||||
<view class="query-form-item-left">
|
||||
<uni-icons type="calendar" color="#A0E2AA" size="24" />
|
||||
<text>使用时间</text>
|
||||
</view>
|
||||
<view class="query-form-item-right">
|
||||
{{ dateStr }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="query-form-item" @click="showTimeActionSheet = true">
|
||||
<view class="query-form-item-left">
|
||||
<uni-icons type="settings" color="#C8CD9A" size="24" />
|
||||
<text>起始节数</text>
|
||||
</view>
|
||||
<view class="query-form-item-right">
|
||||
第{{ timeRange[0] }}-{{ timeRange[1] }}节
|
||||
</view>
|
||||
</view>
|
||||
<view class="query-form-item">
|
||||
<view class="query-form-item-left">
|
||||
<uni-icons type="map" color="#F9BD56" size="24" />
|
||||
<text>选择校区</text>
|
||||
</view>
|
||||
<view>
|
||||
<cus-selects
|
||||
:value="campus"
|
||||
filterable
|
||||
:data="campusList"
|
||||
:value-type="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择校区~"
|
||||
style="width: 100%"
|
||||
@change="onCampusSelectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="query-form-item">
|
||||
<view class="query-form-item-left">
|
||||
<uni-icons type="location" color="#FEC2BF" size="24" />
|
||||
<text>教室位置</text>
|
||||
</view>
|
||||
<view>
|
||||
<cus-selects
|
||||
:value="classroom"
|
||||
filterable
|
||||
:data="classroomList"
|
||||
:value-type="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择教室~"
|
||||
style="width: 100%"
|
||||
@change="onClassroomSelectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 查询范围 -->
|
||||
<view class="query-time-range">
|
||||
<text
|
||||
:class="timeRangeType === 0 ? 'active' : ''"
|
||||
@click="setTimeRange(0)"
|
||||
>
|
||||
上午
|
||||
</text>
|
||||
<text
|
||||
:class="timeRangeType === 1 ? 'active' : ''"
|
||||
@click="setTimeRange(1)"
|
||||
>
|
||||
下午
|
||||
</text>
|
||||
<text
|
||||
:class="timeRangeType === 2 ? 'active' : ''"
|
||||
@click="setTimeRange(2)"
|
||||
>
|
||||
晚上
|
||||
</text>
|
||||
<text
|
||||
:class="timeRangeType === 3 ? 'active' : ''"
|
||||
@click="setTimeRange(3)"
|
||||
>
|
||||
全天
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="qurry-button"
|
||||
bind:tap="queryClassroom"
|
||||
@click="queryClassroom"
|
||||
>
|
||||
查询
|
||||
</view>
|
||||
</UBasePage>
|
||||
|
||||
<uni-calendar
|
||||
ref="calendarRef"
|
||||
:date="selectDate"
|
||||
:insert="false"
|
||||
:start-date="startDate"
|
||||
:end-date="endDate"
|
||||
@confirm="onCalenderConfirm"
|
||||
/>
|
||||
|
||||
<!-- time action sheet -->
|
||||
<view @touchmove.prevent>
|
||||
<view
|
||||
class="bg-white w-full min-h-10 transition-all ease-in-out z-100 duration-300 fixed dark:bg-#121212"
|
||||
:class="showTimeActionSheet ? 'bottom-0' : '-bottom-full'"
|
||||
>
|
||||
<view class="flex flex-col py-6 gap-6">
|
||||
<view
|
||||
class="flex font-medium text-xl px-4 justify-between items-center"
|
||||
>
|
||||
选择上课时间
|
||||
<view
|
||||
class="font-normal text-base text-green-500"
|
||||
@click="handleConfirmTimeActionSheet"
|
||||
>
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
<picker-view class="h-40" :value="timeValue" @change="handleTimeChange">
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in timeList[0]"
|
||||
:key="index"
|
||||
class="flex justify-center items-center"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in timeList[1]"
|
||||
:key="index"
|
||||
class="flex justify-center items-center"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
<view
|
||||
class="flex pb-safe border-t-4 border-gray-200 h-12 text-lg justify-center items-center dark:border-opacity-20"
|
||||
hover-class="bg-gray-200 bg-opacity-50"
|
||||
:hover-stay-time="150"
|
||||
@click="showTimeActionSheet = false"
|
||||
>
|
||||
关闭
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="bg-dark-100 bg-opacity-50 transition-all top-0 right-0 bottom-0 left-0 z-90 fixed"
|
||||
:class="
|
||||
showTimeActionSheet ? 'opacity-100 visible' : 'opacity-0 invisible'
|
||||
"
|
||||
@click="showTimeActionSheet = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background: #f1f2f4;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
width: 45%;
|
||||
height: 10%;
|
||||
background: #fff;
|
||||
top: 8%;
|
||||
left: 32%;
|
||||
z-index: 1;
|
||||
border-radius: 50rpx;
|
||||
transform: translate(-8%, -0%);
|
||||
box-shadow: 0rpx 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
text {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #297fe4;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 25%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -20%);
|
||||
/* border: solid red 2rpx; */
|
||||
background: #fff;
|
||||
width: 80%;
|
||||
height: 60%;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0rpx 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.query-form {
|
||||
padding: 30rpx;
|
||||
|
||||
.query-form-item {
|
||||
border-bottom: 2rpx solid #e9e9e9;
|
||||
padding: 20rpx 0;
|
||||
margin-bottom: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.query-form-item-left {
|
||||
width: 40%;
|
||||
text {
|
||||
margin-left: 5rpx;
|
||||
color: #29a1f7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.query-time-range {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
text {
|
||||
background: #69b8f0;
|
||||
width: 130rpx;
|
||||
height: 90rpx;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
.active {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
.qurry-button {
|
||||
position: fixed;
|
||||
width: 30%;
|
||||
height: 5%;
|
||||
top: 75%;
|
||||
left: 45%;
|
||||
transform: translate(-35%, -15%);
|
||||
background: #29a1f7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
</style>
|
||||
310
src/pages/classroom/course.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<script setup lang="ts">
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import FullCalendar from '@fullcalendar/vue3'
|
||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||
import interactionPlugin from '@fullcalendar/interaction'
|
||||
import listPlugin from '@fullcalendar/list'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type {
|
||||
CalendarOptions,
|
||||
} from '@fullcalendar/core'
|
||||
import cusSelects from '~/components/cus-selects-fan.vue'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { type BuildInfo, type ClassroomCourseInfo, type ClassroomInfo, getBuildListAPI, getClassroomCourseListAPI, getClassroomListAPI } from '~/services/course'
|
||||
|
||||
const { startDate, campusList, campusId, buildId, classroomId } = storeToRefs(useAppStore())
|
||||
const { setPageConfig } = usePageStore()
|
||||
onShow(() => {
|
||||
setPageConfig({
|
||||
showNavBar: true,
|
||||
pageTitle: '教室课表',
|
||||
showBackAction: true,
|
||||
})
|
||||
})
|
||||
// #ifdef H5
|
||||
const campus = ref('')
|
||||
const buildList = ref<BuildInfo[]>([])
|
||||
const classroomList = ref<ClassroomInfo[]>([])
|
||||
|
||||
const getClassroomList = async () => {
|
||||
if (buildId.value) {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getClassroomListAPI(buildId.value)
|
||||
if (res.code === 200)
|
||||
classroomList.value = res.data
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: res.code === 200 ? 'success' : 'error', title: res.msg })
|
||||
}
|
||||
}
|
||||
|
||||
const getBuildList = async () => {
|
||||
if (campusId.value) {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getBuildListAPI(campus.value)
|
||||
if (res.code === 200)
|
||||
buildList.value = res.data
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: res.code === 200 ? 'success' : 'error', title: res.msg })
|
||||
}
|
||||
}
|
||||
|
||||
const onCampusSelectChange = async (e: string) => {
|
||||
campus.value = e
|
||||
await getBuildList()
|
||||
}
|
||||
const onBuildSelectChange = async (e: string) => {
|
||||
buildId.value = e
|
||||
await getClassroomList()
|
||||
}
|
||||
|
||||
/** 当前事件列表 */
|
||||
const currentEvents = ref([])
|
||||
/** 日历对象 */
|
||||
const fullCalendar = ref()
|
||||
const courseList = ref<ClassroomCourseInfo[]>([])
|
||||
|
||||
const getClassroomCourses = async () => {
|
||||
const startTime = dayjs(startDate.value).unix()
|
||||
// const start = dayjs(fullCalendar.value.getApi().view.activeStart).unix()
|
||||
const endTime = dayjs(fullCalendar.value.getApi().view.activeEnd).unix()
|
||||
const week = Math.floor((endTime - startTime) / (60 * 60 * 24 * 7))
|
||||
if (classroomId.value) {
|
||||
const res = await getClassroomCourseListAPI(classroomId.value, week)
|
||||
|
||||
if (res.code === 200) {
|
||||
courseList.value = res.data
|
||||
if (res.data) {
|
||||
fullCalendar.value.getApi().removeAllEvents() // 清除所有事件
|
||||
currentEvents.value = courseList.value.map(data => ({
|
||||
id: data.id,
|
||||
title: `${data.course} - ${data.teacher}`,
|
||||
start: data.startTime.replace('T', ' ').replace('Z', ''),
|
||||
end: data.endTime.replace('T', ' ').replace('Z', ''),
|
||||
extendedProps: {
|
||||
classnames: data.classnames,
|
||||
campus: data.campus,
|
||||
build: data.build,
|
||||
},
|
||||
}))
|
||||
fullCalendar.value.getApi().addEventSource(currentEvents.value) // 添加新的事件源
|
||||
}
|
||||
}
|
||||
fullCalendar.value.getApi().render() // 手动刷新日历
|
||||
}
|
||||
}
|
||||
const onClassroomSelectChange = async (e: string) => {
|
||||
classroomId.value = e
|
||||
await getClassroomCourses()
|
||||
}
|
||||
/** 自定义上周按钮点击事件 */
|
||||
const preWeekCustomClick = async () => {
|
||||
fullCalendar.value.getApi().prev() // 切换到上一周
|
||||
await getClassroomCourses()
|
||||
}
|
||||
/** 自定义下周按钮点击事件 */
|
||||
const nextWeekCustomClick = async () => {
|
||||
fullCalendar.value.getApi().next()
|
||||
await getClassroomCourses()
|
||||
}
|
||||
|
||||
/** 日历组件属性 */
|
||||
const calendarOptions = ref<CalendarOptions>({
|
||||
// 插件
|
||||
plugins: [
|
||||
listPlugin, // 列表视图插件
|
||||
timeGridPlugin, // 周视图和日视图插件
|
||||
interactionPlugin, // 交互插件,支持点击事件
|
||||
],
|
||||
// 自定义按钮
|
||||
customButtons: {
|
||||
preWeekCustom: {
|
||||
text: '上周',
|
||||
click() {
|
||||
preWeekCustomClick() // 点击调用 preWeekCustomClick 函数
|
||||
},
|
||||
},
|
||||
nextWeekCustom: {
|
||||
text: '下周',
|
||||
click() {
|
||||
nextWeekCustomClick() // 点击调用 nextWeekCustomClick 函数
|
||||
},
|
||||
},
|
||||
},
|
||||
/** 修改headerToolbar */
|
||||
headerToolbar: {
|
||||
left: 'preWeekCustom',
|
||||
center: 'title', // 显示日历标题
|
||||
right: 'nextWeekCustom',
|
||||
},
|
||||
dayHeaderFormat: {
|
||||
weekday: 'short', // 显示周几,如"周日"
|
||||
month: '2-digit', // 显示两位数的月份
|
||||
day: '2-digit', // 显示两位数的日期
|
||||
omitCommas: true, // 去除逗号
|
||||
},
|
||||
/** 设置日历高度 */
|
||||
contentHeight: uni.getWindowInfo().windowHeight - 260, // 动态计算高度
|
||||
timeZone: 'Asia/Shanghai',
|
||||
/** 默认视图 (月:dayGridMonth,周:timeGridWeek,日:timeGridDay) */
|
||||
initialView: 'timeGridWeek', // 默认显示周视图
|
||||
firstDay: 1, // 一周的第一天,0表示星期天,1表示星期一
|
||||
slotMinTime: '08:00', // 最小时间段,08:00
|
||||
slotMaxTime: '22:10', // 最大时间段,22:10
|
||||
slotDuration: '00:05', // 时间间隔,5分钟
|
||||
slotLabelFormat: {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
omitZeroMinute: false, // 忽略零分钟
|
||||
hour12: false, // 24小时制
|
||||
meridiem: 'short',
|
||||
},
|
||||
eventTimeFormat: {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false, // 24小时制
|
||||
},
|
||||
eventColor: '#3BB2E3', // 全部日历日程背景色
|
||||
themeSystem: 'bootstrap', // 主题色(本地测试未能生效)
|
||||
events: currentEvents.value, // 事件列表
|
||||
editable: false, // 是否可以拖动修改事件
|
||||
allDaySlot: false, // 是否显示全天事件区域
|
||||
nowIndicator: true, // 是否显示当前时间线
|
||||
selectable: true, // 是否可以选择时间段
|
||||
selectMirror: true, // 是否在选择时间段时显示虚影
|
||||
handleWindowResize: true, // 是否在窗口大小变化时调整日历
|
||||
navLinks: false, // 是否可以通过点击日期导航
|
||||
fixedWeekCount: true, // 每月显示固定周数
|
||||
showNonCurrentDates: true, // 是否显示非当前月的日期
|
||||
dayMaxEvents: true, // 每天最大事件数,超过则显示更多按钮
|
||||
weekends: true, // 是否显示周末
|
||||
/** 切换语言 */
|
||||
locale: 'zh-cn', // 语言设置为简体中文
|
||||
buttonText: {
|
||||
today: '今天',
|
||||
week: '周视图',
|
||||
day: '日',
|
||||
list: '周列表',
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
campus.value = campusId.value
|
||||
await getBuildList()
|
||||
await getClassroomList()
|
||||
await getClassroomCourses()
|
||||
fullCalendar.value.getApi().render()
|
||||
})
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- #ifdef H5 -->
|
||||
<UBasePage>
|
||||
<view class="query-select">
|
||||
<view class="query-select-item">
|
||||
<view class="query-select-item-left">
|
||||
<uni-icons type="map" color="#A0E2AA" size="24" />
|
||||
<text>校区</text>
|
||||
</view>
|
||||
<view>
|
||||
<cus-selects
|
||||
:value="campus"
|
||||
filterable
|
||||
:data="campusList"
|
||||
:value-type="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择校区~"
|
||||
style="width: 100%"
|
||||
@change="onCampusSelectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="query-select-item">
|
||||
<view class="query-select-item-left">
|
||||
<uni-icons type="home" color="#F9BD56" size="24" />
|
||||
<text>教学楼</text>
|
||||
</view>
|
||||
<view>
|
||||
<cus-selects
|
||||
:value="buildId"
|
||||
filterable
|
||||
:data="buildList"
|
||||
:value-type="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择教学楼~"
|
||||
style="width: 100%"
|
||||
@change="onBuildSelectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="query-select-item">
|
||||
<view class="query-select-item-left">
|
||||
<uni-icons type="location" color="#FEC2BF" size="24" />
|
||||
<text>教室</text>
|
||||
</view>
|
||||
<view>
|
||||
<cus-selects
|
||||
:value="classroomId"
|
||||
filterable
|
||||
:data="classroomList"
|
||||
:value-type="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择教室~"
|
||||
style="width: 100%"
|
||||
@change="onClassroomSelectChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions">
|
||||
<template #eventContent="arg">
|
||||
<view class="calender-content">
|
||||
{{ arg.timeText }}
|
||||
</view>
|
||||
<view class="calender-content">
|
||||
{{ arg.event.title }}
|
||||
</view>
|
||||
<view class="calender-content">
|
||||
上课班级数量: {{ arg.event.extendedProps.classnames.length }}
|
||||
</view>
|
||||
</template>
|
||||
</FullCalendar>
|
||||
</UBasePage>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<web-view src="/hybrid/html/calender.html" :fullscreen="false" style="width: 100%; height: 500px;" />
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.query-select{
|
||||
padding: 10rpx 20rpx;
|
||||
background-image: linear-gradient(-225deg, #FFFEFF 0%, #D7FFFE 100%);
|
||||
.query-select-item{
|
||||
border-bottom: 2rpx solid #e9e9e9;
|
||||
padding: 10rpx 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.query-select-item-left {
|
||||
width: 30%;
|
||||
text {
|
||||
margin-left: 5rpx;
|
||||
color: #29a1f7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.calender-content{
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.fc .fc-toolbar-title){
|
||||
font-size: 30rpx;
|
||||
}
|
||||
:deep(.fc){
|
||||
font-size: 26rpx;
|
||||
}
|
||||
:deep(.fc .fc-toolbar.fc-header-toolbar){
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
</style>
|
||||
139
src/pages/classroom/detail.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
const { emptyClassroom } = storeToRefs(useAppStore())
|
||||
const { setPageConfig } = usePageStore()
|
||||
onShow(() => {
|
||||
setPageConfig({
|
||||
showNavBar: true,
|
||||
pageTitle: '空闲教室',
|
||||
showBackAction: true,
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<view class="tilte-content">
|
||||
<view class="title-content-left">
|
||||
<text>查询结果:</text><text style="margin-left: 20rpx">
|
||||
{{ emptyClassroom.data.length }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="title-content-right">
|
||||
<text>{{ emptyClassroom.date }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-for="(item, index) in emptyClassroom.data"
|
||||
:key="index"
|
||||
class="empty-content-item"
|
||||
>
|
||||
<view class="empty-content-item-left">
|
||||
{{ item.campus }}
|
||||
</view>
|
||||
<view class="empty-content-item-right">
|
||||
<view class="empty-content-item-right-item">
|
||||
<uni-icons type="map" color="#F9BD56" size="24" />
|
||||
<text>{{ item.build }}</text>
|
||||
</view>
|
||||
<view class="empty-content-item-right-item">
|
||||
<uni-icons type="location" color="#FEC2BF" size="24" />
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
<view class="empty-content-item-right-item">
|
||||
<uni-icons type="settings" color="#C8CD9A" size="24" />
|
||||
<text>
|
||||
第{{ emptyClassroom.timeRange[0] }}-{{
|
||||
emptyClassroom.timeRange[1]
|
||||
}}节
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</UBasePage>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background: #f5f7f8;
|
||||
}
|
||||
.tilte-content {
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.title-content-left {
|
||||
width: 220rpx;
|
||||
height: 80rpx;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #2095f2;
|
||||
font-weight: bold;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
.title-content-right {
|
||||
width: 400rpx;
|
||||
height: 80rpx;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #2095f2;
|
||||
font-weight: bold;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.empty-content-item {
|
||||
background: white;
|
||||
margin: 30rpx;
|
||||
border-radius: 30rpx;
|
||||
height: 200rpx;
|
||||
box-shadow: 0px 0px 24rpx -4rpx rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.empty-content-item-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100rpx;
|
||||
height: 100%;
|
||||
font-size: 40rpx;
|
||||
background-color: #2095f2;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-top-left-radius: 30rpx;
|
||||
border-bottom-left-radius: 30rpx;
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
|
||||
.empty-content-item-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-left: 20rpx;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
}
|
||||
.empty-content-item-right-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.empty-content-item-right-item text {
|
||||
margin-left: 5rpx;
|
||||
color: #2095f2;
|
||||
}
|
||||
</style>
|
||||
105
src/pages/course/course.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { CourseModel } from '~/stores/modules/course'
|
||||
import CourseActionSheet from '~/components/timetable/CourseActionSheet.vue'
|
||||
import TimetableContent from '~/components/timetable/TimetableContent.vue'
|
||||
import UBasePage from '~/components/UnoUI/UBasePage/UBasePage.vue'
|
||||
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
|
||||
const { customBarHeight, statusBarHeight, classinfo } = storeToRefs(useAppStore())
|
||||
const { setPageConfig } = usePageStore()
|
||||
const { currentWeekIndex, isStart } = storeToRefs(useCourseStore())
|
||||
const {
|
||||
getCourseList,
|
||||
setStartDay,
|
||||
} = useCourseStore()
|
||||
|
||||
onShow(() => {
|
||||
setPageConfig({ showNavBar: false })
|
||||
})
|
||||
|
||||
setStartDay()
|
||||
const showCourseAction = ref(false)
|
||||
|
||||
function handleCreateCourse() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/course/detail',
|
||||
})
|
||||
}
|
||||
|
||||
// handle course item click
|
||||
const showActionSheet = ref(false)
|
||||
const clickedCourseItem = ref<CourseModel>()
|
||||
function handleShowActionSheet(courseItem: CourseModel) {
|
||||
showActionSheet.value = true
|
||||
clickedCourseItem.value = courseItem
|
||||
}
|
||||
|
||||
function handleCloseActionSheet() {
|
||||
showActionSheet.value = false
|
||||
}
|
||||
|
||||
const onFabClick = async () => {
|
||||
uni.showModal({
|
||||
cancelText: '取消',
|
||||
confirmText: '确认',
|
||||
title: '刷新课表?',
|
||||
content: '是否在线刷新课表,确认后将会覆盖课表',
|
||||
success: async () => {
|
||||
if (classinfo.value) {
|
||||
await getCourseList(classinfo.value.value)
|
||||
}
|
||||
else {
|
||||
uni.showToast({
|
||||
icon: 'error',
|
||||
title: '请先配置班级~',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
const formattedWeek = computed(() => {
|
||||
return `第${currentWeekIndex.value + 1}周${!isStart.value ? '(未开学)' : ''}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<view
|
||||
class="bg-primary text-white w-full top-0 z-200 fixed font-bold"
|
||||
:style="{ height: `${customBarHeight}px` }"
|
||||
>
|
||||
<view
|
||||
:style="{
|
||||
'padding-top': `${statusBarHeight}px`,
|
||||
'height': `${customBarHeight - statusBarHeight}px`,
|
||||
}"
|
||||
>
|
||||
<view class="h-full text-center px-6 relative">
|
||||
<view class="h-full text-xl left-4 i-carbon-add absolute" @click="handleCreateCourse" />
|
||||
<view
|
||||
class="flex h-full mx-auto justify-center items-center inline-block text-lg"
|
||||
@click="showCourseAction = !showCourseAction"
|
||||
>
|
||||
{{ formattedWeek }}
|
||||
<view
|
||||
class="transition-transform duration-300 i-carbon-chevron-up"
|
||||
:class="showCourseAction ? 'rotate-180' : 'rotate-0'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- timetable main content -->
|
||||
<TimetableContent :show-course-action="showCourseAction" @course-item-click="handleShowActionSheet" />
|
||||
<!-- course card -->
|
||||
<CourseActionSheet
|
||||
:show-action-sheet="showActionSheet" :course-item="clickedCourseItem"
|
||||
@cancel="handleCloseActionSheet"
|
||||
/>
|
||||
</UBasePage>
|
||||
<uni-fab horizontal="right" :pattern="{ icon: 'loop' }" @fab-click="onFabClick" />
|
||||
</template>
|
||||
333
src/pages/course/detail.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { CourseModel } from '~/stores/modules/course'
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
import { generateUUID } from '~/modules/course'
|
||||
const { setPageConfig } = usePageStore()
|
||||
|
||||
const defaultCourse: CourseModel = {
|
||||
id: generateUUID(),
|
||||
title: '',
|
||||
location: '课程地点',
|
||||
teacher: '',
|
||||
week: 1,
|
||||
weeks: [1, 2, 3, 4, 5],
|
||||
start: 1,
|
||||
duration: 2,
|
||||
}
|
||||
|
||||
const courseStore = useCourseStore()
|
||||
const courseList = ref<CourseModel[]>([])
|
||||
|
||||
const originalCourseId = ref('')
|
||||
const courseTitle = ref('')
|
||||
const courseTeacher = ref('')
|
||||
const isUpdate = ref(false)
|
||||
|
||||
onLoad((option: any) => {
|
||||
isUpdate.value = !!option?.id
|
||||
if (isUpdate.value) {
|
||||
const courseListTemp = courseStore.courseList.filter(item => item.id === option?.id)
|
||||
for (const courseItem of courseListTemp)
|
||||
courseList.value.push(cloneDeep(courseItem))
|
||||
originalCourseId.value = option?.id
|
||||
courseTitle.value = courseListTemp[0].title || ''
|
||||
courseTeacher.value = courseListTemp[0].teacher || ''
|
||||
}
|
||||
else {
|
||||
courseList.value = [defaultCourse]
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
setPageConfig({ pageTitle: isUpdate.value ? '编辑课程' : '添加课程', showBackAction: true, showNavBar: true })
|
||||
})
|
||||
|
||||
function handleDeleteCourseItem(courseIndex: number) {
|
||||
uni.showModal({
|
||||
title: '警告',
|
||||
content: '确定删除该时间段的课程吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm)
|
||||
courseList.value.splice(courseIndex, 1)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleAddNewTime() {
|
||||
courseList.value.push(cloneDeep(courseList.value[courseList.value.length - 1]))
|
||||
}
|
||||
|
||||
function handleSaveCourse() {
|
||||
if (!courseTitle.value) {
|
||||
uni.showToast({
|
||||
title: '请输入课程名称',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
for (const courseItem of courseList.value)
|
||||
Object.assign(courseItem, { title: courseTitle.value, teacher: courseTeacher.value })
|
||||
|
||||
if (isUpdate.value)
|
||||
courseStore.deleteCourseItemById(originalCourseId.value)
|
||||
|
||||
courseStore.setCourseList(courseStore.courseList.concat(courseList.value))
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '保存成功',
|
||||
showCancel: false,
|
||||
})
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const showWeekActionSheet = ref(false)
|
||||
const clickedIndex = ref(-1)
|
||||
const clickedWeeks = ref<number[]>([])
|
||||
|
||||
function handleShowWeekActionSheet(clickIndex: number) {
|
||||
showWeekActionSheet.value = true
|
||||
clickedIndex.value = clickIndex
|
||||
clickedWeeks.value = courseList.value[clickIndex].weeks
|
||||
}
|
||||
|
||||
/**
|
||||
* transform weeks to string eg: [1, 2, 3, 5, 6, 8] to '1-3,5-6,8'
|
||||
* @param weeks week list
|
||||
*/
|
||||
function transformArray2String(weeks: number[]): string {
|
||||
let weeksString = ''
|
||||
for (let i = 0; i < weeks.length; i++) {
|
||||
if (i === 0) {
|
||||
weeksString += weeks[i]
|
||||
}
|
||||
else {
|
||||
if (weeks[i] !== weeks[i - 1] + 1) {
|
||||
const last = weeksString.split(',')[weeksString.split(',').length - 1]
|
||||
if (Number.parseInt(last) !== weeks[i - 1])
|
||||
weeksString += `-${weeks[i - 1]}`
|
||||
weeksString += `,${weeks[i]}`
|
||||
}
|
||||
else {
|
||||
if (i === weeks.length - 1)
|
||||
weeksString += `-${weeks[i]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
return weeksString
|
||||
}
|
||||
|
||||
function handleClickWeek(week: number) {
|
||||
if (!clickedWeeks.value.includes(week)) {
|
||||
clickedWeeks.value.push(week)
|
||||
clickedWeeks.value.sort((a, b) => a - b)
|
||||
}
|
||||
else {
|
||||
const index = clickedWeeks.value.indexOf(week)
|
||||
clickedWeeks.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirmWeekActionSheet() {
|
||||
showWeekActionSheet.value = false
|
||||
courseList.value[clickedIndex.value].weeks = clickedWeeks.value
|
||||
}
|
||||
|
||||
const showTimeActionSheet = ref(false)
|
||||
const timeValue = ref([1, 1, 1])
|
||||
|
||||
function handleShowTimeActionSheet(clickIndex: number) {
|
||||
showTimeActionSheet.value = true
|
||||
clickedIndex.value = clickIndex
|
||||
const { week, start, duration } = courseList.value[clickIndex]
|
||||
timeValue.value = [week, start, duration]
|
||||
}
|
||||
|
||||
const timeList = [
|
||||
['星期数', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
|
||||
['开始节次', '第1节', '第2节', '第3节', '第4节', '第5节', '第6节', '第7节', '第8节', '第9节'],
|
||||
['持续节次', '持续1节', '持续2节', '持续3节', '持续4节', '持续5节', '持续6节', '持续7节', '持续8节'],
|
||||
]
|
||||
|
||||
function handleTimeChange(e: any) {
|
||||
const value = e.detail.value
|
||||
courseList.value[clickedIndex.value].week = value[0] ? value[0] : 1
|
||||
courseList.value[clickedIndex.value].start = value[1] ? value[1] : 1
|
||||
courseList.value[clickedIndex.value].duration = value[2] ? value[2] : 1
|
||||
}
|
||||
|
||||
function handleConfirmTimeActionSheet() {
|
||||
showTimeActionSheet.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<view>
|
||||
<view class="bg-white mb-4 py-1 justify-center items-start dark:bg-#121212">
|
||||
<view class="px-4">
|
||||
<view class="text-lg">
|
||||
课程信息
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex px-4 justify-start items-center">
|
||||
<view class="min-w-14">
|
||||
名称
|
||||
</view>
|
||||
<input v-model="courseTitle" class="w-full" type="text" placeholder="输入课程名(必填)">
|
||||
</view>
|
||||
<view class="flex px-4 justify-start items-center">
|
||||
<view class="min-w-14">
|
||||
教师
|
||||
</view>
|
||||
<input v-model="courseTeacher" class="w-full" type="text" placeholder="输入上课教师">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-for="(courseItem, courseIndex) of courseList" :key="courseIndex">
|
||||
<view class="bg-white flex flex-col mb-4 py-1 gap-2 justify-center dark:bg-#121212">
|
||||
<view class="flex px-4 justify-between items-center">
|
||||
<view class="text-lg">
|
||||
{{ `时间段${courseIndex + 1}` }}
|
||||
</view>
|
||||
<view class="text-red-500 i-carbon-delete" @click="handleDeleteCourseItem(courseIndex)" />
|
||||
</view>
|
||||
<view class="flex px-4 justify-start items-center">
|
||||
<view class="min-w-14">
|
||||
地点
|
||||
</view>
|
||||
<input v-model="courseItem.location" class="w-full" type="text" placeholder="输入上课地点(选填)">
|
||||
</view>
|
||||
<view class="flex px-4 justify-start items-center">
|
||||
<view class="min-w-14">
|
||||
周数
|
||||
</view>
|
||||
<view class="w-full" @click="handleShowWeekActionSheet(courseIndex)">
|
||||
{{ transformArray2String(courseItem.weeks) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex px-4 justify-start items-center">
|
||||
<view class="min-w-14">
|
||||
时间
|
||||
</view>
|
||||
<view class="w-full" @click="handleShowTimeActionSheet(courseIndex)">
|
||||
{{ `${timeList[0][courseItem.week]} 第${courseItem.start}-${courseItem.start + courseItem.duration - 1}节` }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="flex flex-col pb-safe gap-1 justify-center">
|
||||
<view
|
||||
class="flex bg-green-500 h-12 text-white text-center justify-center items-center"
|
||||
hover-class="bg-green-600" :hover-stay-time="150" @click="handleAddNewTime"
|
||||
>
|
||||
<view class="i-carbon-add" />
|
||||
添加其他时间段
|
||||
</view>
|
||||
<view
|
||||
class="flex bg-blue-500 h-12 text-white text-center justify-center items-center"
|
||||
hover-class="bg-blue-600" :hover-stay-time="150" @click="handleSaveCourse"
|
||||
>
|
||||
保存
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- week action sheet -->
|
||||
<view @touchmove.prevent>
|
||||
<view
|
||||
class="bg-white w-full min-h-10 transition-all ease-in-out z-100 duration-300 fixed dark:bg-#121212"
|
||||
:class="showWeekActionSheet ? 'bottom-0' : '-bottom-full'"
|
||||
>
|
||||
<view class="flex flex-col py-6 gap-6">
|
||||
<view class="flex font-medium text-xl px-4 justify-between items-center">
|
||||
选择上课周
|
||||
<view class="font-normal text-base text-green-500" @click="handleConfirmWeekActionSheet">
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
<view class="grid p-1 gap-1 grid-cols-5 justify-center items-center">
|
||||
<template v-for="(item, index) of 20" :key="index">
|
||||
<view
|
||||
class="flex h-8 text-center text-white transition-all inline-block justify-center items-center"
|
||||
:class="clickedWeeks.includes(index + 1) ? 'bg-blue-500' : 'bg-gray-300'"
|
||||
@click="handleClickWeek(index + 1)"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="flex pb-safe border-t-4 border-gray-200 h-12 text-lg justify-center items-center dark:border-opacity-20"
|
||||
hover-class="bg-gray-200 bg-opacity-50" :hover-stay-time="150" @click="showWeekActionSheet = false"
|
||||
>
|
||||
关闭
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class=" bg-dark-100 bg-opacity-50 transition-all top-0 right-0 bottom-0 left-0 z-90 fixed"
|
||||
:class="showWeekActionSheet ? 'opacity-100 visible' : 'opacity-0 invisible'"
|
||||
@click="showWeekActionSheet = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- time action sheet -->
|
||||
<view @touchmove.prevent>
|
||||
<view
|
||||
class="bg-white w-full min-h-10 transition-all ease-in-out z-100 duration-300 fixed dark:bg-#121212"
|
||||
:class="showTimeActionSheet ? 'bottom-0' : '-bottom-full'"
|
||||
>
|
||||
<view class="flex flex-col py-6 gap-6">
|
||||
<view class="flex font-medium text-xl px-4 justify-between items-center">
|
||||
选择上课时间
|
||||
<view class="font-normal text-base text-green-500" @click="handleConfirmTimeActionSheet">
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
<picker-view class="h-40" :value="timeValue" @change="handleTimeChange">
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in timeList[0]" :key="index"
|
||||
class="flex justify-center items-center"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in timeList[1]" :key="index"
|
||||
class="flex justify-center items-center"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in timeList[2]" :key="index"
|
||||
class="flex justify-center items-center"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
<view
|
||||
class="flex pb-safe border-t-4 border-gray-200 h-12 text-lg justify-center items-center dark:border-opacity-20"
|
||||
hover-class="bg-gray-200 bg-opacity-50" :hover-stay-time="150" @click="showTimeActionSheet = false"
|
||||
>
|
||||
关闭
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class=" bg-dark-100 bg-opacity-50 transition-all top-0 right-0 bottom-0 left-0 z-90 fixed"
|
||||
:class="showTimeActionSheet ? 'opacity-100 visible' : 'opacity-0 invisible'"
|
||||
@click="showTimeActionSheet = false"
|
||||
/>
|
||||
</view>
|
||||
</UBasePage>
|
||||
</template>
|
||||
451
src/pages/index/index.vue
Normal file
@@ -0,0 +1,451 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import type { CourseModel } from '~/stores/modules/course'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
import { getTermSettingAPI } from '~/services/course'
|
||||
const { courseList } = storeToRefs(useCourseStore())
|
||||
const { startDate, timeSetting, endDate, totalWeeks } = storeToRefs(
|
||||
useAppStore(),
|
||||
)
|
||||
const { setStartDay, setCourseSetting } = useCourseStore()
|
||||
const month = ref<number>(new Date().getMonth() + 1)
|
||||
const date = ref<number>(new Date().getDate())
|
||||
const today = ref<number>(new Date().getDay())
|
||||
const weekday = [
|
||||
'星期天',
|
||||
'星期一',
|
||||
'星期二',
|
||||
'星期三',
|
||||
'星期四',
|
||||
'星期五',
|
||||
'星期六',
|
||||
]
|
||||
const currentWeek = ref<number>(1)
|
||||
|
||||
interface CourseType {
|
||||
startTime: string
|
||||
endTime: string
|
||||
data: CourseModel[]
|
||||
}
|
||||
const morningCourse = ref<CourseType>()
|
||||
const afternoonCourse = ref<CourseType>()
|
||||
const eveningCourse = ref<CourseType>()
|
||||
const initCourse = async () => {
|
||||
const time = new Date().getTime() - new Date(startDate.value).getTime()
|
||||
currentWeek.value = Math.ceil(time / 1000 / 60 / 60 / 24 / 7)
|
||||
morningCourse.value = {
|
||||
startTime: '08:00',
|
||||
endTime: '12:20',
|
||||
data: courseList.value.filter(
|
||||
item =>
|
||||
item.week === today.value
|
||||
&& item.weeks.includes(currentWeek.value)
|
||||
&& item.start + item.duration <= 6,
|
||||
),
|
||||
}
|
||||
afternoonCourse.value = {
|
||||
startTime: '14:00',
|
||||
endTime: '17:30',
|
||||
data: courseList.value.filter(
|
||||
item =>
|
||||
item.week === today.value
|
||||
&& item.weeks.includes(currentWeek.value)
|
||||
&& item.start + item.duration <= 10
|
||||
&& item.start + item.duration > 6,
|
||||
),
|
||||
}
|
||||
eveningCourse.value = {
|
||||
startTime: '19:00',
|
||||
endTime: '21:25',
|
||||
data: courseList.value.filter(
|
||||
item =>
|
||||
item.week === today.value
|
||||
&& item.weeks.includes(currentWeek.value)
|
||||
&& item.start + item.duration >= 11,
|
||||
),
|
||||
}
|
||||
}
|
||||
const getTermSetting = async () => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getTermSettingAPI()
|
||||
if (res.code === 200) {
|
||||
startDate.value = res.data.startDate
|
||||
endDate.value = res.data.endDate
|
||||
totalWeeks.value = res.data.totalWeeks
|
||||
timeSetting.value = res.data.timeSetting
|
||||
setCourseSetting(res.data.startDate, res.data.totalWeeks)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
onLoad(async () => {
|
||||
if (!startDate.value) {
|
||||
await getTermSetting()
|
||||
setStartDay()
|
||||
}
|
||||
initCourse()
|
||||
})
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
initCourse()
|
||||
setTimeout(() => {
|
||||
// 结束下拉刷新
|
||||
uni.stopPullDownRefresh ()
|
||||
}, 500)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-box">
|
||||
<view class="header">
|
||||
<view class="currentStatus">
|
||||
今日课表
|
||||
</view>
|
||||
<view class="todayDate">
|
||||
{{ month }}月{{ date }}日 | {{ weekday[today] }} |
|
||||
{{ currentWeek > 0 ? `第${currentWeek}周` : '未开学' }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="headerFore"></view> -->
|
||||
<view class="content">
|
||||
<!-- 上午 -->
|
||||
<view class="section">
|
||||
上午课程
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in morningCourse?.data"
|
||||
:key="index"
|
||||
class="class"
|
||||
>
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
{{ timeSetting.filter((x) => x.index === item.start)[0].start }}
|
||||
</view>
|
||||
<view class="end opa">
|
||||
{{
|
||||
timeSetting.filter(
|
||||
(x) => x.index === (item.start + item.duration - 1),
|
||||
)[0].end
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson text-cut">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="infoOther opa">
|
||||
{{ item.location }} | {{ item.teacher }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<block v-if="morningCourse?.data.length === 0">
|
||||
<view class="class noclass">
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
08:00
|
||||
</view>
|
||||
<view class="end">
|
||||
12:20
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson">
|
||||
空 闲
|
||||
</view>
|
||||
<view class="infoOther">
|
||||
第1节 - 第5节
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 下午 -->
|
||||
<view class="section">
|
||||
下午课程
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in afternoonCourse?.data"
|
||||
:key="index"
|
||||
class="class"
|
||||
>
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
{{ timeSetting.filter((x) => x.index === item.start)[0].start }}
|
||||
</view>
|
||||
<view class="end opa">
|
||||
{{
|
||||
timeSetting.filter(
|
||||
(x) => x.index === (item.start + item.duration - 1),
|
||||
)[0].end
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson text-cut">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="infoOther opa">
|
||||
{{ item.location }} | {{ item.teacher }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<block v-if="afternoonCourse?.data.length === 0">
|
||||
<view class="class noclass">
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
14:00
|
||||
</view>
|
||||
<view class="end">
|
||||
17:30
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson">
|
||||
空 闲
|
||||
</view>
|
||||
<view class="infoOther">
|
||||
第6节 - 第9节
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 晚上 -->
|
||||
<view class="section">
|
||||
晚上课程
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in eveningCourse?.data"
|
||||
:key="index"
|
||||
class="class"
|
||||
>
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
{{ timeSetting.filter((x) => x.index === item.start)[0].start }}
|
||||
</view>
|
||||
<view class="end opa">
|
||||
{{
|
||||
timeSetting.filter(
|
||||
(x) => x.index === (item.start + item.duration - 1),
|
||||
)[0].end
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson text-cut">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="infoOther opa">
|
||||
{{ item.location }} | {{ item.teacher }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<block v-if="eveningCourse?.data.length === 0">
|
||||
<view class="class noclass">
|
||||
<view class="time">
|
||||
<view class="start">
|
||||
19:00
|
||||
</view>
|
||||
<view class="end">
|
||||
21:25
|
||||
</view>
|
||||
</view>
|
||||
<view class="viewider" />
|
||||
<view class="infoCan">
|
||||
<view class="infoLesson">
|
||||
空 闲
|
||||
</view>
|
||||
<view class="infoOther">
|
||||
第10节 - 第12节
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.box {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
touch-action: auto;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-box {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 650rpx;
|
||||
background: url('https://cdn.cuuo.cn/images/course-home.png');
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
overflow: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.todayDate {
|
||||
margin-top: 3vw;
|
||||
margin-left: 8vw;
|
||||
font-weight: 400;
|
||||
font-size: 3.5vw;
|
||||
color: #f1faff;
|
||||
letter-spacing: 0.9px;
|
||||
}
|
||||
|
||||
.currentStatus {
|
||||
margin-top: 200rpx;
|
||||
margin-left: 8vw;
|
||||
font-weight: 600;
|
||||
font-size: 5vw;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tips {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border-radius: 15rpx;
|
||||
padding: 10rpx;
|
||||
top: 330rpx;
|
||||
right: 18vw;
|
||||
max-width: 500rpx;
|
||||
z-index: 100;
|
||||
opacity: 0.8;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 20rpx;
|
||||
width: 50rpx;
|
||||
right: -15rpx;
|
||||
top: 25rpx;
|
||||
border-radius: 0rpx 50rpx 0rpx 50rpx;
|
||||
background-color: #fff;
|
||||
z-index: -10;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.headerFore {
|
||||
position: absolute;
|
||||
width: 300rpx;
|
||||
height: 235rpx;
|
||||
top: 330rpx;
|
||||
right: -10vw;
|
||||
background: url('https://cdn.cuuo.cn/images/course-tips.gif');
|
||||
background-size: cover;
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
margin-top: -100rpx;
|
||||
margin-bottom: calc(100rpx + env(safe-area-inset-bottom));
|
||||
padding: 2vw 7vw;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: calc(100vh - 30vw);
|
||||
border-radius: 5vw 5vw 0px 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 6vw;
|
||||
margin-bottom: 3vw;
|
||||
font-size: 3vw;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
color: #8c93b0;
|
||||
}
|
||||
|
||||
.class {
|
||||
margin: 20rpx 10rpx;
|
||||
height: 10vw;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.opa {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.noclass {
|
||||
color: #3a526f;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #000;
|
||||
width: 9vw;
|
||||
min-width: 9vw;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
margin-right: 1.5vw;
|
||||
}
|
||||
|
||||
.start {
|
||||
padding-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.start,
|
||||
.end {
|
||||
font-size: 3vw;
|
||||
}
|
||||
|
||||
.viewider {
|
||||
background-color: #2196f3;
|
||||
width: 1vw;
|
||||
min-width: 1vw;
|
||||
height: 8vw;
|
||||
border-radius: 10rpx;
|
||||
color: #0000ff8c;
|
||||
}
|
||||
|
||||
.infoCan {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 2vw;
|
||||
margin-right: 1vw;
|
||||
}
|
||||
|
||||
.infoLesson,
|
||||
.infoOther {
|
||||
color: #000;
|
||||
letter-spacing: 0;
|
||||
margin-bottom: 0.7vw;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.infoLesson {
|
||||
font-size: 4vw;
|
||||
max-width: 590rpx;
|
||||
}
|
||||
|
||||
.infoOther {
|
||||
font-size: 3vw;
|
||||
}
|
||||
</style>
|
||||
155
src/pages/setting/setting.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import UBasePage from '~/components/UnoUI/UBasePage/UBasePage.vue'
|
||||
import cusSelects from '~/components/cus-selects-fan.vue'
|
||||
import { useAppStore } from '~/stores/modules/app'
|
||||
import { useCourseStore } from '~/stores/modules/course'
|
||||
import { getTermSettingAPI } from '~/services/course'
|
||||
import { usePageStore } from '~/stores'
|
||||
|
||||
const { startDate, endDate, totalWeeks, classNames, classinfo, campusId, timeSetting, campusList }
|
||||
= storeToRefs(useAppStore())
|
||||
const { setPageConfig } = usePageStore()
|
||||
const { getClassNames, getCampusList } = useAppStore()
|
||||
const { getCourseList, setStartDay, setCourseSetting } = useCourseStore()
|
||||
const classId = ref<string>('')
|
||||
const getTermSetting = async () => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getTermSettingAPI()
|
||||
if (res.code === 200) {
|
||||
startDate.value = res.data.startDate
|
||||
endDate.value = res.data.endDate
|
||||
totalWeeks.value = res.data.totalWeeks
|
||||
timeSetting.value = res.data.timeSetting
|
||||
setCourseSetting(res.data.startDate, res.data.totalWeeks)
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
classId.value = classinfo.value.value
|
||||
await getTermSetting()
|
||||
setStartDay()
|
||||
await getCampusList()
|
||||
})
|
||||
const resetTerm = async () => {
|
||||
uni.showModal({
|
||||
cancelText: '取消',
|
||||
title: '刷新提醒',
|
||||
content: '是否刷新学期配置?',
|
||||
confirmText: '确认',
|
||||
success: async () => {
|
||||
await getTermSetting()
|
||||
setStartDay()
|
||||
},
|
||||
fail: (fail) => {
|
||||
uni.showToast({
|
||||
title: '刷新失败~',
|
||||
icon: 'error',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
const saveClassInfo = async () => {
|
||||
uni.showModal({
|
||||
cancelText: '取消',
|
||||
confirmText: '确认',
|
||||
title: '刷新课程?',
|
||||
content: '是否刷新课程信息?',
|
||||
success: async (success) => {
|
||||
if (classId.value) {
|
||||
classinfo.value = classNames.value.filter(
|
||||
item => item.value === classId.value,
|
||||
)[0]
|
||||
await getCourseList(classId.value)
|
||||
}
|
||||
else {
|
||||
uni.showToast({
|
||||
icon: 'error',
|
||||
title: '请选择班级!',
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (fail) => {
|
||||
uni.showToast({
|
||||
title: '刷新失败~',
|
||||
icon: 'error',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
const onCampusSelectChange = async (e) => {
|
||||
campusId.value = e
|
||||
await getClassNames(e)
|
||||
}
|
||||
const onselectChange = async (e) => {
|
||||
classId.value = e
|
||||
}
|
||||
onShow(() => {
|
||||
setPageConfig({ showNavBar: true, pageTitle: '课表配置' })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<uni-section title="学期配置" type="line">
|
||||
<uni-card title="" is-full is-shadow>
|
||||
<uni-forms label-position="top">
|
||||
<uni-forms-item label="开学日期">
|
||||
<uni-easyinput
|
||||
v-model="startDate"
|
||||
placeholder="请输入开学日期"
|
||||
disabled
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="结束日期">
|
||||
<uni-easyinput
|
||||
v-model="endDate"
|
||||
placeholder="请输入结束日期"
|
||||
disabled
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="学期周数">
|
||||
<uni-easyinput
|
||||
v-model="totalWeeks"
|
||||
placeholder="请输入学期周数"
|
||||
disabled
|
||||
/>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<button type="primary" @click="resetTerm">
|
||||
更新学期配置
|
||||
</button>
|
||||
</uni-card>
|
||||
</uni-section>
|
||||
<uni-section title="班级配置" type="line">
|
||||
<uni-card title="" is-full is-shadow class="classinfo">
|
||||
<uni-forms label-position="top">
|
||||
<uni-forms-item label="校区">
|
||||
<cus-selects :value="campusId" filterable :data="campusList" :value-type="{ label: 'name', value: 'id' }" style="width: 100%;" @change="onCampusSelectChange" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="班级">
|
||||
<cus-selects :value="classId" filterable :data="classNames" :value-type="{ label: 'text', value: 'value' }" style="width: 100%;" @change="onselectChange" />
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<button type="primary" @click="saveClassInfo">
|
||||
保存班级信息
|
||||
</button>
|
||||
</uni-card>
|
||||
</uni-section>
|
||||
</UBasePage>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.classinfo {
|
||||
height: 700rpx;
|
||||
}
|
||||
.select_wrap{
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
153
src/pages/splash/splash.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import { usePageStore } from '~/stores/modules/page'
|
||||
const { setPageConfig } = usePageStore()
|
||||
|
||||
onShow(() => {
|
||||
setPageConfig({ showNavBar: false })
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index',
|
||||
})
|
||||
}, 500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBasePage>
|
||||
<view class="loader dark:bg-#121212" />
|
||||
</UBasePage>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.loader {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.loader {
|
||||
top: 72vh;
|
||||
left: 50vw;
|
||||
transform: rotate(165deg);
|
||||
}
|
||||
|
||||
.loader:after,
|
||||
.loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: block;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
border-radius: 0.25em;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
animation: before 2s infinite;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
animation: after 2s infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes before {
|
||||
0% {
|
||||
width: 0.5em;
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
width: 2.5em;
|
||||
box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75),
|
||||
0 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 0.5em;
|
||||
box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes before {
|
||||
0% {
|
||||
width: 0.5em;
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
width: 2.5em;
|
||||
box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75),
|
||||
0 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 0.5em;
|
||||
box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75),
|
||||
-1em 0.5em rgba(111, 202, 220, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes after {
|
||||
0% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
height: 2.5em;
|
||||
box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75),
|
||||
-0.5em 0 rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em 1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes after {
|
||||
0% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
35% {
|
||||
height: 2.5em;
|
||||
box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75),
|
||||
-0.5em 0 rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
70% {
|
||||
height: 0.5em;
|
||||
box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em 1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
|
||||
to {
|
||||
box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75),
|
||||
-0.5em -1em rgba(233, 169, 32, 0.75);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
147
src/services/course.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { http } from '~/modules/http'
|
||||
import type { TimeIndex } from '~/stores/modules/app'
|
||||
|
||||
export interface ClassName {
|
||||
text: string
|
||||
value: string
|
||||
disable: boolean
|
||||
}
|
||||
interface ClassResult {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
export interface CourseInfo {
|
||||
classroom: string
|
||||
course: string
|
||||
day: number
|
||||
id: string
|
||||
teacher: string
|
||||
time: number[]
|
||||
weeks: number[]
|
||||
}
|
||||
|
||||
export interface ClassroomInfo {
|
||||
id: string
|
||||
name: string
|
||||
build: string
|
||||
build_id: string
|
||||
campus_id: string
|
||||
campus: string
|
||||
}
|
||||
|
||||
export interface ClassroomCourseInfo {
|
||||
week: number
|
||||
day: number
|
||||
classTime: number
|
||||
classnames: string[]
|
||||
campus: string
|
||||
build: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
startStamp: number
|
||||
endStamp: number
|
||||
classroom: string
|
||||
course: string
|
||||
teacher: string
|
||||
id: string
|
||||
}
|
||||
/**
|
||||
* 获取校区班级
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export const getClassNamesAPI = async (id: string) => {
|
||||
return http<ClassResult[]>({
|
||||
method: 'GET',
|
||||
url: `/getCampusClass/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取班级课程
|
||||
* @param 班级ID
|
||||
* @returns
|
||||
*/
|
||||
export const getCourseAPI = async (id: string) => {
|
||||
return http<CourseInfo[]>({
|
||||
method: 'GET',
|
||||
url: `/getCourse/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
export interface GetClassrommsParams {
|
||||
campus_id: string
|
||||
build_id?: string
|
||||
startStamp: number
|
||||
endStamp: number
|
||||
}
|
||||
|
||||
export const getEmptyClassroomAPI = async (params: GetClassrommsParams) => {
|
||||
return http<ClassroomInfo[]>({
|
||||
method: 'GET',
|
||||
url: '/getEmptyClassroom',
|
||||
data: { ...params },
|
||||
})
|
||||
}
|
||||
|
||||
interface TermSetting {
|
||||
startDate: string
|
||||
totalWeeks: number
|
||||
endDate: string
|
||||
timeSetting: TimeIndex[]
|
||||
}
|
||||
|
||||
export const getTermSettingAPI = async () => {
|
||||
return http<TermSetting>({
|
||||
method: 'GET',
|
||||
url: '/getTermSetting',
|
||||
})
|
||||
}
|
||||
|
||||
export interface CampusInfo {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const getCampusListAPI = async () => {
|
||||
return http<CampusInfo[]>({
|
||||
method: 'GET',
|
||||
url: '/getCampusList',
|
||||
})
|
||||
}
|
||||
|
||||
export interface BuildInfo {
|
||||
id: string
|
||||
name: string
|
||||
campus: string
|
||||
campus_id: string
|
||||
}
|
||||
|
||||
export const getBuildListALLAPI = async () => {
|
||||
return http<BuildInfo[]>({
|
||||
method: 'GET',
|
||||
url: '/getBuildList',
|
||||
})
|
||||
}
|
||||
|
||||
export const getBuildListAPI = async (id: string) => {
|
||||
return http<BuildInfo[]>({
|
||||
method: 'GET',
|
||||
url: `/getBuilds/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
export const getClassroomCourseListAPI = async (id: string, week: number) => {
|
||||
return http<ClassroomCourseInfo[]>({
|
||||
method: 'GET',
|
||||
url: '/getClassroomCourses',
|
||||
data: { classroom_id: id, week },
|
||||
})
|
||||
}
|
||||
|
||||
export const getClassroomListAPI = async (id: string) => {
|
||||
return http<ClassroomInfo[]>({
|
||||
method: 'GET',
|
||||
url: `/getClassroomList/${id}`,
|
||||
})
|
||||
}
|
||||
12
src/shims.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// with vite-plugin-md, markdowns can be treat as Vue components
|
||||
declare module "*.md" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
BIN
src/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/static/tabs/classroom_course_default.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/static/tabs/classroom_course_selected.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/static/tabs/classroom_default.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/tabs/classroom_selected.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/tabs/course_default.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/static/tabs/course_selected.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/static/tabs/home_default.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/static/tabs/home_selected.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/static/tabs/setting_default.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
src/static/tabs/setting_selected.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
16
src/stores/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createPinia } from 'pinia'
|
||||
import persist from 'pinia-plugin-persistedstate'
|
||||
|
||||
// 创建 pinia 实例
|
||||
const pinia = createPinia()
|
||||
// 使用持久化存储插件
|
||||
pinia.use(persist)
|
||||
|
||||
// 默认导出,给 main.ts 使用
|
||||
export default pinia
|
||||
|
||||
// 模块统一导出
|
||||
export * from './modules/app'
|
||||
export * from './modules/course'
|
||||
export * from './modules/page'
|
||||
|
||||
233
src/stores/modules/app.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type {
|
||||
CampusInfo,
|
||||
ClassName,
|
||||
ClassroomInfo,
|
||||
GetClassrommsParams,
|
||||
} from '~/services/course'
|
||||
import {
|
||||
getCampusListAPI,
|
||||
getClassNamesAPI,
|
||||
getEmptyClassroomAPI,
|
||||
} from '~/services/course'
|
||||
interface MenuButtonBoundingClientRect {
|
||||
width: number
|
||||
height: number
|
||||
top: number
|
||||
left: number
|
||||
right: number
|
||||
bottom: number
|
||||
}
|
||||
export interface TimeIndex {
|
||||
index: number
|
||||
start: string
|
||||
end: string
|
||||
time: number
|
||||
}
|
||||
|
||||
export interface EmptyClass {
|
||||
date: string
|
||||
timeRange: number[]
|
||||
data: ClassroomInfo[]
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore(
|
||||
'app',
|
||||
() => {
|
||||
const darkMode = ref(false)
|
||||
const statusBarHeight = ref(0)
|
||||
const classroomId = ref('')
|
||||
const buildId = ref('')
|
||||
const menuButtonBounding = ref<MenuButtonBoundingClientRect>()
|
||||
const customBarHeight = computed(() =>
|
||||
!menuButtonBounding.value
|
||||
? 0
|
||||
: menuButtonBounding.value.bottom
|
||||
+ menuButtonBounding.value.top
|
||||
- statusBarHeight.value,
|
||||
)
|
||||
const startDate = ref<string | Date>('')
|
||||
const endDate = ref<string>('')
|
||||
const timeSetting = ref<TimeIndex[]>([
|
||||
{
|
||||
index: 1,
|
||||
start: '08:00',
|
||||
end: '08:45',
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
start: '08:50',
|
||||
end: '09:35',
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
start: '09:55',
|
||||
end: '10:40',
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
start: '10:45',
|
||||
end: '11:30',
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
start: '11:35',
|
||||
end: '12:20',
|
||||
time: 0,
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
start: '14:00',
|
||||
end: '14:45',
|
||||
time: 1,
|
||||
},
|
||||
{
|
||||
index: 7,
|
||||
start: '14:50',
|
||||
end: '15:35',
|
||||
time: 1,
|
||||
},
|
||||
{
|
||||
index: 8,
|
||||
start: '15:55',
|
||||
end: '16:40',
|
||||
time: 1,
|
||||
},
|
||||
{
|
||||
index: 9,
|
||||
start: '16:45',
|
||||
end: '17:30',
|
||||
time: 1,
|
||||
},
|
||||
{
|
||||
index: 10,
|
||||
start: '19:00',
|
||||
end: '19:45',
|
||||
time: 2,
|
||||
},
|
||||
{
|
||||
index: 11,
|
||||
start: '19:50',
|
||||
end: '20:35',
|
||||
time: 2,
|
||||
},
|
||||
{
|
||||
index: 12,
|
||||
start: '20:40',
|
||||
end: '21:25',
|
||||
time: 2,
|
||||
},
|
||||
])
|
||||
const totalWeeks = ref<number>(0)
|
||||
const classinfo = ref<ClassName>({
|
||||
text: '',
|
||||
value: '',
|
||||
disable: false,
|
||||
})
|
||||
const campusId = ref<string>('')
|
||||
const campusList = ref<CampusInfo[]>([])
|
||||
const classNames = ref<ClassName[]>([])
|
||||
const emptyClassroom = ref<EmptyClass>({
|
||||
date: '',
|
||||
data: [],
|
||||
timeRange: [1, 2],
|
||||
})
|
||||
|
||||
const getClassNames = async (id: string) => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getClassNamesAPI(id)
|
||||
if (res.code === 200) {
|
||||
classNames.value = res.data.map((item) => {
|
||||
return {
|
||||
text: item.name,
|
||||
value: item.id,
|
||||
disable: false,
|
||||
}
|
||||
})
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
const getCampusList = async () => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getCampusListAPI()
|
||||
if (res.code === 200) {
|
||||
campusList.value = res.data
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
const getEmptyClassroom = async (
|
||||
params: GetClassrommsParams,
|
||||
date: string,
|
||||
timeRange: number[],
|
||||
) => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getEmptyClassroomAPI(params)
|
||||
if (res.code === 200) {
|
||||
emptyClassroom.value = {
|
||||
date,
|
||||
data: res.data,
|
||||
timeRange,
|
||||
}
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
return true
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return {
|
||||
darkMode,
|
||||
startDate,
|
||||
endDate,
|
||||
timeSetting,
|
||||
totalWeeks,
|
||||
classNames,
|
||||
campusList,
|
||||
campusId,
|
||||
buildId,
|
||||
classinfo,
|
||||
classroomId,
|
||||
emptyClassroom,
|
||||
statusBarHeight,
|
||||
customBarHeight,
|
||||
menuButtonBounding,
|
||||
getClassNames,
|
||||
getCampusList,
|
||||
getEmptyClassroom,
|
||||
}
|
||||
},
|
||||
{
|
||||
// 配置持久化
|
||||
persist: {
|
||||
// 调整为兼容多端的API
|
||||
storage: {
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value)
|
||||
},
|
||||
getItem(key) {
|
||||
return uni.getStorageSync(key)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Need to be used outside the setup
|
||||
// export function useAppStoreWidthOut() {
|
||||
// return useAppStore(pinia)
|
||||
// }
|
||||
340
src/stores/modules/course.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { getCourseAPI } from '~/services/course'
|
||||
// import { getCourseAPI } from '~/services/course'
|
||||
// import { useAppStore } from '~/stores/modules/app'
|
||||
// const { startDate, totalWeeks } = storeToRefs(useAppStore())
|
||||
export interface CourseModel {
|
||||
id: string
|
||||
title: string
|
||||
location: string
|
||||
teacher?: string
|
||||
start: number
|
||||
duration: number
|
||||
// [1-7]
|
||||
week: number
|
||||
// [[1-20]]
|
||||
weeks: number[]
|
||||
color?: string
|
||||
}
|
||||
|
||||
export interface ClassName {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const weekTitle = [
|
||||
'周一',
|
||||
'周二',
|
||||
'周三',
|
||||
'周四',
|
||||
'周五',
|
||||
'周六',
|
||||
'周日',
|
||||
]
|
||||
|
||||
const colorMap = new Map<string, string>()
|
||||
|
||||
// @unocss-include
|
||||
export const colorList = [
|
||||
[
|
||||
'#FFDC72',
|
||||
'#CE7CF4',
|
||||
'#FF7171',
|
||||
'#66CC99',
|
||||
'#FF9966',
|
||||
'#66CCCC',
|
||||
'#6699CC',
|
||||
'#99CC99',
|
||||
'#669966',
|
||||
'#66CCFF',
|
||||
'#99CC66',
|
||||
'#FF9999',
|
||||
'#81CC74',
|
||||
],
|
||||
[
|
||||
'#99CCFF',
|
||||
'#FFCC99',
|
||||
'#CCCCFF',
|
||||
'#99CCCC',
|
||||
'#A1D699',
|
||||
'#7397db',
|
||||
'#ff9983',
|
||||
'#87D7EB',
|
||||
'#99CC99',
|
||||
],
|
||||
]
|
||||
|
||||
const conflictCourseMap = new Map<CourseModel, CourseModel[]>()
|
||||
|
||||
export const useCourseStore = defineStore(
|
||||
'course',
|
||||
() => {
|
||||
const isStart = ref<boolean>(false)
|
||||
const courseList = ref<CourseModel[]>([])
|
||||
const currentMonth = ref<number>(0)
|
||||
const originalWeekIndex = ref<number>(0)
|
||||
const startDate = ref<Date | string>('')
|
||||
const totalWeeks = ref<number>(0)
|
||||
const currentWeekIndex = ref<number>(0)
|
||||
const colorArrayIndex = ref<number>(0)
|
||||
/**
|
||||
* set start date
|
||||
* @param someDate the start date of the semester
|
||||
*/
|
||||
function setStartDay() {
|
||||
const start = new Date(startDate.value)
|
||||
const days = new Date().getTime() - start.getTime()
|
||||
isStart.value = days > 0
|
||||
const week = Math.floor(days / (1000 * 60 * 60 * 24 * 7))
|
||||
originalWeekIndex.value = week < 0 ? 0 : week
|
||||
setCurrentWeekIndex(originalWeekIndex.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* change current week index
|
||||
* @param weekIndex the new week index
|
||||
*/
|
||||
function setCurrentWeekIndex(weekIndex: number) {
|
||||
conflictCourseMap.clear()
|
||||
currentWeekIndex.value = weekIndex
|
||||
// change current month
|
||||
const someDate = new Date(startDate.value)
|
||||
someDate.setDate(someDate.getDate() + weekIndex * 7)
|
||||
currentMonth.value = someDate.getMonth() + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* init course list
|
||||
* @param newCourseList new course list
|
||||
*/
|
||||
function setCourseList(newCourseList: CourseModel[]) {
|
||||
conflictCourseMap.clear()
|
||||
// sort by week and start
|
||||
courseList.value = newCourseList.sort(
|
||||
(a, b) => a.week - b.week || a.start - b.start,
|
||||
)
|
||||
resetCourseBgColor()
|
||||
}
|
||||
|
||||
// current week course list
|
||||
const weekCourseList = computed(() => {
|
||||
if (courseList.value) {
|
||||
return courseList.value.filter(item =>
|
||||
item.weeks.includes(currentWeekIndex.value + 1),
|
||||
)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
// data for course action
|
||||
const parsedCourseList = computed(() => {
|
||||
// init a course array
|
||||
const parsedCourseList = Array.from({ length: totalWeeks.value }, () =>
|
||||
Array.from({ length: 7 }, () => Array.from({ length: 6 }, () => 0)),
|
||||
)
|
||||
if (courseList.value) {
|
||||
// process course list
|
||||
for (const courseItem of courseList.value) {
|
||||
const { start, duration, week, weeks } = courseItem
|
||||
for (const w of weeks) {
|
||||
const dayCourseList = parsedCourseList[w - 1][week - 1]
|
||||
dayCourseList[Math.floor(start / 2)]++
|
||||
// some courses may last more than 2 times
|
||||
if (duration > 2)
|
||||
dayCourseList[Math.floor(start / 2 + 1)]++
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsedCourseList
|
||||
})
|
||||
|
||||
// current week date list
|
||||
const currentWeekDayArray = computed(() => {
|
||||
const weekIndex = currentWeekIndex.value
|
||||
const someDate = new Date(startDate.value)
|
||||
someDate.setDate(someDate.getDate() - 1 + weekIndex * 7)
|
||||
|
||||
// Helper function to format date
|
||||
const formatDate = (date) => {
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
return `${month}/${day}`
|
||||
}
|
||||
|
||||
// Generate the array of days for the week
|
||||
const dayArray = Array.from({ length: 7 }, () => {
|
||||
someDate.setDate(someDate.getDate() + 1)
|
||||
return formatDate(someDate)
|
||||
})
|
||||
|
||||
return dayArray
|
||||
})
|
||||
|
||||
/**
|
||||
* list of course for a certain course item time
|
||||
* @param courseItem the course item
|
||||
*/
|
||||
function getConflictCourse(courseItem: CourseModel): CourseModel[] {
|
||||
if (!courseItem)
|
||||
return []
|
||||
const { week, start } = courseItem
|
||||
return courseList.value.filter((item) => {
|
||||
return (
|
||||
item.weeks.includes(currentWeekIndex.value + 1)
|
||||
&& item.week === week
|
||||
&& item.start === start
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* list of course for a certain course item time with map
|
||||
* @param courseItem the course item
|
||||
*/
|
||||
function hasConflictCourseByMap(courseItem: CourseModel): CourseModel[] {
|
||||
if (!conflictCourseMap.has(courseItem))
|
||||
conflictCourseMap.set(courseItem, getConflictCourse(courseItem))
|
||||
return conflictCourseMap.get(courseItem) || []
|
||||
}
|
||||
|
||||
/**
|
||||
* reset course bg color
|
||||
*/
|
||||
function resetCourseBgColor() {
|
||||
colorMap.clear()
|
||||
if (courseList.value) {
|
||||
courseList.value.map(courseItem =>
|
||||
Object.assign(courseItem, { color: getCourseColor(courseItem) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get course item color
|
||||
* @param courseItem course item
|
||||
* @returns course color
|
||||
*/
|
||||
function getCourseColor(courseItem: CourseModel): string {
|
||||
const colorArray = colorList[colorArrayIndex.value]
|
||||
const { title } = courseItem
|
||||
if (!colorMap.has(title))
|
||||
colorMap.set(title, colorArray[colorMap.size % colorArray.length])
|
||||
return colorMap.get(title) || 'bg-white'
|
||||
}
|
||||
|
||||
watch(
|
||||
() => colorArrayIndex.value,
|
||||
() => resetCourseBgColor(),
|
||||
)
|
||||
|
||||
/**
|
||||
* set a course to top when there have more than one course in the same time
|
||||
* @param courseItem course item
|
||||
*/
|
||||
function setCourseItemTop(courseItem: CourseModel) {
|
||||
deleteCourseItem(courseItem)
|
||||
courseList.value.unshift(courseItem)
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a course
|
||||
* @param courseItem course item
|
||||
*/
|
||||
function deleteCourseItem(courseItem: CourseModel) {
|
||||
conflictCourseMap.clear()
|
||||
const { id, week, start } = courseItem
|
||||
for (let i = 0; i < courseList.value.length; i++) {
|
||||
const item = courseList.value[i]
|
||||
if (item.id === id && item.week === week && item.start === start)
|
||||
courseList.value.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a course by title
|
||||
* @param courseTitle course title
|
||||
*/
|
||||
function deleteCourseItemById(id: string) {
|
||||
conflictCourseMap.clear()
|
||||
for (let i = 0; i < courseList.value.length; i++) {
|
||||
const item = courseList.value[i]
|
||||
if (item.id === id)
|
||||
courseList.value.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const getCourseList = async (id: string) => {
|
||||
uni.showLoading({ title: '加载中~' })
|
||||
const res = await getCourseAPI(id)
|
||||
if (res.code === 200) {
|
||||
const courses: CourseModel[] = res.data.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.course,
|
||||
location: item.classroom,
|
||||
teacher: item.teacher,
|
||||
start: item.time[0],
|
||||
duration: item.time.length,
|
||||
week: item.day,
|
||||
weeks: item.weeks,
|
||||
}
|
||||
})
|
||||
setCourseList(courses)
|
||||
courseList.value = courses
|
||||
uni.hideLoading()
|
||||
uni.showToast({ icon: 'success', title: '加载成功~' })
|
||||
}
|
||||
else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
const setCourseSetting = (start: string, total: number) => {
|
||||
startDate.value = start
|
||||
totalWeeks.value = total
|
||||
}
|
||||
return {
|
||||
isStart,
|
||||
startDate,
|
||||
currentMonth,
|
||||
courseList,
|
||||
totalWeeks,
|
||||
setCourseList,
|
||||
weekCourseList,
|
||||
parsedCourseList,
|
||||
originalWeekIndex,
|
||||
currentWeekIndex,
|
||||
currentWeekDayArray,
|
||||
colorArrayIndex,
|
||||
setStartDay,
|
||||
setCurrentWeekIndex,
|
||||
getConflictCourse,
|
||||
hasConflictCourseByMap,
|
||||
setCourseItemTop,
|
||||
deleteCourseItem,
|
||||
deleteCourseItemById,
|
||||
getCourseList,
|
||||
setCourseSetting,
|
||||
}
|
||||
},
|
||||
{
|
||||
// 配置持久化
|
||||
persist: {
|
||||
// 调整为兼容多端的API
|
||||
storage: {
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value)
|
||||
},
|
||||
getItem(key) {
|
||||
return uni.getStorageSync(key)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Need to be used outside the setup
|
||||
// export function useCourseStoreWidthOut() {
|
||||
// return useCourseStore(pinia)
|
||||
// }
|
||||
86
src/stores/modules/page.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { UNotifyOptions } from '~/components/UnoUI/UNotify/types'
|
||||
import type { UToastOptions } from '~/components/UnoUI/UToast/types'
|
||||
|
||||
interface PageConfig {
|
||||
showNavBar?: boolean
|
||||
showBackAction?: boolean
|
||||
showCustomAction?: boolean
|
||||
pageTitle?: string
|
||||
}
|
||||
|
||||
export const usePageStore = defineStore(
|
||||
'page',
|
||||
() => {
|
||||
const showNavBar = ref(true)
|
||||
const showBackAction = ref(false)
|
||||
const showCustomAction = ref(false)
|
||||
const pageTitle = ref('')
|
||||
const notifyRef = ref<{
|
||||
handleShowNotify: (options: UNotifyOptions) => {}
|
||||
}>()
|
||||
const toastRef = ref<{ handleShowToast: (options: UToastOptions) => {} }>()
|
||||
|
||||
const setPageConfig = (config: PageConfig) => {
|
||||
const {
|
||||
showNavBar: _showNavBar = true,
|
||||
showBackAction: _showBackAction = false,
|
||||
showCustomAction: _showCustomAction = false,
|
||||
pageTitle: _pageTitle = '',
|
||||
} = config
|
||||
|
||||
showNavBar.value = _showNavBar
|
||||
showBackAction.value = _showBackAction
|
||||
showCustomAction.value = _showCustomAction
|
||||
pageTitle.value = _pageTitle
|
||||
}
|
||||
|
||||
const showNotify = (options: UNotifyOptions) =>
|
||||
notifyRef.value!.handleShowNotify(options)
|
||||
|
||||
const showToast = (options: UToastOptions) =>
|
||||
toastRef.value!.handleShowToast(options)
|
||||
|
||||
const pageReset = () => {
|
||||
showNavBar.value = true
|
||||
showBackAction.value = false
|
||||
showCustomAction.value = false
|
||||
pageTitle.value = ''
|
||||
notifyRef.value = undefined
|
||||
toastRef.value = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
setPageConfig,
|
||||
showNavBar,
|
||||
pageTitle,
|
||||
showBackAction,
|
||||
showCustomAction,
|
||||
notifyRef,
|
||||
toastRef,
|
||||
showNotify,
|
||||
showToast,
|
||||
pageReset,
|
||||
}
|
||||
},
|
||||
{
|
||||
// 配置持久化
|
||||
persist: {
|
||||
// 调整为兼容多端的API
|
||||
storage: {
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value)
|
||||
},
|
||||
getItem(key) {
|
||||
return uni.getStorageSync(key)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Need to be used outside the setup
|
||||
// export function usePageStoreWidthOut() {
|
||||
// return usePageStore(pinia)
|
||||
// }
|
||||
22
src/theme.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"dark": {
|
||||
"bgColor": "#222222",
|
||||
"bgColorBottom": "#222222",
|
||||
"bgColorTop": "#222222",
|
||||
"bgTxtStyle": "light",
|
||||
"navBgColor": "#222222",
|
||||
"navTxtStyle": "white",
|
||||
"tabBgColor": "#222222",
|
||||
"tabBorderStyle": "white"
|
||||
},
|
||||
"light": {
|
||||
"bgColor": "#F3F4F6",
|
||||
"bgColorBottom": "#F3F4F6",
|
||||
"bgColorTop": "#F3F4F6",
|
||||
"bgTxtStyle": "dark",
|
||||
"navBgColor": "#F3F4F6",
|
||||
"navTxtStyle": "white",
|
||||
"tabBgColor": "#F3F4F6",
|
||||
"tabBorderStyle": "black"
|
||||
}
|
||||
}
|
||||
76
src/uni.scss
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:24rpx;
|
||||
$uni-font-size-base:28rpx;
|
||||
$uni-font-size-lg:32rpx;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:40rpx;
|
||||
$uni-img-size-base:52rpx;
|
||||
$uni-img-size-lg:80rpx;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 4rpx;
|
||||
$uni-border-radius-base: 6rpx;
|
||||
$uni-border-radius-lg: 12rpx;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 10px;
|
||||
$uni-spacing-row-base: 20rpx;
|
||||
$uni-spacing-row-lg: 30rpx;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 8rpx;
|
||||
$uni-spacing-col-base: 16rpx;
|
||||
$uni-spacing-col-lg: 24rpx;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:40rpx;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:36rpx;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:30rpx;
|
||||
6
src/uni_modules/uni-config-center/changelog.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.0.3(2022-11-11)
|
||||
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
|
||||
## 0.0.2(2021-04-16)
|
||||
- 修改插件package信息
|
||||
## 0.0.1(2021-03-15)
|
||||
- 初始化项目
|
||||
81
src/uni_modules/uni-config-center/package.json
Normal file
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"id": "uni-config-center",
|
||||
"displayName": "uni-config-center",
|
||||
"version": "0.0.3",
|
||||
"description": "uniCloud 配置中心",
|
||||
"keywords": [
|
||||
"配置",
|
||||
"配置中心"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "unicloud-template-function"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../../scripts/dist"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
src/uni_modules/uni-config-center/readme.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 为什么使用uni-config-center
|
||||
|
||||
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
|
||||
|
||||
```bash
|
||||
cloudfunctions
|
||||
└─────common 公共模块
|
||||
├─plugin-a // 插件A对应的目录
|
||||
│ ├─index.js
|
||||
│ ├─config.json // plugin-a对应的配置文件
|
||||
│ └─other-file.cert // plugin-a依赖的其他文件
|
||||
└─plugin-b // plugin-b对应的目录
|
||||
├─index.js
|
||||
└─config.json // plugin-b对应的配置文件
|
||||
```
|
||||
|
||||
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
|
||||
|
||||
uni-config-center就是用了统一管理这些配置文件的,使用uni-config-center后的目录结构如下
|
||||
|
||||
```bash
|
||||
cloudfunctions
|
||||
└─────common 公共模块
|
||||
├─plugin-a // 插件A对应的目录
|
||||
│ └─index.js
|
||||
├─plugin-b // plugin-b对应的目录
|
||||
│ └─index.js
|
||||
└─uni-config-center
|
||||
├─index.js // config-center入口文件
|
||||
├─plugin-a
|
||||
│ ├─config.json // plugin-a对应的配置文件
|
||||
│ └─other-file.cert // plugin-a依赖的其他文件
|
||||
└─plugin-b
|
||||
└─config.json // plugin-b对应的配置文件
|
||||
```
|
||||
|
||||
使用uni-config-center后的优势
|
||||
|
||||
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
|
||||
- 支持对config.json设置schema,插件使用者在HBuilderX内编写config.json文件时会有更好的提示(后续HBuilderX会提供支持)
|
||||
|
||||
# 用法
|
||||
|
||||
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖,请参考:[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
|
||||
|
||||
```js
|
||||
const createConfig = require('uni-config-center')
|
||||
|
||||
const uniIdConfig = createConfig({
|
||||
pluginId: 'uni-id', // 插件id
|
||||
defaultConfig: { // 默认配置
|
||||
tokenExpiresIn: 7200,
|
||||
tokenExpiresThreshold: 600,
|
||||
},
|
||||
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
|
||||
// defaudltConfig 默认配置
|
||||
// userConfig 用户配置
|
||||
return Object.assign(defaultConfig, userConfig)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 以如下配置为例
|
||||
// {
|
||||
// "tokenExpiresIn": 7200,
|
||||
// "passwordErrorLimit": 6,
|
||||
// "bindTokenToDevice": false,
|
||||
// "passwordErrorRetryTime": 3600,
|
||||
// "app-plus": {
|
||||
// "tokenExpiresIn": 2592000
|
||||
// },
|
||||
// "service": {
|
||||
// "sms": {
|
||||
// "codeExpiresIn": 300
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// 获取配置
|
||||
uniIdConfig.config() // 获取全部配置,注意:uni-config-center内不存在对应插件目录时会返回空对象
|
||||
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置,返回:7200
|
||||
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置,返回:300
|
||||
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置,如果不存在则取传入的默认值,返回:600
|
||||
|
||||
// 获取文件绝对路径
|
||||
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
|
||||
|
||||
// 引用文件(require)
|
||||
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined,文件内有其他错误导致require失败时会抛出错误。
|
||||
|
||||
// 判断是否包含某文件
|
||||
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件,true: 文件存在,false: 文件不存在
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "uni-config-center",
|
||||
"version": "0.0.3",
|
||||
"description": "配置中心",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"author": "DCloud",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
36
src/uni_modules/uni-id-common/changelog.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## 1.0.18(2024-07-08)
|
||||
- checkToken时如果传入的token为空则返回uni-id-check-token-failed错误码以便uniIdRouter能正常跳转
|
||||
## 1.0.17(2024-04-26)
|
||||
- 兼容uni-app-x对客户端uniPlatform的调整(uni-app-x内uniPlatform区分app-android、app-ios)
|
||||
## 1.0.16(2023-04-25)
|
||||
- 新增maxTokenLength配置,用于限制数据库用户记录token数组的最大长度
|
||||
## 1.0.15(2023-04-06)
|
||||
- 修复部分语言国际化出错的Bug
|
||||
## 1.0.14(2023-03-07)
|
||||
- 修复 admin用户包含其他角色时未包含在token的Bug
|
||||
## 1.0.13(2022-07-21)
|
||||
- 修复 创建token时未传角色权限信息生成的token不正确的bug
|
||||
## 1.0.12(2022-07-15)
|
||||
- 提升与旧版本uni-id的兼容性(补充读取配置文件时回退平台app-plus、h5),但是仍推荐使用新平台名进行配置(app、web)
|
||||
## 1.0.11(2022-07-14)
|
||||
- 修复 部分情况下报`read property 'reduce' of undefined`的错误
|
||||
## 1.0.10(2022-07-11)
|
||||
- 将token存储在用户表的token字段内,与旧版本uni-id保持一致
|
||||
## 1.0.9(2022-07-01)
|
||||
- checkToken兼容token内未缓存角色权限的情况,此时将查库获取角色权限
|
||||
## 1.0.8(2022-07-01)
|
||||
- 修复clientDB默认依赖时部分情况下获取不到uni-id配置的Bug
|
||||
## 1.0.7(2022-06-30)
|
||||
- 修复config文件不合法时未抛出具体错误的Bug
|
||||
## 1.0.6(2022-06-28)
|
||||
- 移除插件内的数据表schema
|
||||
## 1.0.5(2022-06-27)
|
||||
- 修复使用多应用配置时报`Cannot read property 'appId' of undefined`的Bug
|
||||
## 1.0.4(2022-06-27)
|
||||
- 修复使用自定义token内容功能报错的Bug [详情](https://ask.dcloud.net.cn/question/147945)
|
||||
## 1.0.2(2022-06-23)
|
||||
- 对齐旧版本uni-id默认配置
|
||||
## 1.0.1(2022-06-22)
|
||||
- 补充对uni-config-center的依赖
|
||||
## 1.0.0(2022-06-21)
|
||||
- 提供uni-id token创建、校验、刷新接口,简化旧版uni-id公共模块
|
||||
85
src/uni_modules/uni-id-common/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"id": "uni-id-common",
|
||||
"displayName": "uni-id-common",
|
||||
"version": "1.0.18",
|
||||
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
|
||||
"keywords": [
|
||||
"uni-id-common",
|
||||
"uniCloud",
|
||||
"token",
|
||||
"权限"
|
||||
],
|
||||
"repository": "https://gitcode.net/dcloud/uni-id-common",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "unicloud-template-function"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-config-center"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "u",
|
||||
"vue3": "u"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/uni_modules/uni-id-common/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# uni-id-common
|
||||
|
||||
文档请参考:[uni-id-common](https://uniapp.dcloud.net.cn/uniCloud/uni-id-common.html)
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "uni-id-common",
|
||||
"version": "1.0.18",
|
||||
"description": "uni-id token生成、校验、刷新",
|
||||
"main": "index.js",
|
||||
"homepage": "https:\/\/uniapp.dcloud.io\/uniCloud\/uni-id-common.html",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https:\/\/gitee.com\/dcloud\/uni-id-common.git"
|
||||
},
|
||||
"author": "DCloud",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
|
||||
},
|
||||
"origin-plugin-dev-name": "uni-id-common",
|
||||
"origin-plugin-version": "1.0.18",
|
||||
"plugin-dev-name": "uni-id-common",
|
||||
"plugin-version": "1.0.18"
|
||||
}
|
||||
36
tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": false,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictFunctionTypes": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"allowJs": false,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"paths": {
|
||||
"~/*": ["src/*"],
|
||||
"@build/*": ["build/*"]
|
||||
},
|
||||
"types": ["@dcloudio/types", "vite/client"]
|
||||
},
|
||||
"include": [
|
||||
"mock/*.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"types/*.d.ts",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"exclude": ["dist", "**/*.js", "node_modules"]
|
||||
}
|
||||
95
uno.config.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { Preset, SourceCodeTransformer } from 'unocss'
|
||||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetUno,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss'
|
||||
|
||||
import {
|
||||
presetApplet,
|
||||
presetRemRpx,
|
||||
transformerAttributify,
|
||||
} from 'unocss-applet'
|
||||
|
||||
const isApplet = process.env?.UNI_PLATFORM?.startsWith('mp-') ?? false
|
||||
const presets: Preset[] = []
|
||||
const transformers: SourceCodeTransformer[] = []
|
||||
|
||||
if (isApplet) {
|
||||
presets.push(presetApplet())
|
||||
presets.push(presetRemRpx())
|
||||
transformers.push(transformerAttributify({ ignoreAttributes: ['block'] }))
|
||||
}
|
||||
else {
|
||||
presets.push(presetUno())
|
||||
presets.push(presetRemRpx({ mode: 'rpx2rem' }))
|
||||
}
|
||||
|
||||
const courseColors = [
|
||||
'rose',
|
||||
'pink',
|
||||
'fuchsia',
|
||||
'purple',
|
||||
'violet',
|
||||
'indigo',
|
||||
'blue',
|
||||
'cyan',
|
||||
'teal',
|
||||
'emerald',
|
||||
'green',
|
||||
'lime',
|
||||
'yellow',
|
||||
'amber',
|
||||
'orange',
|
||||
'red',
|
||||
]
|
||||
|
||||
export default defineConfig({
|
||||
shortcuts: {
|
||||
'bg-base': 'bg-gray-100 dark:bg-dark',
|
||||
'bg-base-second': 'bg-white dark:bg-dark-100',
|
||||
'color-base': 'text-gray-700 dark:text-white/80',
|
||||
'color-base-second': 'text-gray-400 dark:text-gray-500/50',
|
||||
'border-base': 'border border-gray-200 dark:border-gray/50',
|
||||
'bg-primary': 'bg-light-blue-500 dark:bg-light-blue-600/80',
|
||||
},
|
||||
presets: [
|
||||
presetIcons({
|
||||
scale: 1.2,
|
||||
extraProperties: {
|
||||
'display': 'inline-block',
|
||||
'vertical-align': 'middle',
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* you can add `presetAttributify()` here to enable unocss attributify mode prompt
|
||||
* although preset is not working for applet, but will generate useless css
|
||||
*/
|
||||
presetAttributify(),
|
||||
...presets,
|
||||
],
|
||||
transformers: [
|
||||
transformerDirectives(),
|
||||
transformerVariantGroup(),
|
||||
...transformers,
|
||||
],
|
||||
rules: [
|
||||
[
|
||||
'p-safe',
|
||||
{
|
||||
padding:
|
||||
'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
|
||||
},
|
||||
],
|
||||
['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
|
||||
['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
|
||||
],
|
||||
safelist: [
|
||||
...courseColors.map(c => `bg-${c}`),
|
||||
...courseColors.map(c => `bg-${c}-3`),
|
||||
...courseColors.map(c => `text-${c}-5`),
|
||||
],
|
||||
})
|
||||
BIN
unpackage/res/icons/1024x1024.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
unpackage/res/icons/120x120.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
unpackage/res/icons/144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
unpackage/res/icons/152x152.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
unpackage/res/icons/167x167.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
unpackage/res/icons/180x180.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
unpackage/res/icons/192x192.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
unpackage/res/icons/20x20.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
unpackage/res/icons/29x29.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
unpackage/res/icons/40x40.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
unpackage/res/icons/58x58.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
unpackage/res/icons/60x60.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
unpackage/res/icons/72x72.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
unpackage/res/icons/76x76.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
unpackage/res/icons/80x80.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
unpackage/res/icons/87x87.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
unpackage/res/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
43
vite.config.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import path from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import uniModule from '@dcloudio/vite-plugin-uni'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
// @ts-expect-error missing types
|
||||
const Uni = uniModule.default || uniModule
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => {
|
||||
const UnoCss = await import('unocss/vite').then(i => i.default)
|
||||
return {
|
||||
root: process.cwd(),
|
||||
resolve: {
|
||||
alias: {
|
||||
'~/': `${path.resolve(__dirname, 'src')}/`,
|
||||
'react': 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
'preact': 'preact',
|
||||
'preact/compat': 'preact/compat',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
Uni(),
|
||||
|
||||
// https://github.com/antfu/unocss
|
||||
// see unocss.config.ts for config
|
||||
UnoCss(),
|
||||
|
||||
// https://github.com/antfu/unplugin-auto-import
|
||||
AutoImport({
|
||||
imports: ['vue', 'pinia', 'uni-app'],
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
dirs: ['src/composables', 'src/stores'],
|
||||
vueTemplate: true,
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ['preact', 'preact/compat'],
|
||||
},
|
||||
// 如果你需要更详细的调试输出,可以启用以下选项
|
||||
logLevel: 'info',
|
||||
}
|
||||
},
|
||||
)
|
||||