add:第一次提交
This commit is contained in:
commit
6460344c7a
4
.env
Normal file
4
.env
Normal file
@ -0,0 +1,4 @@
|
||||
VITE_HOST = https://pi.xuexiaole.com
|
||||
# VITE_HOST = http://192.168.0.103
|
||||
VITE_OSS_HOST = https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient
|
||||
VITE_WS_URL = wss://pi.xuexiaole.com/ws/device
|
3
.env.prod
Normal file
3
.env.prod
Normal file
@ -0,0 +1,3 @@
|
||||
VITE_HOST = https://test.pi.xuexiaole.com
|
||||
VITE_OSS_HOST = https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient
|
||||
VITE_WS_URL = wss://test.pi.xuexiaole.com/ws/device
|
3
.env.test
Normal file
3
.env.test
Normal file
@ -0,0 +1,3 @@
|
||||
VITE_HOST = https://test.pi.xuexiaole.com
|
||||
VITE_OSS_HOST = https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient
|
||||
VITE_WS_URL = wss://test.pi.xuexiaole.com/ws/device
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
src/uni_modules/**
|
54
.eslintrc.js
Normal file
54
.eslintrc.js
Normal file
@ -0,0 +1,54 @@
|
||||
module.exports = {
|
||||
extends: ['alloy', 'alloy/vue', 'alloy/typescript', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: {
|
||||
js: '@babel/eslint-parser',
|
||||
jsx: '@babel/eslint-parser',
|
||||
ts: '@typescript-eslint/parser',
|
||||
tsx: '@typescript-eslint/parser',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-assertions': 'off',
|
||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'no-return-assign': 'off',
|
||||
'guard-for-in': 'off',
|
||||
|
||||
'vue/no-setup-props-destructure': 'off',
|
||||
'vue/no-duplicate-attr-inheritance': 'off',
|
||||
'no-eq-null': 'off', // 允许 == 用于 null
|
||||
|
||||
'vue/v-on-event-hyphenation': 'off', // 关闭 vue 中 @ 使用短横线命名
|
||||
'no-duplicate-imports': 'off', // 使用ts-eslint的重复导入规则
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-duplicate-attributes': [
|
||||
'error',
|
||||
{
|
||||
allowCoexistClass: true, // 启用v-bind:class指令可以与普通class属性共存。默认值为true。
|
||||
allowCoexistStyle: true,
|
||||
},
|
||||
],
|
||||
// 圈复杂度 每个函数的最高圈复杂度
|
||||
complexity: [
|
||||
'error',
|
||||
{
|
||||
max: 12,
|
||||
},
|
||||
],
|
||||
},
|
||||
globals: {
|
||||
defineProps: 'readonly',
|
||||
defineEmits: 'readonly',
|
||||
defineExpose: 'readonly',
|
||||
withDefaults: 'readonly',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
};
|
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist*
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
dist*
|
16
.hbuilderx/launch.json
Normal file
16
.hbuilderx/launch.json
Normal file
@ -0,0 +1,16 @@
|
||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no -- commitlint --edit "$1"
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# npm run lint-staged
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
src/uni_modules/**
|
13
.prettierrc.js
Normal file
13
.prettierrc.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
bracketSpacing: true,
|
||||
bracketSameLine: false,
|
||||
arrowParens: 'avoid',
|
||||
vueIndentScriptAndStyle: false,
|
||||
endOfLine: 'lf',
|
||||
};
|
51
.vscode/settings.json
vendored
Normal file
51
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"editor.formatOnType": true,
|
||||
"editor.formatOnSave": true,
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.tabSize": 1,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"files.autoSave": "off",
|
||||
"git.confirmSync": false,
|
||||
"workbench.startupEditor": "newUntitledFile",
|
||||
"editor.suggestSelection": "first",
|
||||
"editor.acceptSuggestionOnCommitCharacter": false,
|
||||
"css.lint.propertyIgnoredDueToDisplay": "ignore",
|
||||
"editor.quickSuggestions": {
|
||||
"other": true,
|
||||
"comments": true,
|
||||
"strings": true
|
||||
},
|
||||
"files.associations": {
|
||||
"editor.snippetSuggestions": "top"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "never",
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"iconify.excludes": ["el"],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/uni_modules/uni-table/i18n",
|
||||
"src/uni_modules/wot-design-uni/locale",
|
||||
"src/uni_modules/wot-design-uni/locale/lang",
|
||||
"src/uni_modules/uni-calendar/components/uni-calendar/i18n",
|
||||
"src/uni_modules/uni-countdown/components/uni-countdown/i18n",
|
||||
"src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n",
|
||||
"src/uni_modules/uni-fav/components/uni-fav/i18n",
|
||||
"src/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n",
|
||||
"src/uni_modules/uni-load-more/components/uni-load-more/i18n",
|
||||
"src/uni_modules/uni-pagination/components/uni-pagination/i18n",
|
||||
"src/uni_modules/uni-popup/components/uni-popup/i18n",
|
||||
"src/uni_modules/uni-search-bar/components/uni-search-bar/i18n",
|
||||
"src/uni_modules/z-paging/components/z-paging/i18n"
|
||||
]
|
||||
}
|
BIN
client-dist.zip
Normal file
BIN
client-dist.zip
Normal file
Binary file not shown.
24
commitlint.config.js
Normal file
24
commitlint.config.js
Normal file
@ -0,0 +1,24 @@
|
||||
// build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
|
||||
|
||||
// ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle 等)的提交
|
||||
|
||||
// docs:文档更新
|
||||
|
||||
// feat:新增功能
|
||||
|
||||
// fix:bug 修复
|
||||
|
||||
// perf:性能优化
|
||||
|
||||
// refactor:重构代码(既没有新增功能,也没有修复 bug)
|
||||
|
||||
// style:不影响程序逻辑的代码修改(修改空白字符,补全缺失的分号等)
|
||||
|
||||
// test:新增测试用例或是更新现有测试
|
||||
|
||||
// revert:回滚某个更早之前的提交
|
||||
|
||||
// chore:不属于以上类型的其他类型(日常事务)
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
};
|
20
index.html
Normal file
20
index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var 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>
|
13983
package-lock.json
generated
Normal file
13983
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
package.json
Normal file
126
package.json
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"name": "xuexiaole-client",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev:app": "uni -p app",
|
||||
"dev:app-android": "uni -p app-android",
|
||||
"dev:app-ios": "uni -p app-ios",
|
||||
"dev:custom": "uni -p",
|
||||
"dev:h5": "uni -p h5-test",
|
||||
"dev:h5:ssr": "uni --ssr",
|
||||
"dev:mp-alipay": "uni -p mp-alipay",
|
||||
"dev:mp-baidu": "uni -p mp-baidu",
|
||||
"dev:mp-jd": "uni -p mp-jd",
|
||||
"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:app-android": "uni build -p app-android",
|
||||
"build:app-ios": "uni build -p app-ios",
|
||||
"build:custom": "uni build -p",
|
||||
"build:h5": "uni build -p h5-prod",
|
||||
"build-test:h5": "uni build -p h5-test",
|
||||
"build:h5:ssr": "uni build --ssr",
|
||||
"build:mp-alipay": "uni build -p mp-alipay",
|
||||
"build:mp-baidu": "uni build -p mp-baidu",
|
||||
"build:mp-jd": "uni build -p mp-jd",
|
||||
"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",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"prepare": "husky install",
|
||||
"lint": "eslint --fix",
|
||||
"lint-staged": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-components": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-h5": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-3081220230817001",
|
||||
"code-inspector-plugin": "^0.10.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"lint-staged": "^15.0.1",
|
||||
"pinia": "^2.0.36",
|
||||
"sass": "^1.72.0",
|
||||
"unplugin-vue-define-options": "^1.4.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"vue-qrcode-reader": "^5.5.7",
|
||||
"weixin-js-sdk": "^1.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/eslint-parser": "^7.22.15",
|
||||
"@commitlint/cli": "^17.8.0",
|
||||
"@commitlint/config-conventional": "^17.8.0",
|
||||
"@dcloudio/types": "^3.3.2",
|
||||
"@dcloudio/uni-automator": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-3081220230817001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-3081220230817001",
|
||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||
"@typescript-eslint/parser": "^6.8.0",
|
||||
"@vue/runtime-core": "^3.2.45",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-config-alloy": "^5.1.2",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"husky": "^8.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "4.1.4",
|
||||
"vue-eslint-parser": "^9.3.2",
|
||||
"vue-tsc": "^1.0.24"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{json,md,css,less}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{js,ts,jsx,tsx,vue}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"uni-app": {
|
||||
"scripts": {
|
||||
"h5-test": {
|
||||
"title": "h5-test",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5",
|
||||
"VITE_HOST": "https://test.pi.xuexiaole.com",
|
||||
"VITE_WS_URL": "wss://test.pi.xuexiaole.com/ws/device"
|
||||
}
|
||||
},
|
||||
"h5-prod": {
|
||||
"title": "h5-prod",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "h5",
|
||||
"VITE_HOST": "https://pi.xuexiaole.com",
|
||||
"VITE_WS_URL": "wss://pi.xuexiaole.com/ws/device"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10587
pnpm-lock.yaml
generated
Normal file
10587
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
shims-uni.d.ts
vendored
Normal file
8
shims-uni.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/// <reference types='@dcloudio/types' />
|
||||
import 'vue';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
|
||||
interface ComponentCustomOptions extends Hooks {}
|
||||
}
|
19
src/App.vue
Normal file
19
src/App.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, nextTick } from 'vue';
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch');
|
||||
});
|
||||
onShow(() => {
|
||||
console.log('App Show');
|
||||
});
|
||||
onHide(() => {
|
||||
console.log('App Hide');
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
56
src/api/global.ts
Normal file
56
src/api/global.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import $req from './request';
|
||||
|
||||
// 获取用户信息
|
||||
export const getUserInfo = () =>
|
||||
$req({
|
||||
url: '/sysUser/selectUser',
|
||||
});
|
||||
|
||||
// 获取客服信息
|
||||
export const getCsInfo = (id: string) =>
|
||||
$req({
|
||||
url: `/customerService/getInfoByUserId/${id}`,
|
||||
});
|
||||
|
||||
// 更新用户信息
|
||||
export const updateUserInfo = (data: anyObj) =>
|
||||
$req({
|
||||
url: '/sysUser/editPerfectMessage',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
|
||||
// 获取协议
|
||||
export const getAgreeInfo = (id: string, isChat = false) =>
|
||||
$req({
|
||||
url: '/agreement/' + id,
|
||||
headers: {
|
||||
Authorization: '',
|
||||
},
|
||||
});
|
||||
|
||||
// 获取字典
|
||||
export const getDict = () =>
|
||||
$req({
|
||||
url: '/sysDictType/tree',
|
||||
});
|
||||
|
||||
// 设备通知注册
|
||||
export const bindRegId = (data: anyObj) =>
|
||||
$req({
|
||||
url: '/sysUser/dealRegistrationId',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
|
||||
// 获取未读消息数量
|
||||
export const getUnreadNum = () => $req({ url: '/customerService/getUnreadNum' });
|
||||
|
||||
// 支付创建订单
|
||||
export const createInspectorPrepayOrder = (data: anyObj) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: '/orderManagement/createInspectorPrepayOrder',
|
||||
data,
|
||||
});
|
||||
};
|
15
src/api/index.ts
Normal file
15
src/api/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export * from './global';
|
||||
export * from './login';
|
||||
export * from './modules/warranty';
|
||||
export * from './modules/awardManage';
|
||||
export * from './modules/taskManage';
|
||||
export * from './modules/bindDevice';
|
||||
export * from './modules/parent';
|
||||
export * from './modules/mySupervisionService';
|
||||
export * from './modules/bindDevice';
|
||||
export * from './modules/mine';
|
||||
export * from './modules/academicReport';
|
||||
export * from './modules/deviceScreenshotRecord';
|
||||
export * from './modules/inviteBind';
|
||||
export * from './modules/home';
|
||||
export * from './modules/applicationManagement';
|
36
src/api/inspector/mine.ts
Normal file
36
src/api/inspector/mine.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 获取督学师信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getUserInfo = (data = {}) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: '/phone/inspectorTeacher/myInfo',
|
||||
data,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查询用户角色
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const queryUserRoles = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/sysUser/getAdminTypeByPhone',
|
||||
params,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 切换用户角色
|
||||
*/
|
||||
export const switchRole = (adminType: number) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/sysUser/changeAdminType',
|
||||
params: {
|
||||
adminType,
|
||||
},
|
||||
});
|
||||
};
|
111
src/api/inspector/student-detail.ts
Normal file
111
src/api/inspector/student-detail.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 获取学员详情
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getStudentDetail = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/phone/inspectorTeacher/getStuVoByOrderIdAndCourseDate',
|
||||
params,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员督学报告列表
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getUserReportPage = (data = {}) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: '/phone/inspectorTeacher/getUserReportPage',
|
||||
data,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员督学报告列表
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getUserInspectorCourseReportInfo = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/phone/inspectorTeacher/userInspectorCourseReportInfo',
|
||||
params,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员学习计划列表
|
||||
* @param data
|
||||
*/
|
||||
export const queryStudyPlanList = (data = {}) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: '/phone/inspectorTeacher/queryStuPlanPage',
|
||||
data,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 删除学员学习计划
|
||||
* @param planId 学习计划id
|
||||
*/
|
||||
export const deletePlanRequest = (planId: string) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/deletePlanById?planId=${planId}`,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学科列表
|
||||
* @param gradeId 年级id
|
||||
*/
|
||||
export const getSubjectListByGrade = (gradeId: string) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: `/subject/list/${gradeId}`,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员学习时长统计
|
||||
* @param params
|
||||
*/
|
||||
export const queryStudyDuration = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/duration',
|
||||
params,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员视频学习统计
|
||||
* @param params
|
||||
*/
|
||||
export const queryVideoStudy = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/video',
|
||||
params,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取学员知识点学习统计
|
||||
* @param params
|
||||
*/
|
||||
export const queryStudyKnowledge = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/knowledge',
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取学员错题学习统计
|
||||
* @param params
|
||||
*/
|
||||
export const queryStudentWrongQuestion = (params = {}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/error',
|
||||
params,
|
||||
});
|
||||
};
|
27
src/api/inspector/student.ts
Normal file
27
src/api/inspector/student.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 获取督学师课表时间
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getCourseTable = (data={}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/phone/inspectorTeacher/getCourseTableById',
|
||||
data,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查询学生列表
|
||||
* @param data
|
||||
*/
|
||||
export const queryStudentList = (params={}) => {
|
||||
return $req({
|
||||
method:'get',
|
||||
url:'/phone/inspectorTeacher/getMyStudent',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
88
src/api/login.ts
Normal file
88
src/api/login.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import $req from './request';
|
||||
/**
|
||||
* 手机号授权
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getBindPhoneTypeApi = data => {
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/xcx/login/getBindRelationList',
|
||||
data,
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 手机号获取用户身份
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getAdminTypeByPhoneApi = (params: anyObj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/sysUser/getAdminTypeByPhone',
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const sendCodeMessageApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/sms/sendMessage?phoneNumbers=${data}`,
|
||||
});
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const smsLoginApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/smsLogin',
|
||||
data,
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getUserInfoApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/sysUser/selectUser',
|
||||
});
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const updateUserInfoApi = (data: anyObj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/sysUser/updateInfo',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 授权用户绑定
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const bindAuthInfoApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/wechatPublic/bindAuthInfo',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 获取静默授权链接
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getAuthUrlApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/wechatPublic/getAuthUrl',
|
||||
});
|
||||
|
||||
// 退出登录
|
||||
export const logout = () =>
|
||||
$req({
|
||||
url: '/logout',
|
||||
});
|
60
src/api/modules/academicReport.ts
Normal file
60
src/api/modules/academicReport.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import $req from '../request';
|
||||
/**
|
||||
* 学情报告-学科目录
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const subjectApi = (gradeId: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subject/list/${gradeId}`,
|
||||
});
|
||||
/**
|
||||
* 学情报告-学习时长统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const studyTimeStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/duration',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 学情报告-视频学习统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const videoStudyStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/video',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 学情报告-知识图谱统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const knowledgeStudyStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/knowledge',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 学情报告-错题本统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const studyErrorStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/error',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 学情报告-英语语感统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const englishLanguageStatApi = (data: anyobj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/userSentenceLearn/queryStatListByTimeType',
|
||||
data,
|
||||
});
|
10
src/api/modules/applicationManagement.ts
Normal file
10
src/api/modules/applicationManagement.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import $req from '../request';
|
||||
/**
|
||||
* 应用管控
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const queryAppRecordApi = (serialNum: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/userAppRecord/queryAppRecord/${serialNum}`,
|
||||
});
|
49
src/api/modules/awardManage.ts
Normal file
49
src/api/modules/awardManage.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import $req from '../request';
|
||||
/**
|
||||
* 家长老师-奖励管理
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getRewardListApi = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userRewardMapping/list',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 家长老师-新增奖励
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const addRewardApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/userRewardMapping',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 家长老师-修改奖励
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const updateRewardApi = data =>
|
||||
$req({
|
||||
method: 'put',
|
||||
url: '/userRewardMapping',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 家长老师-删除奖励
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const deleteRewardApi = ids =>
|
||||
$req({
|
||||
method: 'delete',
|
||||
url: `/userRewardMapping/${ids}`,
|
||||
});
|
||||
/**
|
||||
* 家长老师-奖励详情
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getRewardDetailApi = id =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/userRewardMapping/${id}`,
|
||||
});
|
78
src/api/modules/bindDevice.ts
Normal file
78
src/api/modules/bindDevice.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 当前登录用户的绑定信息
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getCurInfo = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/curInfo',
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
* 家长绑定管理员
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const parentBindAdmin = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBind/admin',
|
||||
data,
|
||||
});
|
||||
|
||||
/**
|
||||
* 家长申请绑定
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const parentBindApply = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBind/apply',
|
||||
data,
|
||||
});
|
||||
|
||||
/**
|
||||
* -设备当前管理员信息
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const adminInfo = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/adminInfo',
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
* -家长申请绑定信息
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const applyInfo = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/apply/info',
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
* 管理员审批
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const adminApproval = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBind/admin/approval',
|
||||
data,
|
||||
});
|
||||
|
||||
/**
|
||||
* 时间管控保存
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const deviceTimeControl = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/deviceTimeControl',
|
||||
data,
|
||||
});
|
12
src/api/modules/deviceScreenshotRecord.ts
Normal file
12
src/api/modules/deviceScreenshotRecord.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 设备截屏记录表-查询列表
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const deviceScreenshotRecordList = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/deviceScreenshotRecord/list',
|
||||
params,
|
||||
});
|
22
src/api/modules/home.ts
Normal file
22
src/api/modules/home.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 设备屏幕查询
|
||||
* 查询
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getDeviceScreenControlApi = (simSerialNumber: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/deviceTimeControl/${simSerialNumber}`,
|
||||
});
|
||||
/**
|
||||
* 一键解锁屏
|
||||
* 解锁屏
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const lockDeviceScreenApi = (simSerialNumber: string) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/deviceTimeControl/update/lock/${simSerialNumber}`,
|
||||
});
|
197
src/api/modules/inspector.ts
Normal file
197
src/api/modules/inspector.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import $req from '../request';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { user } from '@/store';
|
||||
import { getCache } from '@/utils';
|
||||
// 课表日历
|
||||
export const getScheduleList = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/phone/inspectorTeacher/getCourseTableById',
|
||||
data,
|
||||
});
|
||||
|
||||
// 查询指定日期学生数据
|
||||
export const getCourseDate = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url:
|
||||
`/phone/inspectorTeacher/getCourseTabletStudentVoByTime` +
|
||||
`${data.date ? `?courseDate=${data.date}` : ''}`,
|
||||
// data,
|
||||
});
|
||||
|
||||
// 开始督学
|
||||
export const startInpector = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/startInspector?courseDate=${data.courseDate}`,
|
||||
});
|
||||
|
||||
// 结束督学
|
||||
export const endInpector = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/endInspector?courseDate=${data.courseDate}`,
|
||||
});
|
||||
|
||||
// 督学实况
|
||||
export const condition = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/inspector_real?courseDate=${data.courseDate}`,
|
||||
});
|
||||
|
||||
// 获取学员课表日历
|
||||
export const getCourseData = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/getStuCourseTable?userId=${data.id}`,
|
||||
// data,
|
||||
});
|
||||
|
||||
// 获取学科
|
||||
export const getSubject = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subject/list/${data.gradeId}`,
|
||||
});
|
||||
// 获取学科教材
|
||||
export const getBooks = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/userTextBook/list?gradeId=${data.gradeId}&subjectId=${data.subjectId}&userId=${data.userId}¤t=${data.current}`,
|
||||
});
|
||||
|
||||
// 获取教材章节
|
||||
export const getChaper = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subjectChapter/tree?textBookId=${data.textBookId}`,
|
||||
});
|
||||
// 获取教材章节知识点
|
||||
export const getKnowledge = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subjectKnowledge/tree?chapterId=${data.chapterId}`,
|
||||
});
|
||||
|
||||
// 发布计划
|
||||
export const publishPlan = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/publishPlan`,
|
||||
data,
|
||||
});
|
||||
// 获取学员计划
|
||||
export const getStuPlan = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/getStuPlanByDate?userId=${data.userId}&courseDate=${data.courseDate}`,
|
||||
});
|
||||
|
||||
// 获取督学师信息
|
||||
export const getUserInfo = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/getCurrentTeacher`,
|
||||
});
|
||||
// 文件上传
|
||||
export const uploadFile = async (file): Promise<any> => {
|
||||
const { token } = storeToRefs(user());
|
||||
|
||||
const mergerToken = token.value || getCache('token');
|
||||
|
||||
const data = await uni.uploadFile({
|
||||
url: `/api/main/sysFileInfo/tenUploadAll`, // ${import.meta.env.VITE_HOST}
|
||||
file: file,
|
||||
name: file.fileName,
|
||||
header: {
|
||||
// 'content-type': 'multipart/form-data',
|
||||
Authorization: mergerToken ? `Bearer ${mergerToken}` : token,
|
||||
},
|
||||
});
|
||||
return JSON.parse(data.data);
|
||||
};
|
||||
|
||||
// 完善督学师资料 /phone/inspectorTeacher/updateTeacherInfo
|
||||
export const updateTeacherInfo = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/phone/inspectorTeacher/updateTeacherInfo`,
|
||||
data,
|
||||
});
|
||||
|
||||
// 沟通列表
|
||||
export const getTalkList = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/inspectorTalkListPage?current=${data.current}`,
|
||||
});
|
||||
|
||||
// 沟通详情
|
||||
export const getTalkDetails = (data?: Record<string, any>): Promise<any> => {
|
||||
const param = data.inspectorTalkRecordId
|
||||
? `?inspectorTalkRecordId=${data.inspectorTalkRecordId}&userId=${data.userId}`
|
||||
: `?userId=${data.userId}`;
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/studentInspectorTalkPage${param}`,
|
||||
});
|
||||
};
|
||||
|
||||
// 消息存储
|
||||
export const saveTalk = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/inspectorStudent/sendInspectorTalkRecord`,
|
||||
data,
|
||||
});
|
||||
|
||||
// 验证码校验
|
||||
export const validCode = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/sms/validateMessage?phoneNumbers=${data.phone}&code=${data.code}`,
|
||||
data,
|
||||
});
|
||||
// 获取我的未读信息
|
||||
export const getUnreadCount = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/myUnreadCount`,
|
||||
});
|
||||
|
||||
// 消息标记为已读
|
||||
export const readMsg = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/inspectorStudent/readTalkRecord`,
|
||||
data,
|
||||
});
|
||||
|
||||
// 督学师是否已完善资料
|
||||
export const needPerfectInfo = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/phone/inspectorTeacher/is_perfect`,
|
||||
});
|
||||
// 学生是否可以进行精准学判断
|
||||
export const getPrecisionFlag = (data?: Record<string, any>): Promise<any> =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/userTrainTitle/airPrecisionFlag?subjectId=${data.subjectId}&userId=${data.userId}`,
|
||||
});
|
||||
|
||||
export const getKnowledgeFlag = (knowledgeId: string): Promise<boolean> => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: `/subjectKnowledge/titleFlag?id=${knowledgeId}`,
|
||||
});
|
||||
}
|
||||
|
||||
export const getKnowledgeVideo = (chapterId: string | number): Promise<any> => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: `/subjectChapterVideo/list?chapterId=${chapterId}`,
|
||||
});
|
||||
}
|
30
src/api/modules/inviteBind.ts
Normal file
30
src/api/modules/inviteBind.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import $req from '../request';
|
||||
/**
|
||||
* 获取邀请配置
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getJsapiSignatureApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/wechatPublic/createJsapiSignature',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 邀请绑定
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const parentBindInviteApi = (data: anyobj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindInvite',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 跳转邀请绑定页面后获取对应数据
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getInviteInfoApi = id =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/parentBindInvite/${id}`,
|
||||
});
|
78
src/api/modules/mine.ts
Normal file
78
src/api/modules/mine.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 家长绑定的孩子
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getParentBindChildApi = (params: anyObj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBindChild/list',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 家长绑定的设备
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getParentBindDeviceApi = (params: anyObj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBindDevice/list',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 我的孩子-绑定的家长
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const parentBindChildApi = (id: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/parentBindChild/${id}`,
|
||||
});
|
||||
/**
|
||||
* 我的孩子-解除绑定家长
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const childUnBoundParentApi = (data: anyObj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindChild/unBound',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 我的设备-解除绑定
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const parentUnBoundDeviceApi = (data: anyObj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindDevice/unBound',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 我的设备-当前登录
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const parentDeviceCurrentLoginApi = (id: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/parentBindDevice/${id}`,
|
||||
});
|
||||
/**
|
||||
* 设备管理-绑定申请列表
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const applyBindInfoListApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/applyInfoList',
|
||||
});
|
||||
/**
|
||||
* 设备管理-当前登录用户是否管理员
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const userInfoPermitApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/applyInfoPermit',
|
||||
});
|
54
src/api/modules/mySupervisionService.ts
Normal file
54
src/api/modules/mySupervisionService.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import $req from '../request';
|
||||
|
||||
|
||||
export const getInspectorCourseList = (params: {userId: string | number}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/userInspectorCourseList',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
export const getInspectorModeList = () => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/inspectorModeList',
|
||||
});
|
||||
}
|
||||
|
||||
// 最近或最后一次课时督学师和同学信息
|
||||
export const getInspectorTeacherStudentInfo = (params: {orderId: string}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/inspectorTeacherStudentInfo',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 督学订单列表
|
||||
export const getInspectorOrderList = (params: {userId: string | number}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/userInspectorOrderList',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 查询督学计划
|
||||
export const getInspectorPlan = (params: {inspectorPlanId: string}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/inspectorPlanInfo',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
// 督学计划报告
|
||||
export const getInspectorPlanReport = (params: {userId: string}) => {
|
||||
return $req({
|
||||
method: 'get',
|
||||
url: '/inspectorParent/userInspectorCourseReportList',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
12
src/api/modules/parent.ts
Normal file
12
src/api/modules/parent.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import $req from '../request';
|
||||
|
||||
export const getChildList = () => {
|
||||
return $req({
|
||||
url: '/inspectorParent/childList',
|
||||
});
|
||||
};
|
||||
export const getModelList = () => {
|
||||
return $req({
|
||||
url: '/inspectorParent/inspectorModeList',
|
||||
});
|
||||
};
|
72
src/api/modules/taskManage.ts
Normal file
72
src/api/modules/taskManage.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import $req from '../request';
|
||||
/**
|
||||
/**
|
||||
* 任务管理-列表
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getTaskListApi = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/scTask/list',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 任务管理-新增
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const addTaskApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/scTask',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 教师端-查询学生任务情况
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getFamilyTaskStuApi = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/scTask/getFamilyTaskStuList',
|
||||
params,
|
||||
});
|
||||
|
||||
/**
|
||||
* 选择任务-列表
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const taskConfigListApi = params =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/scTaskConfig/list',
|
||||
params,
|
||||
});
|
||||
/**
|
||||
* 选择任务-新增
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const addTaskConfigApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/scTaskConfig',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 选择任务-删除
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const deleteTaskConfigApi = ids =>
|
||||
$req({
|
||||
method: 'delete',
|
||||
url: `/scTaskConfig/${ids}`,
|
||||
});
|
||||
/**
|
||||
* 选择任务-更新
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const updateTaskStatusApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/scTask/updateTaskStatus',
|
||||
data,
|
||||
});
|
31
src/api/modules/warranty.ts
Normal file
31
src/api/modules/warranty.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import $req from '../request';
|
||||
|
||||
/**
|
||||
* 家长端-电子保修卡信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getWarrantyCardMsgApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/getBindDeviceList',
|
||||
});
|
||||
/**
|
||||
* 家长端-申请售后
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const applyServiceApi = (data: anyObj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/deviceWarrantyRecord',
|
||||
data,
|
||||
});
|
||||
/**
|
||||
* 家长端-查询售后历史
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getServiceHistoryApi = (params: anyObj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/deviceWarrantyRecord/list',
|
||||
params,
|
||||
});
|
93
src/api/request.ts
Normal file
93
src/api/request.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { user } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getCache } from '@/utils';
|
||||
|
||||
const CONFIG = {
|
||||
// host: import.meta.env.VITE_HOST || '',
|
||||
// baseURL: 'http://127.0.0.1:9053/api/main',
|
||||
baseURL: '/api/main',
|
||||
timeout: 60000,
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
export class HttpError extends Error {
|
||||
data: any;
|
||||
constructor(message: string, data: Record<string, any>) {
|
||||
super(message);
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
const request = async (config: Record<string, any>): Promise<any> => {
|
||||
const networkType = await uni.getNetworkType();
|
||||
if (networkType.networkType === 'none') {
|
||||
uni.showModal({
|
||||
content: '暂无网络,请恢复网络后使用',
|
||||
confirmText: '知道了',
|
||||
showCancel: false,
|
||||
confirmColor: '#ffe60f',
|
||||
});
|
||||
return Promise.reject(
|
||||
new HttpError('暂无网络,请恢复网络后使用', {
|
||||
code: '-1',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { clear } = user();
|
||||
const { token } = storeToRefs(user());
|
||||
const mergerToken = token.value || getCache('token');
|
||||
const method = (config.method || CONFIG.method).toUpperCase();
|
||||
|
||||
uni.request({
|
||||
method,
|
||||
// url: (config.host || CONFIG.host) + (config.baseURL || CONFIG.baseURL) + config.url,
|
||||
url: (config.baseURL || CONFIG.baseURL) + config.url,
|
||||
data: method === 'GET' ? config.params : config.data,
|
||||
timeout: config.timeout || CONFIG.timeout,
|
||||
header: {
|
||||
Authorization: mergerToken ? `Bearer ${mergerToken}` : '',
|
||||
...config.headers,
|
||||
},
|
||||
complete(res: anyObj) {
|
||||
if (res.statusCode === 200) {
|
||||
if (res.data.code === 200) {
|
||||
return resolve(res.data);
|
||||
} else {
|
||||
if (!config.showToast) {
|
||||
uni.showToast({
|
||||
title: res.data?.message,
|
||||
icon: 'none',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
return reject(new HttpError(res.data?.message, res.data));
|
||||
}
|
||||
} else {
|
||||
res.href = (config.baseURL || CONFIG.baseURL) + config.url;
|
||||
if (res.statusCode === 401) {
|
||||
clear();
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index',
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title:
|
||||
res.statusCode === 500 ? '服务异常' : res?.data?.message || '服务异常,请稍后重试',
|
||||
});
|
||||
}
|
||||
return reject(
|
||||
new HttpError(res.data?.message, {
|
||||
code: res.statusCode,
|
||||
data: res.data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default request;
|
57
src/components/BackBar/index.vue
Normal file
57
src/components/BackBar/index.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<uni-nav-bar
|
||||
fixed
|
||||
shadow
|
||||
background-color="#fff"
|
||||
color="#0A1F13"
|
||||
:border="false"
|
||||
:left-icon="leftIcon ? 'left' : ''"
|
||||
:title="title"
|
||||
@clickLeft="onLeftClick"
|
||||
>
|
||||
<template #right> <slot></slot> </template>
|
||||
</uni-nav-bar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['back']);
|
||||
function onLeftClick() {
|
||||
emits('back');
|
||||
if (props.custom) {
|
||||
console.log('自定义返回路径');
|
||||
return;
|
||||
}
|
||||
console.log('默认组件返回路径');
|
||||
const canNavBack = getCurrentPages();
|
||||
if (canNavBack && canNavBack.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
// if (canNavBack[canNavBack.length - 1].route === canNavBack[canNavBack.length - 2].route) {
|
||||
// uni.navigateTo({
|
||||
// url: `/${canNavBack[canNavBack.length - 3].route}`,
|
||||
// });
|
||||
// } else {
|
||||
// uni.navigateBack({
|
||||
// delta: 1,
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
history.back();
|
||||
}
|
||||
}
|
||||
</script>
|
102
src/components/CalenderPopup/CalenderPopup.vue
Normal file
102
src/components/CalenderPopup/CalenderPopup.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<view class="calender-popup">
|
||||
<view class="open-btn" @click="openCalender">
|
||||
<slot>
|
||||
<view class="default-btn">
|
||||
<view>日历</view>
|
||||
<wd-icon name="calendar" size="35rpx"></wd-icon>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<wd-popup v-model="showCalenderPopup" position="bottom" custom-style="border-radius: 30rpx 30rpx 0 0;">
|
||||
<student-calender v-model:value="currentDate"></student-calender>
|
||||
<view class="calender-popup-footer">
|
||||
<wd-button :round="false" class="cancel" @click="closeCalender">取消</wd-button>
|
||||
<wd-button :round="false" class="confirm" @click="confirm">确定</wd-button>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang=ts>
|
||||
import {ref, watch} from "vue";
|
||||
import StudentCalender from "@/components/student-calendar/index"
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: [String, dayjs],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change'])
|
||||
|
||||
const showCalenderPopup = ref(false)
|
||||
|
||||
const currentDate = ref("")
|
||||
|
||||
const openCalender = () => {
|
||||
showCalenderPopup.value = true
|
||||
}
|
||||
|
||||
const closeCalender = () => {
|
||||
showCalenderPopup.value = false
|
||||
}
|
||||
|
||||
watch(() => props.value, value => {
|
||||
if (value) {
|
||||
currentDate.value = typeof value === "string" ? value : value.format('YYYY-MM-DD')
|
||||
} else {
|
||||
currentDate.value = dayjs().format('YYYY-MM-DD')
|
||||
}
|
||||
}, {immediate: true, deep: true})
|
||||
|
||||
const confirm = () => {
|
||||
emit('update:value', dayjs(currentDate.value))
|
||||
emit("change", dayjs(currentDate.value))
|
||||
closeCalender()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.calender-popup {
|
||||
display: inline-block;
|
||||
.open-btn {
|
||||
display: inline-block;
|
||||
.default-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
color: #666;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.calender-popup-footer {
|
||||
height: 122rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx 30rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.cancel {
|
||||
width: 230rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #EFEFFF;
|
||||
color: #615DFF;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.confirm {
|
||||
width: 440rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #615DFF;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
46
src/components/CustomNavbar/index.vue
Normal file
46
src/components/CustomNavbar/index.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
// import { handleBack } from '../../hooks';
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
function handleBack() {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<wd-navbar
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
safeAreaInsetTop
|
||||
fixed
|
||||
placeholder
|
||||
left-arrow
|
||||
:z-index="0"
|
||||
@click-left="handleBack"
|
||||
>
|
||||
<template #left>
|
||||
<image
|
||||
src="https://box-1313840333.cos.ap-guangzhou.myqcloud.com/subpackage/back.svg"
|
||||
class="back_icon"
|
||||
></image>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.back_icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
:deep(.wd-navbar) {
|
||||
.wd-navbar {
|
||||
&__left {
|
||||
padding-left: $space;
|
||||
width: 48rpx;
|
||||
}
|
||||
&__title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
127
src/components/CustomPopup/index.vue
Normal file
127
src/components/CustomPopup/index.vue
Normal file
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view class="custom_popup">
|
||||
<uni-popup ref="popupRef" :is-mask-click="false" animation type="center">
|
||||
<view class="popup_box">
|
||||
<view class="container">
|
||||
<view v-if="titleText" class="title">
|
||||
{{ titleText }}
|
||||
</view>
|
||||
<!-- 自定义内容 -->
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<text v-if="cancelBtnText" class="btn cancel_btn" @click="tapCancelBtn">{{
|
||||
cancelBtnText
|
||||
}}</text>
|
||||
<text class="btn confirm_btn" @click="tapConfirmBtn">{{ confirmBtnText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
titleText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
cancelBtnText: {
|
||||
type: String,
|
||||
default: '取消',
|
||||
},
|
||||
confirmBtnText: {
|
||||
type: String,
|
||||
default: '确定',
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['update:modelValue', 'handleConfirmBtn', 'handleCancelBtn']);
|
||||
|
||||
const popupRef = ref(null);
|
||||
// 确认
|
||||
function tapConfirmBtn() {
|
||||
emits('handleConfirmBtn');
|
||||
}
|
||||
// 取消
|
||||
function tapCancelBtn() {
|
||||
popupRef.value.close();
|
||||
emits('update:modelValue', false);
|
||||
emits('handleCancelBtn');
|
||||
}
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (props.modelValue) {
|
||||
nextTick(() => {
|
||||
popupRef.value.open();
|
||||
});
|
||||
} else {
|
||||
nextTick(() => {
|
||||
popupRef.value && popupRef.value.close();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom_popup {
|
||||
.popup_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 594rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
|
||||
.container {
|
||||
min-height: 223rpx;
|
||||
|
||||
.title {
|
||||
padding: 30rpx 0 0 0;
|
||||
line-height: 48rpx;
|
||||
font-weight: 500;
|
||||
font-style: 34rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 108rpx;
|
||||
line-height: 108rpx;
|
||||
border-top: 1px solid #eeeeee;
|
||||
color: #333333;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cancel_btn {
|
||||
border-right: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
.confirm_btn {
|
||||
color: #615dff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
76
src/components/CustomSegmented/index.vue
Normal file
76
src/components/CustomSegmented/index.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<wd-segmented
|
||||
v-model:value="current"
|
||||
:options="list"
|
||||
custom-class="main-view"
|
||||
@change="handleChange"
|
||||
>
|
||||
<!-- <template #label="{ option }">-->
|
||||
<!-- <view class="option-view">{{ option.value }}</view>-->
|
||||
<!-- </template>-->
|
||||
</wd-segmented>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
const current = ref(0);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
value => {
|
||||
current.value = value;
|
||||
},
|
||||
);
|
||||
|
||||
const handleChange = (value: number) => {
|
||||
current.value = value;
|
||||
emit('update:value', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main-view {
|
||||
padding: 0;
|
||||
height: 52rpx;
|
||||
line-height: 52rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.wd-segmented__item) {
|
||||
background-color: #f7f8fa;
|
||||
font-size: 26rpx;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
&.is-active {
|
||||
background-color: #615dff;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.wd-segmented__item--active) {
|
||||
display: none;
|
||||
background-color: #615dff;
|
||||
//z-index: 1 !important;
|
||||
height: 100%;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.option-view {
|
||||
//background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
65
src/components/Empty/index.vue
Normal file
65
src/components/Empty/index.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
content?: string;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
delay?: number | string;
|
||||
marginTop?: number | string;
|
||||
}>(),
|
||||
{
|
||||
content: '暂无内容',
|
||||
width: 300,
|
||||
height: 300,
|
||||
delay: 0,
|
||||
marginTop: 100,
|
||||
},
|
||||
);
|
||||
|
||||
const show = ref(false);
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
onMounted(() => {
|
||||
setTimeout(() => (show.value = true), Number(props.delay));
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="show" class="empty" :style="{ marginTop: `${marginTop}rpx` }">
|
||||
<slot>
|
||||
<image
|
||||
:src="`${OSS_URL}/empty.png`"
|
||||
:style="{
|
||||
width: `${width}rpx`,
|
||||
height: `${height}rpx`,
|
||||
}"
|
||||
/>
|
||||
<div class="empty-content">
|
||||
{{ content }}
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
.empty-content {
|
||||
width: 100%;
|
||||
color: #666666;
|
||||
font-size: 28rpx;
|
||||
line-height: 42rpx;
|
||||
margin-top: 50rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
96
src/components/MfxBtnPopup/index.vue
Normal file
96
src/components/MfxBtnPopup/index.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import type { TPopupConfig } from '@/types/draw';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
config: TPopupConfig;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'onChange']);
|
||||
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="show"
|
||||
position="bottom"
|
||||
safe-area-inset-bottom
|
||||
custom-style="border-radius: 32rpx 32rpx 0 0;"
|
||||
:close-on-click-modal="false"
|
||||
@click-modal="show = false"
|
||||
>
|
||||
<view class="wrapper">
|
||||
<view class="header">
|
||||
{{ config?.title }}
|
||||
<image
|
||||
class="close-icon"
|
||||
src="@/static/common/icon_down_out.png"
|
||||
@click.stop="show = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button
|
||||
v-for="(item, index) in config?.btnInfo"
|
||||
:key="index"
|
||||
class="footer-button"
|
||||
@click.stop="
|
||||
() => {
|
||||
item?.fn?.();
|
||||
show = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ item?.text }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
background: #fff;
|
||||
color: $font-color;
|
||||
padding: 32upx;
|
||||
color: $font-color;
|
||||
|
||||
.header {
|
||||
height: 56upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
font-size: 32upx;
|
||||
position: relative;
|
||||
|
||||
.close-icon {
|
||||
width: 56upx;
|
||||
height: 56upx;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding-top: 16upx;
|
||||
|
||||
&-button {
|
||||
width: 100%;
|
||||
height: 88upx;
|
||||
line-height: 88upx;
|
||||
margin-top: 32upx;
|
||||
color: $font-color;
|
||||
border-radius: 44upx;
|
||||
font-size: 28upx;
|
||||
background: $bg-fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
119
src/components/MfxOverlay/index.vue
Normal file
119
src/components/MfxOverlay/index.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
type?: string; // alert confirm
|
||||
title?: string;
|
||||
content?: string;
|
||||
cancelBtnText?: string;
|
||||
confirmBtnText?: string;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'onConfirm', 'onCancel']);
|
||||
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emits('update:modelValue', value),
|
||||
});
|
||||
|
||||
function handleCancel() {
|
||||
emits('onCancel');
|
||||
show.value = false;
|
||||
}
|
||||
function handleConfirm() {
|
||||
emits('onConfirm');
|
||||
show.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-overlay :show="show" :z-index="999">
|
||||
<view class="wrapper">
|
||||
<wd-transition :show="show" name="zoom-in">
|
||||
<view class="block">
|
||||
<view v-if="title" class="title">{{ title }}</view>
|
||||
<view class="content">
|
||||
{{ content || '提示内容' }}
|
||||
</view>
|
||||
<view class="footer">
|
||||
<template v-if="type === 'confirm'">
|
||||
<button
|
||||
class="footer-button"
|
||||
:style="{ marginRight: '24upx' }"
|
||||
@click.stop="handleCancel"
|
||||
>
|
||||
{{ cancelBtnText || '取消' }}
|
||||
</button>
|
||||
<button class="footer-button primary" @click.stop="handleConfirm">
|
||||
{{ confirmBtnText || '确定' }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button class="footer-button primary" @click.stop="show = false">
|
||||
{{ confirmBtnText || '好的,我知道了' }}
|
||||
</button>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</wd-transition>
|
||||
</view>
|
||||
</wd-overlay>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
.block {
|
||||
width: 612upx;
|
||||
/* padding: 32upx 40upx; */
|
||||
padding: 32upx 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-radius: 30upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-size: 32upx;
|
||||
color: $font-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: #333;
|
||||
font-size: 28upx;
|
||||
margin-top: 36upx;
|
||||
padding: 0 40upx;
|
||||
}
|
||||
.footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 72upx;
|
||||
padding: 0 0 0 24upx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-button {
|
||||
flex: 1;
|
||||
height: 88upx;
|
||||
background: $bg-fill;
|
||||
border-radius: 44upx;
|
||||
font-size: 32upx;
|
||||
font-weight: bold;
|
||||
color: $font-color;
|
||||
margin-right: 24rpx;
|
||||
|
||||
&.primary {
|
||||
background: $theme-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
69
src/components/MfxPopup/index.vue
Normal file
69
src/components/MfxPopup/index.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'onClose']);
|
||||
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
emit('update:modelValue', false);
|
||||
emit('onClose');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<wd-popup
|
||||
v-model="show"
|
||||
position="bottom"
|
||||
safe-area-inset-bottom
|
||||
custom-style="border-radius: 32rpx 32rpx 0 0;"
|
||||
:close-on-click-modal="false"
|
||||
@click-modal="handleClose"
|
||||
>
|
||||
<view class="mfx_popup">
|
||||
<view class="mfx_popup-header">
|
||||
{{ title || '' }}
|
||||
<image
|
||||
class="mfx_popup-header-close"
|
||||
src="@/static/common/icon_down_out.png"
|
||||
@click.stop="handleClose"
|
||||
/>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mfx_popup {
|
||||
background: #fff;
|
||||
color: $font-color;
|
||||
padding: 32upx;
|
||||
color: $font-color;
|
||||
|
||||
&-header {
|
||||
height: 56upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
font-size: 32upx;
|
||||
position: relative;
|
||||
|
||||
&-close {
|
||||
width: 56upx;
|
||||
height: 56upx;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
63
src/components/MfxSwitch/index.vue
Normal file
63
src/components/MfxSwitch/index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
const emits = defineEmits(['update:modelValue', 'onChange']);
|
||||
|
||||
const switchValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emits('update:modelValue', value),
|
||||
});
|
||||
|
||||
function handleChange() {
|
||||
if (props?.disabled) return;
|
||||
switchValue.value = !switchValue.value;
|
||||
emits('onChange', switchValue.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="mfx-switch"
|
||||
:class="{ disabled }"
|
||||
:style="{ background: `${switchValue ? '#64EEEE' : '#EAEEF2'}` }"
|
||||
@click.stop="handleChange"
|
||||
>
|
||||
<view
|
||||
class="mfx-switch-circle"
|
||||
:style="{
|
||||
transform: `translateX(${switchValue ? 66 : 14}rpx)`,
|
||||
background: `${switchValue ? '#1a1a1a' : '#ffffff'}`,
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mfx-switch {
|
||||
width: 108rpx;
|
||||
height: 56rpx;
|
||||
background: #eaeef2;
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&-circle {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
transform: translateX(14rpx);
|
||||
transition: transform 0.4s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
25
src/components/PageLoading/index.vue
Normal file
25
src/components/PageLoading/index.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
//
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="loading-wrapper">
|
||||
<wd-loading :size="40" color="#64eeee" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-wrapper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
175
src/components/Picker/index.vue
Normal file
175
src/components/Picker/index.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="picker_box">
|
||||
<uni-popup ref="popupRef" :is-mask-click="false" animation type="bottom">
|
||||
<view class="popup_box">
|
||||
<view v-if="title" class="header_title">
|
||||
{{ title }}
|
||||
</view>
|
||||
<picker-view
|
||||
:indicator-style="indicatorStyle"
|
||||
:overshoot="false"
|
||||
:value="pickValue"
|
||||
class="picker-view"
|
||||
@change="bindChange"
|
||||
>
|
||||
<picker-view-column>
|
||||
<view
|
||||
v-for="(item, index) in pickList"
|
||||
:key="index"
|
||||
:class="['picker_item', pickValue[0] === index ? 'selected_item' : '']"
|
||||
>{{ item.name }}</view
|
||||
>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view class="operation">
|
||||
<button class="cancel_btn" @tap="tapCancelBtn">取消</button>
|
||||
<button class="confirm_btn" @tap="tapConfirmBtn">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
pickList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
pickVal: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['update:modelValue', 'confirm']);
|
||||
|
||||
const btnDisabled = ref(true); // 按钮禁用
|
||||
|
||||
const popupRef = ref(null);
|
||||
const indicatorStyle = ref(`height: 50px;`);
|
||||
const pickValue = ref([]);
|
||||
const isBindChange = ref(false); // 是否触发选择操作
|
||||
|
||||
// 选择值
|
||||
function bindChange(e) {
|
||||
isBindChange.value = true;
|
||||
btnDisabled.value = false;
|
||||
pickValue.value = e.detail.value;
|
||||
}
|
||||
// 取消
|
||||
function tapCancelBtn() {
|
||||
emits('update:modelValue', false);
|
||||
}
|
||||
// 确定
|
||||
function tapConfirmBtn() {
|
||||
setTimeout(() => {
|
||||
if (isBindChange.value) {
|
||||
// 选择
|
||||
if (!btnDisabled.value) {
|
||||
emits('confirm', pickValue.value);
|
||||
}
|
||||
} else {
|
||||
// 没有选择
|
||||
emits('confirm', pickValue.value);
|
||||
}
|
||||
}, 620);
|
||||
}
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (props.modelValue) {
|
||||
nextTick(() => {
|
||||
popupRef.value.open();
|
||||
pickValue.value = props.pickVal;
|
||||
btnDisabled.value = true;
|
||||
isBindChange.value = false;
|
||||
});
|
||||
} else {
|
||||
if (popupRef.value) {
|
||||
popupRef.value.close();
|
||||
isBindChange.value = false;
|
||||
btnDisabled.value = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.picker_box {
|
||||
.popup_box {
|
||||
position: relative;
|
||||
height: 660rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
overflow: hidden;
|
||||
padding: 50rpx 0 0 0;
|
||||
|
||||
.header_title {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
left: 30rpx;
|
||||
z-index: 99;
|
||||
color: #333;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 750rpx;
|
||||
height: 450rpx;
|
||||
margin: 20rpx 0 20rpx 0;
|
||||
}
|
||||
|
||||
.picker_item {
|
||||
line-height: 100rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selected_item {
|
||||
color: #615dff;
|
||||
}
|
||||
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1px solid #eeeeee;
|
||||
bottom: 60rpx;
|
||||
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel_btn {
|
||||
width: 230rpx;
|
||||
height: 90rpx;
|
||||
color: #615dff;
|
||||
background-color: #ececff;
|
||||
}
|
||||
|
||||
.confirm_btn {
|
||||
margin-left: 20rpx;
|
||||
height: 90rpx;
|
||||
width: 440rpx;
|
||||
color: #fff;
|
||||
background-color: #615dff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
278
src/components/SupervisorTeacher/SupervisorTeacher.vue
Normal file
278
src/components/SupervisorTeacher/SupervisorTeacher.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<view class="supervisor-teacher">
|
||||
<template v-if="!teacher">
|
||||
<view class="empty-content">
|
||||
<view class="avatar-image">
|
||||
<view class="image-box">
|
||||
<image :src="`${OSS_URL}/patriarch_header_1.png`"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="dot-list">
|
||||
<view class="dot-item"></view>
|
||||
<view class="dot-item"></view>
|
||||
<view class="dot-item"></view>
|
||||
</view>
|
||||
<view class="teacher-icon">
|
||||
<wd-icon name="user" size="22px"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tip">正在为您精心匹配督学师,请耐心等待~</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="base-info">
|
||||
<view class="avatar">
|
||||
<image :src="teacher.avatar"></image>
|
||||
</view>
|
||||
<view class="info-box">
|
||||
<view class="name">{{ teacher.name }}</view>
|
||||
<view class="school-edu">
|
||||
<text>{{ teacher.school }}</text> |
|
||||
<text>{{ teacher.edu }}</text>
|
||||
</view>
|
||||
<view class="label-list">
|
||||
<view
|
||||
v-for="item in teacher.labelList"
|
||||
:key="item.name"
|
||||
class="label"
|
||||
:style="{ color: item.color, backgroundColor: item.bgColor }"
|
||||
>{{ item.name }}</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-show="open" class="description">
|
||||
<view class="title-label">
|
||||
<template v-for="(item, index) in teacher.titleLabel" :key="index">
|
||||
<text>{{ item }}</text>
|
||||
<text v-if="index !== teacher.titleLabel.length - 1"> | </text>
|
||||
</template>
|
||||
</view>
|
||||
<view class="desc-list">
|
||||
{{teacher.desc}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="show-open-row">
|
||||
<view class="show-open" @click="changeOpen">
|
||||
<view>{{ open ? '收起' : '展开' }}</view>
|
||||
<wd-icon v-if="!open" name="arrow-down" size="24rpx"></wd-icon>
|
||||
<wd-icon v-else name="arrow-up" size="24rpx"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
export interface ITeacher {
|
||||
name: string;
|
||||
school: string;
|
||||
edu: string;
|
||||
avatar: string;
|
||||
labelList: {
|
||||
name: string;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
}[];
|
||||
titleLabel: string[];
|
||||
desc: string;
|
||||
}
|
||||
const props = defineProps<{
|
||||
teacher: ITeacher | null | undefined;
|
||||
}>();
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const changeOpen = () => {
|
||||
open.value = !open.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.supervisor-teacher {
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
.empty-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 50rpx;
|
||||
.avatar-image {
|
||||
width: 232rpx;
|
||||
height: 232rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
.image-box {
|
||||
border-radius: 50%;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
background-color: #ededff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
image {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border: 2rpx solid #615dff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&::before {
|
||||
background-color: #fafaff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
&::after {
|
||||
background-color: #f4f4ff;
|
||||
width: 184rpx;
|
||||
height: 184rpx;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
.dot-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 62rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 7rpx;
|
||||
position: relative;
|
||||
.dot-item {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background-color: transparent;
|
||||
border-radius: 50%;
|
||||
&:nth-child(1) {
|
||||
background-color: #9592ff;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
background-color: #dbdaff;
|
||||
}
|
||||
&:nth-child(3) {
|
||||
background-color: #dbdaff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.teacher-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background-color: #ededf0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 30rpx;
|
||||
line-height: 42rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.base-info {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding-bottom: 20rpx;
|
||||
.avatar {
|
||||
margin-right: 20rpx;
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.info-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
.name {
|
||||
font-size: 36rpx;
|
||||
line-height: 48rpx;
|
||||
color: #0a1f13;
|
||||
font-weight: 500;
|
||||
}
|
||||
.school-edu {
|
||||
color: #a9aab5;
|
||||
font-size: 30rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.label-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10rpx;
|
||||
.label {
|
||||
padding: 0 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.description {
|
||||
padding-top: 20rpx;
|
||||
.title-label {
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
.desc-list {
|
||||
color: #666;
|
||||
font-size: 30rpx;
|
||||
line-height: 48rpx;
|
||||
.desc-list-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
.dot {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10rpx;
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background-color: #666666;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.show-open-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
.show-open {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #a8aab5;
|
||||
user-select: none;
|
||||
height: 36rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
161
src/components/Tabbar/index.vue
Normal file
161
src/components/Tabbar/index.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tab-bar-item"
|
||||
@click="switchTab(item, index)"
|
||||
>
|
||||
<view class="tab-box">
|
||||
<image
|
||||
class="tab_img"
|
||||
:src="currentIndex === index ? item.selectedIconPath : item.iconPath"
|
||||
></image>
|
||||
<text v-if="notice && item.text === '我的'" class="notice"></text>
|
||||
</view>
|
||||
|
||||
<view class="tab_text" :style="{ color: currentIndex === index ? selectedColor : color }">{{
|
||||
item.text
|
||||
}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { bindApplyStore } from '@/store';
|
||||
|
||||
import { getCache } from '@/utils';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selectedIndex: number;
|
||||
}>(),
|
||||
{ selectedIndex: 0 },
|
||||
);
|
||||
const { getApplyBindInfoList } = bindApplyStore();
|
||||
|
||||
const currentIndex = ref(0);
|
||||
const list = ref([]);
|
||||
const color = ref<string>('#8E8D9D');
|
||||
const selectedColor = ref<string>('#625EFF');
|
||||
const notice = ref(false);
|
||||
function switchTab(item, index) {
|
||||
currentIndex.value = index;
|
||||
let url = item.pagePath;
|
||||
uni.redirectTo({ url: url });
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
currentIndex.value = props.selectedIndex;
|
||||
let adminType = getCache('userInfo').adminType;
|
||||
if (adminType === 16) {
|
||||
// 家长
|
||||
await getApplyBindInfoList();
|
||||
notice.value = getCache('applyBindMsg');
|
||||
list.value = [
|
||||
{
|
||||
pagePath: '/pages/home/index',
|
||||
iconPath: '/static/tabBar/homeno.png',
|
||||
selectedIconPath: '/static/tabBar/home.png',
|
||||
text: '首页',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/academicReport/index',
|
||||
iconPath: '/static/tabBar/studyReportNo.png',
|
||||
selectedIconPath: '/static/tabBar/studyReport.png',
|
||||
text: '学情报告',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/applicationManagement/index',
|
||||
iconPath: '/static/tabBar/applicationNo.png',
|
||||
selectedIconPath: '/static/tabBar/application.png',
|
||||
text: '应用管控',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/mine/index',
|
||||
iconPath: '/static/tabBar/mineno.png',
|
||||
selectedIconPath: '/static/tabBar/mine.png',
|
||||
text: '我的',
|
||||
},
|
||||
];
|
||||
} else if (adminType === 6) {
|
||||
// 督学
|
||||
uni.reLaunch({
|
||||
url: '/pages/inspector/schedule/index',
|
||||
});
|
||||
list.value = [
|
||||
{
|
||||
pagePath: '/pages/inspector/schedule/index',
|
||||
iconPath: '/static/tabBar/scheduleNo.png',
|
||||
selectedIconPath: '/static/tabBar/schedule.png',
|
||||
text: '课表',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/inspector/student/index',
|
||||
iconPath: '/static/tabBar/studentNo.png',
|
||||
selectedIconPath: '/static/tabBar/student.png',
|
||||
text: '学员',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/inspector/mine/index',
|
||||
iconPath: '/static/tabBar/mineno.png',
|
||||
selectedIconPath: '/static/tabBar/mine.png',
|
||||
text: '我的',
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-top: 1rpx solid #eee;
|
||||
padding-bottom: env(safe-area-inset-bottom); // 适配iphoneX的底部
|
||||
z-index: 99999;
|
||||
|
||||
.tab-bar-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
.tab-box {
|
||||
position: relative;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
|
||||
.notice {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
background-color: red;
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tab_img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tab_text {
|
||||
font-size: 22rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
148
src/components/TipPopup/index.vue
Normal file
148
src/components/TipPopup/index.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<view class="tip_popup">
|
||||
<uni-popup ref="popupRef" :is-mask-click="false" animation type="center">
|
||||
<view class="popup_box">
|
||||
<view class="container">
|
||||
<image
|
||||
v-if="showSuccessIcon"
|
||||
:src="
|
||||
type === 'success'
|
||||
? `${OSS_URL}/iconfont/success_icon.png`
|
||||
: `${OSS_URL}/iconfont/fail_icon.png`
|
||||
"
|
||||
class="icon"
|
||||
mode=""
|
||||
></image>
|
||||
<view :class="[type === 'success' ? 'success' : 'fail']"> {{ text }} </view>
|
||||
<view v-if="messageTip" class="message_tip" v-html="messageTip"></view>
|
||||
</view>
|
||||
<view v-if="showBtn" class="btn" @tap="tapBack">
|
||||
{{ btnText }}
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 类型:success,fail
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
},
|
||||
// 是否展示成功图标
|
||||
showSuccessIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 是否展示按钮
|
||||
showBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 按钮文案
|
||||
btnText: {
|
||||
type: String,
|
||||
default: '返回',
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '加入成功',
|
||||
},
|
||||
messageTip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['update:modelValue', 'backBtn']);
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
const popupRef = ref(null);
|
||||
// 点击
|
||||
function tapBack() {
|
||||
popupRef.value.close();
|
||||
emits('update:modelValue', false);
|
||||
emits('backBtn');
|
||||
}
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if (props.modelValue) {
|
||||
nextTick(() => {
|
||||
popupRef.value.open();
|
||||
if (!props.showBtn) {
|
||||
setTimeout(() => {
|
||||
popupRef.value.close();
|
||||
emits('update:modelValue', false);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tip_popup {
|
||||
.popup_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 594rpx;
|
||||
height: 346rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
|
||||
.success {
|
||||
margin-top: 30rpx;
|
||||
color: #21d17a;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.fail {
|
||||
margin-top: 30rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #ff2828;
|
||||
}
|
||||
.message_tip {
|
||||
margin-top: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 108rpx;
|
||||
line-height: 108rpx;
|
||||
border-top: 1px solid #eeeeee;
|
||||
color: #333333;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
74
src/components/custom-table/index.vue
Normal file
74
src/components/custom-table/index.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<view class="table-view">
|
||||
<view class="table-view-ths table-view-item">
|
||||
<view v-for="column in columns" :key="column.key">{{ column.title }}</view>
|
||||
</view>
|
||||
|
||||
<view v-for="(item, index) in data" :key="index" class="table-view-item table-view-tds">
|
||||
<view v-for="column in columns" :key="column.key">{{ item[column.key] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const props = defineProps<{
|
||||
columns: Record<string, any>[];
|
||||
data: Record<string, any>[];
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.table-view {
|
||||
border-radius: 20rpx;
|
||||
font-size: 26rpx;
|
||||
font-style: normal;
|
||||
line-height: normal;
|
||||
overflow: hidden;
|
||||
|
||||
&-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 2rpx solid #d9e1f2;
|
||||
&:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.table-view-ths {
|
||||
background-color: #ebf2ff;
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&.table-view-tds {
|
||||
background-color: #F7FAFF;
|
||||
&:nth-child(odd) {
|
||||
background-color: #ebf2ff;
|
||||
}
|
||||
}
|
||||
|
||||
& > view {
|
||||
width: 33.33%;
|
||||
text-align: center;
|
||||
border-right: 2rpx solid #d9e1f2;
|
||||
padding: 20rpx 0;
|
||||
|
||||
&:first-child {
|
||||
width: 230rpx;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
padding-left: 37rpx;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
323
src/components/customCalender/index.vue
Normal file
323
src/components/customCalender/index.vue
Normal file
@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<view class="calendar-box">
|
||||
<view class="calendar-wrapper">
|
||||
<view v-if="headerSlot">
|
||||
<slot name="header" :date="{ monthDate: currentDateStr, date: dateForm.currentDay }"></slot>
|
||||
</view>
|
||||
<view v-if="showHeader && !headerSlot" class="calendar-toolbar">
|
||||
<view class="current" @click="dateForm.showMonthPicker = true">
|
||||
{{ currentDateStr }}
|
||||
</view>
|
||||
<view class="steps">
|
||||
<view class="step prev" @click="prevMonth">
|
||||
<wd-icon name="arrow-left" size="30rpx"></wd-icon>
|
||||
</view>
|
||||
<view class="step next" @click="nextMonth">
|
||||
<wd-icon name="arrow-right" size="30rpx"></wd-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="calendar-week">
|
||||
<view v-for="item of weekList" :key="item" class="week-item calendarBorder">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="calendar-inner">
|
||||
<view
|
||||
v-for="(item, index) of calendarList"
|
||||
:key="index"
|
||||
:class="{
|
||||
'calendar-item': true,
|
||||
calendarBorder: true,
|
||||
'calendar-item-checked': dateForm.multipleList.includes(item.date),
|
||||
'disabled': item.disabled
|
||||
}"
|
||||
@click="handleClickDay(item)"
|
||||
>
|
||||
<view
|
||||
:class="{
|
||||
date: true,
|
||||
'calendar-item-hover': item.isCurrentMonth,
|
||||
'calendar-item-disabled': !item.isCurrentMonth,
|
||||
'calendar-item-current':
|
||||
dateForm.multipleList.includes(item.date) && item.isCurrentMonth,
|
||||
}"
|
||||
>{{ item.day }}</view
|
||||
>
|
||||
<view v-if="cellSlot">
|
||||
<slot name="cell" :date="item"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, computed, onMounted, watch, useSlots, nextTick } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { type CalendarDay, generateMonthCalendar } from '@/utils/calendar';
|
||||
const props = defineProps({
|
||||
minDate: {
|
||||
type: Date,
|
||||
// required: true,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
// required: true
|
||||
},
|
||||
auto: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
selectedDay: {
|
||||
type: String,
|
||||
// required: true,
|
||||
default: dayjs().format('YYYY-MM-DD'),
|
||||
},
|
||||
// 可选日期//默认所有可选
|
||||
canSeletion: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
calendar: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
calendarDays: {
|
||||
type: Array,
|
||||
default: [],
|
||||
// required: true,
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
IndexDayCount: {
|
||||
type: Array,
|
||||
default: [],
|
||||
// required: true,
|
||||
},
|
||||
dateDisabled: {
|
||||
type: Function
|
||||
}
|
||||
});
|
||||
const emits = defineEmits(['selectedDay', 'changeMonth', 'selection']);
|
||||
const uSlot = useSlots();
|
||||
const dateForm = reactive({
|
||||
showMonthPicker: false,
|
||||
currentDay: dayjs(new Date()).format('YYYY-MM-DD'),
|
||||
multipleList: [],
|
||||
});
|
||||
const weekList = ['一', '二', '三', '四', '五', '六', '日']; // 周
|
||||
const monthFirstDay = ref(dayjs().startOf('month'));
|
||||
const calendarList = computed(() => {
|
||||
return generateMonthCalendar(monthFirstDay.value.toDate()).flat().map(item => {
|
||||
return {
|
||||
...item,
|
||||
disabled: props.dateDisabled && typeof props.dateDisabled === 'function' ? props.dateDisabled(item) : false
|
||||
}
|
||||
});
|
||||
});
|
||||
const currentDateStr = computed(() => {
|
||||
return dayjs(monthFirstDay.value).format('YYYY年MM月');
|
||||
});
|
||||
const headerSlot = computed(() => {
|
||||
return uSlot.header;
|
||||
});
|
||||
const cellSlot = computed(() => {
|
||||
return uSlot.cell;
|
||||
});
|
||||
watch(
|
||||
() => props.selectedDay,
|
||||
val => {
|
||||
nextTick(() => {
|
||||
// 非多选默认选中当天或者不默认选中
|
||||
if (!props.auto) {
|
||||
dateForm.multipleList = [];
|
||||
} else if (!props.multiple || props.auto) {
|
||||
dateForm.multipleList = [val];
|
||||
}
|
||||
monthFirstDay.value = dayjs(val).startOf('month');
|
||||
});
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const prevMonth = () => {
|
||||
monthFirstDay.value = monthFirstDay.value.subtract(1, 'month');
|
||||
setCurrentDay();
|
||||
};
|
||||
|
||||
const nextMonth = () => {
|
||||
monthFirstDay.value = monthFirstDay.value.add(1, 'month');
|
||||
setCurrentDay();
|
||||
};
|
||||
function setCurrentDay() {
|
||||
if (!props.multiple) {
|
||||
const date = dayjs(monthFirstDay.value).format('YYYY-MM-DD');
|
||||
dateForm.multipleList = props.auto ? [date] : [];
|
||||
dateForm.currentDay = date;
|
||||
}
|
||||
}
|
||||
// 操作:点击了某天
|
||||
function handleClickDay(item: any) {
|
||||
if (!item || !item.isCurrentMonth || item.disabled) return;
|
||||
if (props.canSeletion.length) {
|
||||
if (!props.canSeletion.includes(item.date)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 如果支持多选
|
||||
if (props.multiple) {
|
||||
if (dateForm.multipleList.includes(item.date)) {
|
||||
dateForm.multipleList = dateForm.multipleList.filter(date => date !== item.date);
|
||||
} else {
|
||||
if (dateForm.multipleList.length < props.limit || !props.limit) {
|
||||
dateForm.multipleList.push(item.date);
|
||||
}
|
||||
}
|
||||
emits('selection', dateForm.multipleList);
|
||||
} else {
|
||||
dateForm.multipleList = [item.date];
|
||||
dateForm.currentDay = item.date;
|
||||
}
|
||||
emits('selectedDay', item);
|
||||
}
|
||||
defineExpose({
|
||||
prevMonth,
|
||||
nextMonth,
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.calendar-box {
|
||||
width: 690rpx;
|
||||
min-height: 637rpx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
// background: #fff;
|
||||
font-size: 28rpx;
|
||||
margin: 0 auto;
|
||||
.calendar-wrapper {
|
||||
border-radius: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
.calendar-toolbar {
|
||||
width: 690rpx;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.steps {
|
||||
display: flex;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.steps .step {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #615dff;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.prev,
|
||||
.next,
|
||||
.current {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
margin-left: 20rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
.van-icon-arrow-down {
|
||||
vertical-align: -15%;
|
||||
}
|
||||
}
|
||||
.calendar-week {
|
||||
width: 690rpx;
|
||||
border-bottom: 1rpx solid #e8e8e8;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
// justify-content: space-around;
|
||||
.week-item {
|
||||
width: calc(100% / 7);
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
.calendar-inner {
|
||||
width: 690rpx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 20rpx;
|
||||
.calendar-item {
|
||||
width: calc(100% / 7);
|
||||
min-height: 112rpx;
|
||||
position: relative;
|
||||
border-left: 1rpx solid #e8e8e8;
|
||||
border-bottom: 1rpx solid #e8e8e8;
|
||||
box-sizing: border-box;
|
||||
&.disabled {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
.calendar-item:nth-child(7n) {
|
||||
border-right: 1rpx solid #e8e8e8;
|
||||
}
|
||||
// .calendar-item:nth-child(7n+1){
|
||||
// border-left: 0;
|
||||
// }
|
||||
.calendar-item-hover {
|
||||
cursor: pointer;
|
||||
//&:hover {
|
||||
// color: #000;
|
||||
// border-radius: 40rpx;
|
||||
// background: #ffffff;
|
||||
// box-shadow: 0 2rpx 30rpx 0 rgba(137, 138, 255, 0.55);
|
||||
//}
|
||||
}
|
||||
.calendar-item-disabled {
|
||||
color: #ccc !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.calendar-item-checked {
|
||||
color: #615dff !important;
|
||||
background-color: #f0f0ff;
|
||||
}
|
||||
.calendar-item-current {
|
||||
color: #615dff !important;
|
||||
}
|
||||
.date {
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
line-height: 44rpx;
|
||||
color: #0a1f13;
|
||||
}
|
||||
}
|
||||
|
||||
.calendarBorder {
|
||||
text-align: center;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
// border-bottom: 1px solid #eee;
|
||||
// border-right: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
.van-popup--bottom.van-popup--round {
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
}
|
||||
</style>
|
44
src/components/dmgIcon/index.vue
Normal file
44
src/components/dmgIcon/index.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string;
|
||||
size?: string | number;
|
||||
color?: string;
|
||||
img?: boolean;
|
||||
}>(),
|
||||
{ img: false, size: '40rpx' },
|
||||
);
|
||||
|
||||
const emit = defineEmits(['click']);
|
||||
|
||||
defineOptions({
|
||||
options: {
|
||||
virtualHost: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<nut-icon
|
||||
v-if="props.img"
|
||||
:name="`https://box-1313840333.cos.ap-guangzhou.myqcloud.com/mobole-terminal/icon/${props.name}.svg`"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: anyObj) => emit('click', e)"
|
||||
></nut-icon>
|
||||
<nut-icon
|
||||
v-else
|
||||
font-class-name="iconfont"
|
||||
class-prefix="icon"
|
||||
:name="props.name"
|
||||
:custom-color="props.color"
|
||||
:size="props.size"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: anyObj) => emit('click', e)"
|
||||
></nut-icon>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.iconfont {
|
||||
color: $uni-text-color;
|
||||
}
|
||||
</style>
|
5
src/components/echarts-add-lower/add-lower.ts
Normal file
5
src/components/echarts-add-lower/add-lower.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface AddLowerDataItem {
|
||||
text: string;
|
||||
value: number;
|
||||
isAdd: boolean;
|
||||
}
|
118
src/components/echarts-add-lower/index.vue
Normal file
118
src/components/echarts-add-lower/index.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="type-view">
|
||||
<view class="type-view-increase">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16"
|
||||
fill="none"
|
||||
>
|
||||
<rect x="1.5" y="4.5" width="29" height="7" fill="#3FD15F" stroke="white" />
|
||||
</svg>
|
||||
增加
|
||||
</view>
|
||||
<view class="type-view-lower">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16"
|
||||
fill="none"
|
||||
>
|
||||
<rect x="1.5" y="4.5" width="29" height="7" fill="#FF8074" stroke="white" />
|
||||
</svg>
|
||||
减少
|
||||
</view>
|
||||
</view>
|
||||
<view :id="htmlId" :style="{ width: '100%', height: `400rpx` }"></view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
import { nextTick, watchEffect } from 'vue';
|
||||
import type { AddLowerDataItem } from '@/components/echarts-add-lower/add-lower';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
htmlId: string;
|
||||
data: AddLowerDataItem[];
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
const initEcharts = async () => {
|
||||
const myChart = echarts.init(document.getElementById(props.htmlId));
|
||||
console.log('myChart', myChart);
|
||||
const option = {
|
||||
grid: {
|
||||
left: '40px',
|
||||
right: '20px',
|
||||
top: '20',
|
||||
bottom: '20',
|
||||
},
|
||||
legend: {
|
||||
data: ['增加', '减少'],
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.data.map(item => {
|
||||
return item.text;
|
||||
}),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '40',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
valueAnimation: true,
|
||||
},
|
||||
data: props.data.map((item: AddLowerDataItem) => {
|
||||
return {
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: item.isAdd ? '#3FD15F' : '#FF8074',
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart && myChart.setOption(option);
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.data && props.data.length > 0) {
|
||||
console.log('props.data', props.data);
|
||||
nextTick(() => {
|
||||
initEcharts();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.k-g-s-title {
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 48rpx; /* 150% */
|
||||
}
|
||||
|
||||
.type-view {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx 0;
|
||||
|
||||
svg {
|
||||
width: 32rpx;
|
||||
height: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
108
src/components/echarts-circle/index.vue
Normal file
108
src/components/echarts-circle/index.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view
|
||||
:id="htmlId"
|
||||
ref="echartsRef"
|
||||
:style="{ width: '100%', height: `${height ? height : data.length * 100}rpx` }"
|
||||
@click="viewClick"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
htmlId: string;
|
||||
data: DataItem[];
|
||||
height?: number;
|
||||
legendVisible?: boolean;
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
|
||||
interface DataItem {
|
||||
value: number;
|
||||
name: string;
|
||||
color?: string;
|
||||
labelVisible?: boolean;
|
||||
labelPosition?: string;
|
||||
labelFormatter?: (param: any) => string;
|
||||
}
|
||||
|
||||
const myChart = ref<echarts.ECharts | null>(null);
|
||||
|
||||
const colors = [
|
||||
'#FBCA85',
|
||||
'#F5EB82',
|
||||
'#FB8F85',
|
||||
'#82F58B',
|
||||
'#85FBDE',
|
||||
'#85DEFB',
|
||||
'#9FA7FB',
|
||||
'#D59FFB',
|
||||
'#FB9FD5',
|
||||
];
|
||||
|
||||
const viewClick = (e: MouseEvent) => {};
|
||||
|
||||
const seriesData = computed(() => {
|
||||
return props.data.map((item, index) => {
|
||||
return {
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
itemStyle: {
|
||||
color: item.color ?? colors[index],
|
||||
},
|
||||
label: {
|
||||
show: item.labelVisible ?? true,
|
||||
formatter: (param: any) => {
|
||||
if (item.labelFormatter && typeof item.labelFormatter === 'function') {
|
||||
return item.labelFormatter(param);
|
||||
}
|
||||
return `${param.value}%`;
|
||||
},
|
||||
color: colors[index],
|
||||
position: item.labelPosition ?? 'outside',
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const options = computed(() => {
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: props.legendVisible ?? {
|
||||
bottom: '0%',
|
||||
left: 'center',
|
||||
icon: 'circle',
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['50%', '40%'],
|
||||
startAngle: 0,
|
||||
endAngle: 360,
|
||||
data: seriesData.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const initEcharts = async () => {
|
||||
myChart.value = echarts.init(document.getElementById(props.htmlId));
|
||||
|
||||
myChart.value && myChart.value.setOption(options.value);
|
||||
};
|
||||
onMounted(() => {
|
||||
initEcharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
110
src/components/echarts-line/index.vue
Normal file
110
src/components/echarts-line/index.vue
Normal file
@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view
|
||||
:id="htmlId"
|
||||
ref="echartsRef"
|
||||
:style="{ width: '100%', height: `${yData[0]?.data?.length * 60}rpx` }"
|
||||
@click="viewClick"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
import {computed, markRaw, onMounted, ref} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
htmlId: String,
|
||||
yData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
let myChart = ref();
|
||||
|
||||
const colors = [
|
||||
'#FBCA85',
|
||||
'#F5EB82',
|
||||
'#FB8F85',
|
||||
'#82F58B',
|
||||
'#85FBDE',
|
||||
'#85DEFB',
|
||||
'#9FA7FB',
|
||||
'#D59FFB',
|
||||
'#FB9FD5',
|
||||
];
|
||||
|
||||
const viewClick = (e: MouseEvent) => {
|
||||
// myChart.value.dispatchAction({
|
||||
// type: 'showTip',
|
||||
// seriesIndex:0,
|
||||
// dataIndex: 0,
|
||||
// });
|
||||
};
|
||||
|
||||
const seriesData = computed(() => {
|
||||
return props.yData.map((item, index) => {
|
||||
return {
|
||||
data: item.data.map((value, i) => {
|
||||
return {
|
||||
value,
|
||||
itemStyle: {
|
||||
color: colors[i],
|
||||
},
|
||||
};
|
||||
}),
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
name: item.name,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const legendData = computed(() => {
|
||||
return props.yData.map(item => item.name);
|
||||
});
|
||||
|
||||
const initEcharts = async () => {
|
||||
myChart.value = echarts.init(document.getElementById(props.htmlId),undefined,{
|
||||
renderer:'svg'
|
||||
});
|
||||
const option = {
|
||||
tooltip: {
|
||||
// trigger: 'axis',
|
||||
triggerOn: 'click',
|
||||
padding: 10,
|
||||
backgroundColor: '#ccc',
|
||||
borderColor: '#aaa',
|
||||
borderWidth: 1
|
||||
},
|
||||
legend: {
|
||||
data: legendData.value,
|
||||
},
|
||||
grid: {
|
||||
left: '40px',
|
||||
right: '20px',
|
||||
top: '40',
|
||||
bottom: '20',
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: props.xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: seriesData.value,
|
||||
};
|
||||
myChart.value && myChart.value.setOption(option);
|
||||
};
|
||||
onMounted(() => {
|
||||
initEcharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
85
src/components/horizontal-chart/index.vue
Normal file
85
src/components/horizontal-chart/index.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<view
|
||||
:id="htmlId"
|
||||
ref="echartsRef"
|
||||
:style="{ width: '100%', height: `${yData.length * 100}rpx` }"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
htmlId: String,
|
||||
yData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const echartsRef = ref(null);
|
||||
|
||||
const colors = [
|
||||
'#FBCA85',
|
||||
'#F5EB82',
|
||||
'#FB8F85',
|
||||
'#82F58B',
|
||||
'#85FBDE',
|
||||
'#85DEFB',
|
||||
'#9FA7FB',
|
||||
'#D59FFB',
|
||||
'#FB9FD5',
|
||||
];
|
||||
|
||||
const seriesData = computed(() => {
|
||||
return props.xData.map((item, index) => {
|
||||
return {
|
||||
value: item,
|
||||
itemStyle: {
|
||||
color: colors[index],
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const initEcharts = async () => {
|
||||
const myChart = echarts.init(document.getElementById(props.htmlId));
|
||||
const option = {
|
||||
grid: {
|
||||
left: '40px',
|
||||
right: '20px',
|
||||
top: '0%',
|
||||
bottom: '20',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: props.yData,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// 460, 280, 200, 260, 560, 280, 880, 590, 500
|
||||
data: seriesData.value,
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
valueAnimation: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
myChart && myChart.setOption(option);
|
||||
};
|
||||
onMounted(() => {
|
||||
initEcharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
30
src/components/inspector-no-data/index.vue
Normal file
30
src/components/inspector-no-data/index.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<view class="not-data">
|
||||
<image :src="notInspector" alt="" />
|
||||
<view>{{ text }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import notInspector from '@/static/svg/not-inspector.svg';
|
||||
|
||||
defineProps({
|
||||
text: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.not-data {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
& > image {
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
141
src/components/inspector/isp-data-load.vue
Normal file
141
src/components/inspector/isp-data-load.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<view class="load-view">
|
||||
<view v-if="loading" class="load-view-loading">
|
||||
<wd-loading size="40" color="#999999"></wd-loading>
|
||||
<!-- <view class="load-text">{{ loadText }}</view>-->
|
||||
<div class="loading-text">
|
||||
加载中
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</view>
|
||||
<view v-if="empty && !loading" class="not-data">
|
||||
<image :src="notInspector" alt="" />
|
||||
<view>{{ emptyText }}</view>
|
||||
</view>
|
||||
<view v-else-if="!empty">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import notInspector from '@/static/svg/not-inspector.svg';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
emptyText?: string;
|
||||
loading: boolean;
|
||||
empty: boolean;
|
||||
}>(),
|
||||
{
|
||||
emptyText: '暂无数据',
|
||||
empty: true,
|
||||
},
|
||||
);
|
||||
|
||||
const loadText = ref('加载中...');
|
||||
let loadInterval = ref(undefined);
|
||||
watchEffect(() => {
|
||||
const loading = props.loading;
|
||||
if (loading) {
|
||||
const text = '加载中';
|
||||
const dots = ['.', '..', '...'];
|
||||
let index = 0;
|
||||
loadInterval.value = setInterval(() => {
|
||||
index = index === 2 ? 0 : index + 1;
|
||||
loadText.value = text + dots[index];
|
||||
}, 500);
|
||||
} else {
|
||||
clearInterval(loadInterval.value);
|
||||
loadInterval.value = undefined;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.load-view {
|
||||
position: relative;
|
||||
|
||||
&-loading {
|
||||
width: 750rpx;
|
||||
min-height: 300rpx;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
place-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
.not-data {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
||||
& > image {
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
//width: 100rpx;
|
||||
font-size: 24rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
color: #999999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.loading-text span {
|
||||
display: inline-block;
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
margin-left: 2rpx;
|
||||
background: #999999; /* 点的颜色 */
|
||||
border-radius: 50%;
|
||||
animation: loading 1.5s linear infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.loading-text span:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
//background-color: red;
|
||||
}
|
||||
|
||||
.loading-text span:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
//background-color: red;
|
||||
}
|
||||
|
||||
.loading-text span:nth-child(3) {
|
||||
animation-delay: 1s;
|
||||
//background-color: red;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
41
src/components/inspector/isp-load-more.vue
Normal file
41
src/components/inspector/isp-load-more.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<wd-loadmore :state="state" @reload="loadmore" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { onReachBottom } from '@dcloudio/uni-app';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: any;
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
const emit = defineEmits(['reload']);
|
||||
|
||||
const state = ref('loading');
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.data && props.data.records?.length < props.data?.size) {
|
||||
state.value = 'finished';
|
||||
} else {
|
||||
state.value = 'loading';
|
||||
}
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
loadmore();
|
||||
// if (state.value === 'loading') return;
|
||||
});
|
||||
|
||||
const loadmore = () => {
|
||||
// state.value = 'loading';
|
||||
emit('reload');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
78
src/components/inspector/isp-menu.vue
Normal file
78
src/components/inspector/isp-menu.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<wd-tabbar
|
||||
v-model="tabbar"
|
||||
fixed
|
||||
safeAreaInsetBottom
|
||||
placeholder
|
||||
:z-index="9"
|
||||
active-color="#625EFF"
|
||||
inactive-color="#8E8D9D"
|
||||
@change="tabbarChange"
|
||||
>
|
||||
<wd-tabbar-item
|
||||
v-for="menu in menus"
|
||||
:key="menu.key"
|
||||
:name="menu.key"
|
||||
:title="menu.title"
|
||||
:icon="menu.icon"
|
||||
>
|
||||
<template #icon>
|
||||
<wd-img
|
||||
round
|
||||
height="50rpx"
|
||||
width="50rpx"
|
||||
:src="tabbar === menu.key ? menu.activeIcon : menu.icon"
|
||||
></wd-img>
|
||||
</template>
|
||||
</wd-tabbar-item>
|
||||
</wd-tabbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
currentMenu: string;
|
||||
}>();
|
||||
|
||||
const tabbar = ref('mine');
|
||||
|
||||
const menus = [
|
||||
{
|
||||
title: '课表',
|
||||
key: 'schedule',
|
||||
icon: '/static/tabBar/scheduleNo.png',
|
||||
activeIcon: '/static/tabBar/schedule.png',
|
||||
url: '/pages/inspector/schedule/index',
|
||||
},
|
||||
{
|
||||
title: '学员',
|
||||
key: 'student',
|
||||
icon: '/static/tabBar/studentNo.png',
|
||||
activeIcon: '/static/tabBar/student.png',
|
||||
url: '/pages/inspector/student/index',
|
||||
},
|
||||
{
|
||||
title: '我的',
|
||||
key: 'mine',
|
||||
icon: '/static/tabBar/mineno.png',
|
||||
activeIcon: '/static/tabBar/mine.png',
|
||||
url: '/pages/inspector/mine/index',
|
||||
},
|
||||
];
|
||||
|
||||
watchEffect(() => {
|
||||
tabbar.value = menus.find(menu => menu.key === props.currentMenu)?.key ?? 'mine';
|
||||
});
|
||||
|
||||
const tabbarChange = ({ value }) => {
|
||||
console.log(value, 'value');
|
||||
if (value === props.currentMenu) {
|
||||
return;
|
||||
}
|
||||
const menu = menus.find(menu => menu.key === value);
|
||||
uni.redirectTo({
|
||||
url: menu.url,
|
||||
});
|
||||
};
|
||||
</script>
|
119
src/components/inspector/isp-plan-item.vue
Normal file
119
src/components/inspector/isp-plan-item.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="plan-view-item">
|
||||
<view class="order-or-finished">
|
||||
<image
|
||||
v-if="plan.finished"
|
||||
:src="`${OSS_URL}/duxueshi/finished_icon.png`"
|
||||
class="finised_icon"
|
||||
/>
|
||||
<text v-else class="order">{{ plan.order < 10 ? `0${plan.order}` : plan.order }}</text>
|
||||
</view>
|
||||
<view class="plan-view-item-content">
|
||||
<view class="plan-view-item-content-info">
|
||||
<view>
|
||||
<text class="course-name">{{ plan.courseName }}</text>
|
||||
<text class="tag" :style="{ backgroundColor: plan.bgColor }">{{ plan.label }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text :style="{ color: plan.finished ? '#21d17a' : '#FF2828' }">{{
|
||||
plan.progressText
|
||||
}}</text>
|
||||
<text>|</text>
|
||||
<text>{{ plan.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-intro">{{ plan.note }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PlanItem } from '@/components/inspector/plan-item';
|
||||
defineProps<{
|
||||
plan: PlanItem;
|
||||
}>();
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.plan-view-item {
|
||||
display: flex;
|
||||
padding: 20rpx 16rpx 0 16rpx;
|
||||
//align-items: flex-start;
|
||||
// gap: 20rpx;
|
||||
//align-self: stretch;
|
||||
|
||||
.order-or-finished {
|
||||
width: 46rpx;
|
||||
margin-right: 20rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 46rpx; /* 153.333% */
|
||||
|
||||
.finised_icon {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
}
|
||||
.order {
|
||||
display: block;
|
||||
width: 46rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
width: calc(100% - 66rpx);
|
||||
border-bottom: 2rpx solid #ececec;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 0 10rpx;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
line-height: 30rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
// gap: 10rpx;
|
||||
|
||||
& > view {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
align-items: center;
|
||||
|
||||
& > text {
|
||||
font-size: 24rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
color: #a9aab5;
|
||||
|
||||
&.course-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #0a1f13;
|
||||
}
|
||||
|
||||
&.progress-rate {
|
||||
color: #21d17a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-intro {
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
margin-top: 10rpx;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
43
src/components/inspector/plan-item.ts
Normal file
43
src/components/inspector/plan-item.ts
Normal file
@ -0,0 +1,43 @@
|
||||
export interface PlanItem {
|
||||
order: number;
|
||||
finished: boolean;
|
||||
courseName: string;
|
||||
label: string;
|
||||
progressText: string;
|
||||
time: string;
|
||||
note: string;
|
||||
bgColor?: string;
|
||||
}
|
||||
|
||||
export const TASK_TYPE = {
|
||||
'0': {
|
||||
label: '自由学',
|
||||
notePrefix: `观看学习视频:《#{chapterName}》章节《#{courseTimeName}》视频`,
|
||||
bgColor: '#FFA65D',
|
||||
},
|
||||
'1': {
|
||||
label: '自由学',
|
||||
notePrefix: '完成章节练习:《#{chapterName}》',
|
||||
bgColor: '#FFA65D',
|
||||
},
|
||||
'2': {
|
||||
label: '精准学',
|
||||
notePrefix: 'AI精准学#{finishedNum}次',
|
||||
bgColor: '#4FD6EE',
|
||||
},
|
||||
'3': {
|
||||
label: '语感训练',
|
||||
notePrefix: '语感训练#{finishedNum}次',
|
||||
bgColor: '#B4B3FF',
|
||||
},
|
||||
'4': {
|
||||
label: '错题本',
|
||||
notePrefix: '订正错题本#{finishedNum}道',
|
||||
},
|
||||
};
|
||||
|
||||
export function formatTaskTypeNotePrefix(notePrefix, values, defaultValue = 0) {
|
||||
return notePrefix.replace(/#{(.*?)}/g, (match, key) => {
|
||||
return values[key] || defaultValue;
|
||||
});
|
||||
}
|
155
src/components/student-calendar/index.vue
Normal file
155
src/components/student-calendar/index.vue
Normal file
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<view class="calendar-main">
|
||||
<view class="month-view">
|
||||
<wd-icon name="arrow-left" size="40rpx" @click="preMonth"></wd-icon>
|
||||
<view class="month-text">{{ monthFirstDay.format('YYYY年MM月') }}</view>
|
||||
<wd-icon name="arrow-right" size="40rpx" @click="nextMonth"></wd-icon>
|
||||
</view>
|
||||
<view class="days-view">
|
||||
<view class="days-view-weeks">
|
||||
<view v-for="week in weeks" :key="week">{{ week }}</view>
|
||||
</view>
|
||||
<view class="days-view-day">
|
||||
<view v-for="(weekDays, index) in dayArray" :key="index" class="week-day">
|
||||
<view
|
||||
v-for="day in weekDays"
|
||||
:key="day"
|
||||
:class="[
|
||||
'day-item',
|
||||
{
|
||||
'not-current': !day.isCurrentMonth,
|
||||
'is-active': value === day.date,
|
||||
'current-day': day.date === dayjs().format('YYYY-MM-DD'),
|
||||
},
|
||||
]"
|
||||
@click="chooseDay(day)"
|
||||
>
|
||||
<view class="day-text">{{ day.day }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { type CalendarDay, generateMonthCalendar } from '@/utils/calendar';
|
||||
|
||||
defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: dayjs().format('YYYY-MM-DD'),
|
||||
},
|
||||
});
|
||||
|
||||
const _emit = defineEmits(['update:value']);
|
||||
|
||||
const monthFirstDay = ref(dayjs().startOf('month'));
|
||||
|
||||
const weeks = ['一', '二', '三', '四', '五', '六', '日'];
|
||||
|
||||
const dayArray = computed(() => {
|
||||
return generateMonthCalendar(monthFirstDay.value.toDate());
|
||||
});
|
||||
|
||||
const chooseDay = (_day: CalendarDay) => {
|
||||
_emit('update:value', _day.date);
|
||||
};
|
||||
|
||||
const preMonth = () => {
|
||||
monthFirstDay.value = monthFirstDay.value.subtract(1, 'month');
|
||||
};
|
||||
|
||||
const nextMonth = () => {
|
||||
monthFirstDay.value = monthFirstDay.value.add(1, 'month');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.calendar-main {
|
||||
}
|
||||
|
||||
.month-view {
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
display: flex;
|
||||
place-content: center;
|
||||
align-items: center;
|
||||
|
||||
.month-text {
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.days-view {
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-weeks {
|
||||
display: flex;
|
||||
border-bottom: 2rpx solid #cccccc;
|
||||
color: #999999;
|
||||
padding: 20rpx 0;
|
||||
|
||||
& > view {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-day {
|
||||
.week-day {
|
||||
display: flex;
|
||||
//border-bottom: 2rpx solid #cccccc;
|
||||
color: #999999;
|
||||
padding: 20rpx 0;
|
||||
|
||||
& > .day-item {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
&.not-current {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
& > .day-text {
|
||||
border: 4rpx solid #615dff;
|
||||
}
|
||||
}
|
||||
|
||||
&.current-day {
|
||||
&.is-active {
|
||||
& > .day-text {
|
||||
background-color: #615dff;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
|
||||
& > .day-text {
|
||||
color: #615dff;
|
||||
}
|
||||
}
|
||||
|
||||
& > .day-text {
|
||||
font-size: 26rpx;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
border-radius: 60rpx;
|
||||
font-weight: 550;
|
||||
border: 4rpx solid white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
180
src/components/student-report/horizontal-chart.vue
Normal file
180
src/components/student-report/horizontal-chart.vue
Normal file
@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<report-content-base :title="title">
|
||||
<isp-data-load :loading="loading" :empty-text="emptyText" :empty="yData.length === 0">
|
||||
<view class="charts-box" :style="{ height: `${height}rpx` }">
|
||||
<qiun-data-charts
|
||||
type="bar"
|
||||
canvas2d
|
||||
:opts="opts"
|
||||
:tooltipShow="false"
|
||||
:chartData="chartData"
|
||||
/>
|
||||
</view>
|
||||
</isp-data-load>
|
||||
<!-- <view :id="htmlId" ref="echartsRef" :style="{ width: '100%', height: `${height}rpx` }"></view> -->
|
||||
</report-content-base>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ReportContentBase from '@/pages/inspector/student-detail/components/report-content-base.vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { computed, nextTick, onMounted, ref, watchEffect } from 'vue';
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
emptyText: String,
|
||||
htmlId: String,
|
||||
yData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
xData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const opts = ref({
|
||||
padding: [0, 42, 0, 0],
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
position: 'top',
|
||||
padding: 2,
|
||||
show: false,
|
||||
},
|
||||
xAxis: {
|
||||
boundaryGap: 'justify',
|
||||
disableGrid: false,
|
||||
min: 0,
|
||||
max: 10,
|
||||
axisLine: false,
|
||||
labelCount: 4, // 标签数
|
||||
type: 'grid', // X轴类型
|
||||
gridType: 'dash', // X轴网格线类型
|
||||
gridColor: '#d4d4d4',
|
||||
splitNumber: 10,
|
||||
},
|
||||
yAxis: {},
|
||||
extra: {
|
||||
bar: {
|
||||
type: 'group',
|
||||
width: 25,
|
||||
meterBorde: 1,
|
||||
meterFillColor: '#FFFFFF',
|
||||
activeBgColor: '#000000',
|
||||
activeBgOpacity: 0.05,
|
||||
barBorderCircle: false,
|
||||
seriesGap: 2,
|
||||
categoryGap: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
const chartData = ref({
|
||||
categories: [] as any,
|
||||
series: [
|
||||
{
|
||||
data: [] as any,
|
||||
},
|
||||
],
|
||||
});
|
||||
const height = computed(() => {
|
||||
return props.yData.length * 120;
|
||||
});
|
||||
const loading = ref(false);
|
||||
watchEffect(() => {
|
||||
if (props.yData && props.yData.length > 0) {
|
||||
nextTick(() => {
|
||||
chartData.value.categories = props.yData;
|
||||
for (let i in props.xData) {
|
||||
chartData.value.series[0].data[i] = props.xData[i];
|
||||
}
|
||||
console.log('props.yData', props.yData, props.xData);
|
||||
});
|
||||
}
|
||||
});
|
||||
// const props = defineProps({
|
||||
// title: String,
|
||||
// htmlId: String,
|
||||
// yData: {
|
||||
// type: Array,
|
||||
// default: () => [],
|
||||
// },
|
||||
// xData: {
|
||||
// type: Array,
|
||||
// default: () => [],
|
||||
// },
|
||||
// });
|
||||
// const echartsRef = ref(null);
|
||||
|
||||
// const colors = [
|
||||
// '#FBCA85',
|
||||
// '#F5EB82',
|
||||
// '#FB8F85',
|
||||
// '#82F58B',
|
||||
// '#85FBDE',
|
||||
// '#85DEFB',
|
||||
// '#9FA7FB',
|
||||
// '#D59FFB',
|
||||
// '#FB9FD5',
|
||||
// ];
|
||||
|
||||
// const height = computed(() => {
|
||||
// return props.yData.length * 100;
|
||||
// });
|
||||
|
||||
// const seriesData = computed(() => {
|
||||
// return props.xData.map((item, index) => {
|
||||
// return {
|
||||
// value: item,
|
||||
// itemStyle: {
|
||||
// color: colors[index],
|
||||
// },
|
||||
// };
|
||||
// });
|
||||
// });
|
||||
|
||||
// const initEcharts = async () => {
|
||||
// const myChart = echarts.init(document.getElementById(props.htmlId));
|
||||
// const option = {
|
||||
// grid: {
|
||||
// left: '40px',
|
||||
// right: '20px',
|
||||
// top: '0%',
|
||||
// bottom: '20',
|
||||
// },
|
||||
// xAxis: {
|
||||
// type: 'value',
|
||||
// },
|
||||
// yAxis: {
|
||||
// type: 'category',
|
||||
// data: props.yData,
|
||||
// },
|
||||
// series: [
|
||||
// {
|
||||
// data: props.xData.map((item, index) => {
|
||||
// return {
|
||||
// value: item,
|
||||
// itemStyle: {
|
||||
// color: colors[index],
|
||||
// },
|
||||
// };
|
||||
// }),
|
||||
// type: 'bar',
|
||||
// label: {
|
||||
// show: true,
|
||||
// position: 'right',
|
||||
// valueAnimation: true,
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// setTimeout(() => {
|
||||
// myChart.setOption(option);
|
||||
// }, 300);
|
||||
// };
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.not-data) {
|
||||
padding: 30rpx 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
</style>
|
87
src/components/student-report/index.vue
Normal file
87
src/components/student-report/index.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="report_box">
|
||||
<!-- 学习汇总 -->
|
||||
<study-summary :report="report" />
|
||||
<!-- 督学计划完成情况 -->
|
||||
<plan-finish-data :tasks="report.tasks ?? []" />
|
||||
<horizontal-chart
|
||||
title="各学科练习题数统计"
|
||||
emptyText="暂无练习数据"
|
||||
:x-data="practiceQuestionStatistics.xData"
|
||||
:y-data="practiceQuestionStatistics.yData"
|
||||
html-id="practise-chart"
|
||||
/>
|
||||
<horizontal-chart
|
||||
title="各学科观看视频统计"
|
||||
emptyText="暂无观看视频数据"
|
||||
:x-data="watchVideoStatistics.xData"
|
||||
:y-data="watchVideoStatistics.yData"
|
||||
html-id="video-see-chart"
|
||||
/>
|
||||
<!-- 知识图谱统计 -->
|
||||
<knowledge-graph-statistics :knowledge-graph-statistics="report.knowledgeGraphStatistics" />
|
||||
<!-- 已学知识点及掌握程度 -->
|
||||
<knowledge-mastery :learned-knowledge="report.learnedKnowledge || []" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StudySummary from '@/components/student-report/study-summary.vue';
|
||||
import PlanFinishData from '@/components/student-report/plan-finish-data.vue';
|
||||
import HorizontalChart from '@/components/student-report/horizontal-chart.vue';
|
||||
import KnowledgeGraphStatistics from '@/components/student-report/knowledge-graph-statistics.vue';
|
||||
import KnowledgeMastery from '@/components/student-report/knowledge-mastery.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
report: any;
|
||||
}>(),
|
||||
{
|
||||
report: {},
|
||||
},
|
||||
);
|
||||
|
||||
const colors = [
|
||||
'#FB9FD5',
|
||||
'#D59FFB',
|
||||
'#9FA7FB',
|
||||
'#85DEFB',
|
||||
'#85FBDE',
|
||||
'#FBCA85',
|
||||
'#FB8F85',
|
||||
'#82F58B',
|
||||
'#F5EB82',
|
||||
'#7a6bff',
|
||||
];
|
||||
// 练习题统计
|
||||
const practiceQuestionStatistics = computed(() => {
|
||||
const practiceQuestionStatistics = props.report.practiceQuestionStatistics ?? [];
|
||||
const yData = practiceQuestionStatistics.map((item: any) => item.subjectName);
|
||||
const xData = practiceQuestionStatistics.map((item: any) => {
|
||||
return { value: item.number, color: colors[Number(item.subjectId) - 1] };
|
||||
});
|
||||
return {
|
||||
yData,
|
||||
xData,
|
||||
};
|
||||
});
|
||||
// 观看视频统计
|
||||
const watchVideoStatistics = computed(() => {
|
||||
const watchVideoStatistics = props.report.watchVideoStatistics ?? [];
|
||||
const yData = watchVideoStatistics.map((item: any) => item.subjectName);
|
||||
const xData = watchVideoStatistics.map((item: any) => item.number);
|
||||
return {
|
||||
yData,
|
||||
xData,
|
||||
};
|
||||
});
|
||||
console.log('督学报告', props.report);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.report_box {
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
135
src/components/student-report/knowledge-graph-statistics.vue
Normal file
135
src/components/student-report/knowledge-graph-statistics.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<report-content-base>
|
||||
<view class="chart_title">
|
||||
<text class="txt">知识图谱统计</text>
|
||||
<view class="underline"></view>
|
||||
</view>
|
||||
<view class="k-g-s-title">我的知识图谱变化情况</view>
|
||||
<view class="chart_label">
|
||||
<view class="increase"><text class="iden_color"></text>增加</view>
|
||||
<view class="decrease"><text class="iden_color"></text>减少</view>
|
||||
</view>
|
||||
<ColumnChart :chartData="data" />
|
||||
<!-- <qy-echarts-add-lower
|
||||
v-if="data.length > 0"
|
||||
html-id="knowledge-graph-stats-echarts"
|
||||
:data="data"
|
||||
/> -->
|
||||
</report-content-base>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import ColumnChart from '@/pages/academicReport/components/Echart/ColumnChart.vue';
|
||||
import ReportContentBase from '@/pages/inspector/student-detail/components/report-content-base.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
knowledgeGraphStatistics: any;
|
||||
}>();
|
||||
|
||||
const data: any = computed(() => {
|
||||
if (!props.knowledgeGraphStatistics) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
categories: ['陌生数', '熟悉数', '掌握数'],
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
color: props.knowledgeGraphStatistics.unfamiliarCount < 0 ? '#ff8074' : '#3fd15f',
|
||||
value: Math.abs(props.knowledgeGraphStatistics.unfamiliarCount),
|
||||
},
|
||||
{
|
||||
color: props.knowledgeGraphStatistics.knowCount < 0 ? '#ff8074' : '#3fd15f',
|
||||
value: Math.abs(props.knowledgeGraphStatistics.knowCount),
|
||||
},
|
||||
{
|
||||
color: props.knowledgeGraphStatistics.graspCount < 0 ? '#ff8074' : '#3fd15f',
|
||||
value: Math.abs(props.knowledgeGraphStatistics.graspCount),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.chart_title {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 204rpx;
|
||||
height: 60rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
.txt {
|
||||
position: absolute;
|
||||
display: block;
|
||||
z-index: 99;
|
||||
}
|
||||
.underline {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 12rpx;
|
||||
background-color: #dad9ff;
|
||||
}
|
||||
}
|
||||
.chart_label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: rgba(102, 102, 102, 1);
|
||||
|
||||
.iden_color {
|
||||
display: inline-block;
|
||||
width: 30rpx;
|
||||
height: 8rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.increase {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
|
||||
.iden_color {
|
||||
background-color: #3fd15f;
|
||||
}
|
||||
}
|
||||
|
||||
.decrease {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iden_color {
|
||||
background-color: #ff8074;
|
||||
}
|
||||
}
|
||||
}
|
||||
.k-g-s-title {
|
||||
margin: 50rpx auto 30rpx;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 48rpx; /* 150% */
|
||||
}
|
||||
|
||||
.type-view {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx 0;
|
||||
|
||||
svg {
|
||||
width: 32rpx;
|
||||
height: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
51
src/components/student-report/knowledge-mastery.vue
Normal file
51
src/components/student-report/knowledge-mastery.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<report-content-base title="已学知识点及掌握程度">
|
||||
<qy-custom-table :columns="columns" :data="knowledgeStudyData" />
|
||||
</report-content-base>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ReportContentBase from '@/pages/inspector/student-detail/components/report-content-base.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
learnedKnowledge: LearnedKnowledgeItem[];
|
||||
}>(),
|
||||
{},
|
||||
);
|
||||
|
||||
interface LearnedKnowledgeItem {
|
||||
name: string;
|
||||
answerCount: number;
|
||||
masteryDegree: number;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
title: '知识点名称',
|
||||
},
|
||||
{
|
||||
key: 'frequency',
|
||||
title: '答题频次',
|
||||
},
|
||||
{
|
||||
key: 'mastery',
|
||||
title: '掌握程度',
|
||||
},
|
||||
];
|
||||
|
||||
const knowledgeStudyData = computed(() => {
|
||||
return (props.learnedKnowledge ?? []).map(item => {
|
||||
return {
|
||||
name: item.name,
|
||||
frequency: item.answerCount,
|
||||
// 1-陌生 2-熟悉 3-已掌握
|
||||
mastery: item.masteryDegree === 1 ? '陌生' : item.masteryDegree === 2 ? '熟悉' : '掌握',
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
88
src/components/student-report/plan-finish-data.vue
Normal file
88
src/components/student-report/plan-finish-data.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="finish-title">督学计划完成情况</view>
|
||||
<view class="plan-view">
|
||||
<isp-plan-item v-for="item in showTasks" :key="item" :plan="item" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatTaskTypeNotePrefix, TASK_TYPE } from '@/components/inspector/plan-item';
|
||||
|
||||
const props = defineProps({
|
||||
tasks: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const showTasks = computed(() => {
|
||||
return props.tasks.map((task: any, index) => {
|
||||
const _type = TASK_TYPE[task.taskType];
|
||||
return {
|
||||
order: index + 1,
|
||||
courseName: task.subjectName,
|
||||
label: _type.label,
|
||||
bgColor: _type.bgColor,
|
||||
finished: task.taskType === 0 ? Number(task.progress) > 0.8 : task.finishedNum >= task.num,
|
||||
progressText:
|
||||
task.taskType === 0
|
||||
? Number(task.progress) > 0.8
|
||||
? '1/1'
|
||||
: '0/1'
|
||||
: `${task.finishedNum}/${task.num}`,
|
||||
time: `${dayjs(task.inspectorTimeStart).format('HH:mm')}-${dayjs(
|
||||
task.inspectorTimeEnd,
|
||||
).format('HH:mm')}`,
|
||||
note: formatTaskTypeNotePrefix(_type.notePrefix, task),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// const plans = [
|
||||
// {
|
||||
// order: 1,
|
||||
// finished: false,
|
||||
// courseName: '数学',
|
||||
// label: '自由学',
|
||||
// progressText: '100%',
|
||||
// time: '19:00-19:20',
|
||||
// note: '观看学习视频:《第一章 空间向量与立体几何》',
|
||||
// },
|
||||
// {
|
||||
// order: 1,
|
||||
// finished: true,
|
||||
// courseName: '语文',
|
||||
// label: '自由学',
|
||||
// progressText: '100%',
|
||||
// time: '19:00-19:20',
|
||||
// note: '观看学习视频:《第一章 空间向量与立体几何》',
|
||||
// },
|
||||
// ];
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.finish-title {
|
||||
margin: 70rpx 0 30rpx 0;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 48rpx; /* 150% */
|
||||
}
|
||||
|
||||
.plan-view {
|
||||
background-color: white;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid #ececec;
|
||||
box-sizing: border-box;
|
||||
:deep(.plan-view-item:last-child) {
|
||||
.plan-view-item-content {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
0
src/components/student-report/student-report.ts
Normal file
0
src/components/student-report/student-report.ts
Normal file
140
src/components/student-report/study-summary.vue
Normal file
140
src/components/student-report/study-summary.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<view class="summary-view">
|
||||
<qy-up-down-flat v-for="(item, index) in studySummary" :key="index" :data="item" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { minToHour } from '@/utils';
|
||||
import { UpDownFlatState, UpDownFlatType } from '@/components/up-down-flat/index';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
report: any;
|
||||
}>(),
|
||||
{
|
||||
report: {},
|
||||
},
|
||||
);
|
||||
|
||||
const studySummary = computed(() => {
|
||||
const studyVideoState = [UpDownFlatState.FLAT, UpDownFlatState.UP, UpDownFlatState.DOWN];
|
||||
const exercisesState = [UpDownFlatState.FLAT, UpDownFlatState.UP, UpDownFlatState.DOWN];
|
||||
const correctnessState = [UpDownFlatState.FLAT, UpDownFlatState.UP, UpDownFlatState.DOWN];
|
||||
|
||||
return [
|
||||
{
|
||||
title: '已学视频',
|
||||
type: UpDownFlatType.NORMAL,
|
||||
value: [props.report.studyVideoCount],
|
||||
unit: '个',
|
||||
state: studyVideoState[props.report.studyVideoCountCompare],
|
||||
},
|
||||
{
|
||||
title: '练习题数',
|
||||
type: UpDownFlatType.NORMAL,
|
||||
value: [props.report.exercisesCount],
|
||||
unit: '道',
|
||||
state: exercisesState[props.report.exercisesCountCompare],
|
||||
},
|
||||
{
|
||||
title: '答题正确率',
|
||||
type: UpDownFlatType.NORMAL,
|
||||
value: [`${props.report.correctness}%`],
|
||||
state:
|
||||
props.report.correctnessCompare === -1
|
||||
? UpDownFlatState.DOWN
|
||||
: correctnessState[props.report.correctnessCompare],
|
||||
},
|
||||
{
|
||||
title: '实际督学时长',
|
||||
type: UpDownFlatType.TIME,
|
||||
value: minToHour(props.report.inspectorTimeMinute),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.summary-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&-item {
|
||||
width: 305rpx;
|
||||
height: 155rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f7faff;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&-title {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
height: 76rpx;
|
||||
background-color: #ebf2ff;
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
height: 79rpx;
|
||||
align-items: center;
|
||||
place-content: center;
|
||||
|
||||
.normal-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.value-text {
|
||||
font-size: 28rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.up-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/up.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.up-color {
|
||||
color: #3fd15f;
|
||||
}
|
||||
|
||||
.down-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/decline.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.down-color {
|
||||
color: #ff2828;
|
||||
}
|
||||
|
||||
.flat-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/flat.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.flat-color {
|
||||
color: #ff8d5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
72
src/components/svgs/calendar-icon/index.vue
Normal file
72
src/components/svgs/calendar-icon/index.vue
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M3.125 11.875H26.875V25C26.875 25.6904 26.3154 26.25 25.625 26.25H4.375C3.68464 26.25 3.125 25.6904 3.125 25V11.875Z"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.125 5.625C3.125 4.93464 3.68464 4.375 4.375 4.375H25.625C26.3154 4.375 26.875 4.93464 26.875 5.625V11.875H3.125V5.625Z"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 2.5V7.5"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M20 2.5V7.5"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.5 21.25H21.25"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.75 21.25H12.5"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.5 16.25H21.25"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.75 16.25H12.5"
|
||||
stroke="#666666"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
22
src/components/svgs/edit/index.vue
Normal file
22
src/components/svgs/edit/index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none">
|
||||
<path
|
||||
d="M4.375 26.25H26.875"
|
||||
stroke="#DEDFE9"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M6.875 16.6999V21.25H11.4482L24.375 8.31756L19.8094 3.75L6.875 16.6999Z"
|
||||
fill="#DEDFE9"
|
||||
stroke="#DEDFE9"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
14
src/components/svgs/inverted-triangle/index.vue
Normal file
14
src/components/svgs/inverted-triangle/index.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none">
|
||||
<path
|
||||
d="M22.5 11.875L15 19.375L7.5 11.875H22.5Z"
|
||||
fill="#DEDFE9"
|
||||
stroke="#DEDFE9"
|
||||
stroke-width="2.5"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
50
src/components/svgs/svgs-add-plan.vue
Normal file
50
src/components/svgs/svgs-add-plan.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 120 120"
|
||||
fill="none"
|
||||
>
|
||||
<g filter="url(#filter0_d_251_23854)">
|
||||
<circle cx="60" cy="60" r="50" fill="white" />
|
||||
</g>
|
||||
<rect x="40" y="57" width="40" height="6" rx="3" fill="#615DFF" />
|
||||
<rect x="57" y="80" width="40" height="6" rx="3" transform="rotate(-90 57 80)" fill="#615DFF" />
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_251_23854"
|
||||
x="0"
|
||||
y="0"
|
||||
width="120"
|
||||
height="120"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.89083 0 0 0 0 0.90362 0 0 0 0 0.929199 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_251_23854" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_251_23854"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<style scoped lang="scss"></style>
|
18
src/components/up-down-flat/index.ts
Normal file
18
src/components/up-down-flat/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export enum UpDownFlatType {
|
||||
NORMAL = 'normal',
|
||||
TIME = 'time',
|
||||
}
|
||||
|
||||
export enum UpDownFlatState {
|
||||
UP = 'up',
|
||||
DOWN = 'down',
|
||||
FLAT = 'flat',
|
||||
}
|
||||
|
||||
export interface UpDownFlat {
|
||||
title: string;
|
||||
value: any[];
|
||||
unit: string;
|
||||
type: UpDownFlatType;
|
||||
state: UpDownFlatState;
|
||||
}
|
133
src/components/up-down-flat/index.vue
Normal file
133
src/components/up-down-flat/index.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<view class="summary-view-item">
|
||||
<view class="summary-view-item-title">{{ data.title }}</view>
|
||||
<view v-if="data.type === 'normal'" class="summary-view-item-content">
|
||||
<text :class="['value-text', state.color]">{{ data.value[0] }}</text>
|
||||
<text class="normal-text">{{ data.unit }}</text>
|
||||
<text v-if="state.icon !== 'none'" :class="state.icon"></text>
|
||||
</view>
|
||||
|
||||
<view v-else class="summary-view-item-content">
|
||||
<text :class="['value-text', state.color]">{{ data.value[0] }}</text>
|
||||
<text class="normal-text">时</text>
|
||||
<text :class="['value-text', state.color]">{{ data.value[1] }}</text>
|
||||
<text class="normal-text">分</text>
|
||||
<text v-if="state.icon !== 'none'" :class="state.icon"></text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { UpDownFlat } from '@/components/up-down-flat/index';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
data: UpDownFlat;
|
||||
}>();
|
||||
|
||||
// const { value } = props.data;
|
||||
|
||||
const states = {
|
||||
up: {
|
||||
icon: 'up-icon',
|
||||
color: 'up-color',
|
||||
},
|
||||
down: {
|
||||
icon: 'down-icon',
|
||||
color: 'down-color',
|
||||
},
|
||||
flat: {
|
||||
icon: 'flat-icon',
|
||||
color: 'flat-color',
|
||||
},
|
||||
default: {
|
||||
icon: 'none',
|
||||
color: 'default-color',
|
||||
},
|
||||
};
|
||||
|
||||
const state = computed(() => {
|
||||
const _state = props.data.state ?? 'default';
|
||||
return states[_state];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.summary-view-item {
|
||||
width: 100%;
|
||||
height: 155rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f7faff;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&-title {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
height: 76rpx;
|
||||
background-color: #ebf2ff;
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
height: 79rpx;
|
||||
align-items: center;
|
||||
place-content: center;
|
||||
|
||||
.normal-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.value-text {
|
||||
font-size: 28rpx;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.up-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/up.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.up-color {
|
||||
color: #3fd15f;
|
||||
}
|
||||
|
||||
.down-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/decline.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.down-color {
|
||||
color: #ff2828;
|
||||
}
|
||||
|
||||
.flat-icon {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
background: url('~@/static/svg/flat.svg') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.flat-color {
|
||||
color: #ff8d5f;
|
||||
}
|
||||
|
||||
.default-color {
|
||||
color: #615dff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
8
src/env.d.ts
vendored
Normal file
8
src/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
2
src/hooks/index.ts
Normal file
2
src/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './useKey';
|
||||
export * from './useTools';
|
97
src/hooks/useGlobal.js
Normal file
97
src/hooks/useGlobal.js
Normal file
@ -0,0 +1,97 @@
|
||||
// import qs from 'qs';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// function formatUrlWithParams(url, params) {
|
||||
// return `${url}?${qs.stringify(params, { encode: false })}`;
|
||||
// }
|
||||
|
||||
/**
|
||||
* uni 页面跳转修改,支持传入对象参数,保留当前页面,跳转到应用内的某个页面
|
||||
* @param url uni 跳转的页面
|
||||
* @param params 参数
|
||||
*/
|
||||
function navigateTo(url, params = {}) {
|
||||
uni.navigateTo({
|
||||
url: formatUrlWithParams(url, params)
|
||||
});
|
||||
}
|
||||
//修改时间格式
|
||||
export const formatSecond = (val) => {
|
||||
let time = Number(val)
|
||||
if (time > 3600) {
|
||||
// 大于一个小时
|
||||
let h = parseInt(time / 60 / 60 % 24)
|
||||
h = h > 0 ? h + '小时' : ''
|
||||
let m = parseInt(time / 60 % 60)
|
||||
m = m > 0 ? m + '分钟' : ''
|
||||
return h + m
|
||||
} else if (time < 3600 && time > 60) {
|
||||
let m = parseInt(time / 60 % 60)
|
||||
m = m > 0 ? m + '分钟' : ''
|
||||
let s = parseInt(time % 60)
|
||||
s = s > 0 ? s + '秒' : ''
|
||||
return m + s
|
||||
} else if (time < 60 && time > 0) {
|
||||
return time + '秒'
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
export const formatSecondEn = (val) => {
|
||||
let time = Number(val)
|
||||
if (time > 3600) {
|
||||
// 大于一个小时
|
||||
let h = parseInt(time / 60 / 60 % 24)
|
||||
h = h > 0 ? h + 'h' : ''
|
||||
let m = parseInt(time / 60 % 60)
|
||||
m = m > 0 ? m + 'm' : ''
|
||||
return h + m
|
||||
} else if (time < 3600 && time > 60) {
|
||||
let m = parseInt(time / 60 % 60)
|
||||
m = m > 0 ? m + 'm' : ''
|
||||
let s = parseInt(time % 60)
|
||||
s = s > 0 ? s + 's' : ''
|
||||
return m + s
|
||||
} else if (time < 60 && time > 0) {
|
||||
return time + 's'
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
// 2024-01-21转1.21
|
||||
export const formatDate = (dateString) => {
|
||||
// 将字符串格式的日期转换成Date对象
|
||||
var date = new Date(dateString);
|
||||
// 获取月份和日期
|
||||
var month = date.getMonth() + 1; // 月份从0开始,所以要加1
|
||||
var day = date.getDate();
|
||||
// 拼接月份和日期并返回结果
|
||||
var formattedDate = month + '.' + day;
|
||||
return formattedDate;
|
||||
}
|
||||
/**
|
||||
* 时间格式化 YY.MM.DD hh:mm 或者YY.MM.DD
|
||||
* @param
|
||||
* @returns
|
||||
*/
|
||||
export const formatDate1 = (date, toMinute) => {
|
||||
let date1 = new Date(date);
|
||||
let year = date1.getFullYear();
|
||||
let month = date1.getMonth() + 1; // 月份是从0开始的
|
||||
let day = date1.getDate();
|
||||
let hour = date1.getHours()
|
||||
let minute = date1.getMinutes()
|
||||
if (toMinute) {
|
||||
return year + '.' + (month < 10 ? '0' + month : month) + '.' + (day < 10 ? '0' + day : day) + ' ' + (hour <
|
||||
10 ? '0' + hour : hour) + ':' +
|
||||
(minute < 10 ? '0' + minute : minute);
|
||||
} else {
|
||||
return year + '.' + (month < 10 ? '0' + month : month) + '.' + (day < 10 ? '0' + day : day);
|
||||
}
|
||||
|
||||
}
|
64
src/hooks/useImage.js
Normal file
64
src/hooks/useImage.js
Normal file
@ -0,0 +1,64 @@
|
||||
// 获取学生头像
|
||||
export const studentHeadImage = sex => {
|
||||
let headPic = '';
|
||||
switch (sex) {
|
||||
case 0:
|
||||
// 男
|
||||
headPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/boy.png';
|
||||
return headPic;
|
||||
case 1:
|
||||
// 女
|
||||
headPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/girl.png';
|
||||
return headPic;
|
||||
}
|
||||
};
|
||||
// 获取成就勋章
|
||||
export const getMedalImage = type => {
|
||||
let medalPic = '';
|
||||
switch (type) {
|
||||
case 1:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/medal_1.png';
|
||||
return medalPic;
|
||||
case 2:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/medal_2.png';
|
||||
return medalPic;
|
||||
case 3:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/medal_3.png';
|
||||
return medalPic;
|
||||
case 4:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/medal_4.png';
|
||||
return medalPic;
|
||||
}
|
||||
};
|
||||
// 获取置灰成就勋章
|
||||
export const getGrayMedalImage = type => {
|
||||
let medalPic = '';
|
||||
switch (type) {
|
||||
case 1:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/gray_medal_1.png';
|
||||
return medalPic;
|
||||
case 2:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/gray_medal_2.png';
|
||||
return medalPic;
|
||||
case 3:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/gray_medal_3.png';
|
||||
return medalPic;
|
||||
case 4:
|
||||
medalPic =
|
||||
'https://xuexiaole-1313840333.cos.ap-guangzhou.myqcloud.com/xuexiaoleClient/gray_medal_4.png';
|
||||
return medalPic;
|
||||
}
|
||||
};
|
||||
// 获取升将持平
|
||||
export const getRankFlagImage = url => {
|
||||
return '/static/iconPicture/' + url;
|
||||
};
|
67
src/hooks/useKey.ts
Normal file
67
src/hooks/useKey.ts
Normal file
@ -0,0 +1,67 @@
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
// 学历
|
||||
export const useEducation = {
|
||||
0: '小学',
|
||||
1: '初中',
|
||||
2: '高中',
|
||||
3: '中专',
|
||||
4: '大专',
|
||||
5: '本科',
|
||||
6: '研究生',
|
||||
7: '博士',
|
||||
8: '博士后',
|
||||
};
|
||||
// 家长称谓
|
||||
export const useRelation1 = {
|
||||
1: '父亲',
|
||||
2: '母亲',
|
||||
3: '爷爷',
|
||||
4: '奶奶',
|
||||
5: '外公',
|
||||
6: '外婆',
|
||||
7: '其他',
|
||||
};
|
||||
export const useRelation = [
|
||||
{
|
||||
name: '爸爸',
|
||||
icon: `${OSS_URL}/urm/bindDevice/baba.png`,
|
||||
id: 1,
|
||||
color: '#63B1FF',
|
||||
},
|
||||
{
|
||||
name: '妈妈',
|
||||
icon: `${OSS_URL}/urm/bindDevice/mama.png`,
|
||||
id: 2,
|
||||
color: '#FF8A8A',
|
||||
},
|
||||
{
|
||||
name: '爷爷',
|
||||
icon: `${OSS_URL}/urm/bindDevice/yeye.png`,
|
||||
id: 3,
|
||||
color: '#8979FF',
|
||||
},
|
||||
{
|
||||
name: '奶奶',
|
||||
icon: `${OSS_URL}/urm/bindDevice/nainai.png`,
|
||||
id: 4,
|
||||
color: '#FFB152FB',
|
||||
},
|
||||
{
|
||||
name: '外公',
|
||||
icon: `${OSS_URL}/urm/bindDevice/wg.png`,
|
||||
id: 5,
|
||||
color: '#69CBFF',
|
||||
},
|
||||
{
|
||||
name: '外婆',
|
||||
icon: `${OSS_URL}/urm/bindDevice/waipo.png`,
|
||||
id: 6,
|
||||
color: '#E27AFF',
|
||||
},
|
||||
{
|
||||
name: '其他',
|
||||
icon: `${OSS_URL}/urm/bindDevice/qita.png`,
|
||||
id: 7,
|
||||
color: '#5BE19E',
|
||||
},
|
||||
];
|
10
src/hooks/useObj.js
Normal file
10
src/hooks/useObj.js
Normal file
@ -0,0 +1,10 @@
|
||||
// 对象属性key改名
|
||||
export const renameKeys = (obj, oldKeys, newKeys) => {
|
||||
for (let i in oldKeys) {
|
||||
let oldKey = oldKeys[i]
|
||||
let newKey = newKeys[i]
|
||||
obj[newKey] = obj[oldKey]
|
||||
// delete obj[oldKey]
|
||||
}
|
||||
return obj
|
||||
}
|
127
src/hooks/useSocket.ts
Normal file
127
src/hooks/useSocket.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { useWebSocket } from '@vueuse/core';
|
||||
import { user } from '@/store';
|
||||
import { ref } from 'vue';
|
||||
import type { UseWebSocketReturn } from '@vueuse/core';
|
||||
|
||||
let SOCKET: UseWebSocketReturn<any>;
|
||||
export const useSocket = () => {
|
||||
const { token, userInfo } = user();
|
||||
const inspectorMessage = ref<anyObj>();
|
||||
const inspectorDesktopMessage = ref([]);
|
||||
const desktopPath = ref<string>();
|
||||
enum SOCKET_MSG_TYPE {
|
||||
lockScreen = 'lockScreen',
|
||||
unLockScreen = 'unLockScreen',
|
||||
desktopPreview = 'desktopPreview',
|
||||
inspectorTalkMessage = 'inspectorTalkMessage',
|
||||
desktopScreenshotImage = 'desktopScreenshotImage',
|
||||
}
|
||||
function init() {
|
||||
SOCKET = useWebSocket(
|
||||
`${process.env.VITE_WS_URL}?accessToken=${token.replace('Bearer ', '')}`,
|
||||
{
|
||||
autoReconnect: {
|
||||
retries: 5,
|
||||
delay: 3000,
|
||||
onFailed() {
|
||||
console.log('重新连接失败');
|
||||
},
|
||||
},
|
||||
heartbeat: {
|
||||
message: '{"type":"ping"}',
|
||||
interval: 5000,
|
||||
pongTimeout: 3000,
|
||||
},
|
||||
onConnected,
|
||||
onDisconnected,
|
||||
onError,
|
||||
onMessage,
|
||||
},
|
||||
);
|
||||
sendMsg({
|
||||
type: 'ping',
|
||||
});
|
||||
}
|
||||
|
||||
function onConnected(ws: WebSocket) {
|
||||
// ws.binaryType = 'arraybuffer';
|
||||
console.log('ws已连接');
|
||||
}
|
||||
function onDisconnected() {
|
||||
console.log('ws已关闭');
|
||||
}
|
||||
function onError(ws: WebSocket, event: Event) {
|
||||
// console.log(event);
|
||||
}
|
||||
|
||||
function onMessage(ws: WebSocket, event: MessageEvent) {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log(data);
|
||||
switch (data.type) {
|
||||
// case SOCKET_MSG_TYPE.lockScreen:
|
||||
// // 执行锁屏
|
||||
// useLog(LogType.LockScreenTime, {
|
||||
// simSerialNumber: SN,
|
||||
// lockFlag: 1,
|
||||
// });
|
||||
// break;
|
||||
// case SOCKET_MSG_TYPE.unLockScreen:
|
||||
// // 执行解锁
|
||||
// useLog(LogType.LockScreenTime, {
|
||||
// simSerialNumber: SN,
|
||||
// lockFlag: 0,
|
||||
// });
|
||||
// break;
|
||||
case SOCKET_MSG_TYPE.desktopPreview:
|
||||
onDesktopPreview();
|
||||
break;
|
||||
case SOCKET_MSG_TYPE.inspectorTalkMessage:
|
||||
inspectorMessage.value = data.msg;
|
||||
break;
|
||||
case SOCKET_MSG_TYPE.desktopScreenshotImage:
|
||||
inspectorDesktopMessage.value = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 桌面预览
|
||||
async function onDesktopPreview() {
|
||||
// const { data: res } = await uploadFile({});
|
||||
// sendMsg({
|
||||
// type: 'desktopScreenshotImage',
|
||||
// userId: userInfo.userId,
|
||||
// simSerialNumber: SN,
|
||||
// msg: '',
|
||||
// });
|
||||
// SOCKET.send(
|
||||
// JSON.stringify({
|
||||
// type: 'desktopScreenshotImage',
|
||||
// userId: userInfo.userId,
|
||||
// simSerialNumber: SN,
|
||||
// msg: ''
|
||||
// }),
|
||||
// );
|
||||
}
|
||||
// 发送消息
|
||||
function sendMsg(data: anyObj, config = {}) {
|
||||
if (!SOCKET) {
|
||||
console.error('ws不存在');
|
||||
return;
|
||||
}
|
||||
inspectorDesktopMessage.value = [];
|
||||
SOCKET.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function textToBuffer(str: string) {
|
||||
return new TextEncoder().encode(str);
|
||||
}
|
||||
|
||||
function bufferToText(buffer: ArrayBuffer) {
|
||||
return new TextDecoder().decode(buffer);
|
||||
}
|
||||
return {
|
||||
init,
|
||||
sendMsg,
|
||||
inspectorMessage,
|
||||
inspectorDesktopMessage,
|
||||
};
|
||||
};
|
22
src/hooks/useTools.ts
Normal file
22
src/hooks/useTools.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export const maskPhone = (phone: string) => {
|
||||
if (!phone || phone.length !== 11) return phone;
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1***$2');
|
||||
};
|
||||
|
||||
export const debounce = (fn: (...args: any[]) => void, delay: number, immediate = false) => {
|
||||
let timer: number | null = null;
|
||||
return function (this: any, ...args: any[]) {
|
||||
timer && clearTimeout(timer);
|
||||
if (immediate) {
|
||||
const callNow = !timer;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
}, delay);
|
||||
callNow && fn.apply(this, args);
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, args);
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
};
|
18
src/main.ts
Normal file
18
src/main.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createSSRApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
import * as Pinia from 'pinia';
|
||||
import './styles/global.scss';
|
||||
import './styles/font.scss';
|
||||
import './styles/iconfont.scss';
|
||||
import BackBar from '@/components/BackBar/index';
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
app.use(Pinia.createPinia());
|
||||
app.component('BackBar', BackBar);
|
||||
return {
|
||||
app,
|
||||
Pinia,
|
||||
};
|
||||
}
|
91
src/manifest.json
Normal file
91
src/manifest.json
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "学小乐管理端",
|
||||
"appid": "__UNI__A05ABA1",
|
||||
"description": "",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueStyleCompiler": "uni-app",
|
||||
"compilerVersion": 3,
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules": {},
|
||||
/* 应用发布信息 */
|
||||
"distribute": {
|
||||
/* android打包配置 */
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios": {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp": {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin": {
|
||||
"appid": "wxd13aabeb40898e1d",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-baidu": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"mp-toutiao": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"uniStatistics": {
|
||||
"enable": false
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"h5": {
|
||||
"title": "学小乐",
|
||||
"devServer": {
|
||||
"proxy": {
|
||||
"/api": {
|
||||
"target": "https://test.pi.xuexiaole.com",
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/api": "" // 重写路径
|
||||
}
|
||||
}
|
||||
},
|
||||
"https": false
|
||||
},
|
||||
"router": {
|
||||
"mode": "hash",
|
||||
"base": "./"
|
||||
}
|
||||
}
|
||||
}
|
402
src/pages.json
Normal file
402
src/pages.json
Normal file
@ -0,0 +1,402 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/home/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学小乐"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/academicReport/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学情报告"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/applicationManagement/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "应用管控"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/bindApply/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绑定申请",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/Warranty/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "电子保修卡",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/Warranty/applyService",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请售后",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/Warranty/serviceHistory",
|
||||
"style": {
|
||||
"navigationBarTitleText": "售后历史",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/Warranty/serviceHistoryDetails",
|
||||
"style": {
|
||||
"navigationBarTitleText": "售后详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/taskManage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "任务管理",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/taskManage/taskItem",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增任务",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/taskManage/taskConfigList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择任务",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/taskManage/patriarchSendObject",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择发送对象",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/awardManage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "奖励管理",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/awardManage/awardItem",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加奖励",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/prefectInformation/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "完善信息",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/prefectInformation/cropper",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/details/childrenDetails",
|
||||
"style": {
|
||||
"navigationBarTitleText": "账号详情",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/details/deviceDetails",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设备详情",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/inviteBind/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "邀请绑定",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/bindDevice/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "扫码绑定",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/bindDevice/applyForBinding",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请绑定",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/bindDevice/bindChildAccount",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绑定申请",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/bindDevice/familyRelationships",
|
||||
"style": {
|
||||
"navigationBarTitleText": "亲子关系",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/inviteBind/relationShips",
|
||||
"style": {
|
||||
"navigationBarTitleText": "亲子关系",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/TimeManagement/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "时间管控",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/DesktopPreview/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设备截屏",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/DesktopPreview/historyScreenShot",
|
||||
"style": {
|
||||
"navigationBarTitleText": "历史截屏",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/LockScreen/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "一键锁屏",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人资料",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/personalInformation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人简介",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/editPhone",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改手机号",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"path": "pages/inspector/personalData/certificateView",
|
||||
"style": {
|
||||
"navigationBarTitleText": "资质证书",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/certificateAdd",
|
||||
"style": {
|
||||
"navigationBarTitleText": "资质证书",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/cropper",
|
||||
"style": {
|
||||
"navigationBarTitleText": "头像裁剪",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/personalData/subject",
|
||||
"style": {
|
||||
"navigationBarTitleText": "擅长科目",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/schedule/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/schedule/condition",
|
||||
"style": {
|
||||
"navigationBarTitleText": "督学时况",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/chat/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "沟通",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/chat/chatDetails",
|
||||
"style": {
|
||||
"navigationBarTitleText": "沟通",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/student/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学员",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/student-detail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学员详情",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/releasePlan/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发布计划",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/releasePlan/selectPlan",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择任务",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/releasePlan/selectBook",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择教材",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/releasePlan/selectChapter",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择章节",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/inspector/releasePlan/selectVideo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择视频",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/parents/mySupervisionService/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的督学服务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/parents/mySupervisionService/studyPlan",
|
||||
"style": {
|
||||
"navigationBarTitleText": "查看学习计划"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/parents/supervisionService/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "督学服务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/parents/supervisionService/buyService",
|
||||
"style": {
|
||||
"navigationBarTitleText": "督学服务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/parents/supervisionService/serviceDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "督学服务"
|
||||
}
|
||||
}
|
||||
/*督学师==============END=======================*/
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#333333",
|
||||
"selectedColor": "#333333",
|
||||
"borderStyle": "white",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": []
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "学小乐",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"navigationStyle": "custom"
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||
"^qy-(.*)": "~@/components/$1/index.vue",
|
||||
"^svg-(.*)": "~@/components/svgs/$1/index.vue",
|
||||
"^svgs-(.*)": "~@/components/svgs/svgs-$1.vue",
|
||||
"^isp-(.*)": "~@/components/inspector/isp-$1.vue"
|
||||
}
|
||||
}
|
||||
}
|
83
src/pages/academicReport/Components/Echart/BarChart.vue
Normal file
83
src/pages/academicReport/Components/Echart/BarChart.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<view class="charts-box" :style="{ width: width, height: height }">
|
||||
<qiun-data-charts
|
||||
type="bar"
|
||||
:canvas2d="canvas2d"
|
||||
:opts="opts"
|
||||
:tooltipShow="false"
|
||||
:chartData="_chartData"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
interface SeriesType {
|
||||
data: { color?: string; value: number }[];
|
||||
}
|
||||
interface ChartDataType {
|
||||
categories: string[];
|
||||
series: SeriesType[];
|
||||
}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
chartData: ChartDataType;
|
||||
}>(),
|
||||
{ height: '420rpx', categories: () => [] },
|
||||
);
|
||||
const canvas2d = ref(true);
|
||||
const _chartData = ref();
|
||||
const opts = ref({
|
||||
padding: [0, 42, 0, 0],
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
position: 'top',
|
||||
padding: 2,
|
||||
show: false,
|
||||
},
|
||||
xAxis: {
|
||||
boundaryGap: 'justify',
|
||||
disableGrid: false,
|
||||
min: 0,
|
||||
max: 10,
|
||||
axisLine: false,
|
||||
type: 'grid', // X轴类型
|
||||
gridType: 'dash', // X轴网格线类型
|
||||
gridColor: '#d4d4d4',
|
||||
splitNumber: 10,
|
||||
format: 'xAixsHours',
|
||||
},
|
||||
yAxis: {},
|
||||
extra: {
|
||||
bar: {
|
||||
type: 'group',
|
||||
width: 25,
|
||||
meterBorde: 1,
|
||||
meterFillColor: '#FFFFFF',
|
||||
activeBgColor: '#000000',
|
||||
activeBgOpacity: 0.05,
|
||||
barBorderCircle: false,
|
||||
seriesGap: 2,
|
||||
categoryGap: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
watch(
|
||||
() => props.chartData,
|
||||
val => {
|
||||
_chartData.value = val;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
63
src/pages/academicReport/Components/Echart/ColumnChart.vue
Normal file
63
src/pages/academicReport/Components/Echart/ColumnChart.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="charts-box" :style="{ width: width, height: height }">
|
||||
<qiun-data-charts
|
||||
type="column"
|
||||
:canvas2d="canvas2d"
|
||||
:opts="opts"
|
||||
:tooltipShow="false"
|
||||
:chartData="chartData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
interface SeriesType {
|
||||
data: { color?: string; value: number }[];
|
||||
}
|
||||
interface ChartDataType {
|
||||
categories: string[];
|
||||
series: SeriesType[];
|
||||
}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
chartData: ChartDataType;
|
||||
}>(),
|
||||
{ height: '440rpx', categories: () => [] },
|
||||
);
|
||||
const canvas2d = ref(true);
|
||||
const chartData = ref(props.chartData);
|
||||
const opts = ref({
|
||||
padding: [20, 25, 0, 0],
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
xAxis: {
|
||||
disableGrid: true,
|
||||
},
|
||||
yAxis: {
|
||||
// format: 'integerFormat',
|
||||
},
|
||||
extra: {
|
||||
column: {
|
||||
type: 'group',
|
||||
width: 30,
|
||||
activeBgColor: '#000000',
|
||||
activeBgOpacity: 0.05,
|
||||
barBorderCircle: false,
|
||||
seriesGap: 5,
|
||||
linearOpacity: 0.5,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
78
src/pages/academicReport/Components/Echart/LineChart.vue
Normal file
78
src/pages/academicReport/Components/Echart/LineChart.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<view class="charts-box" :style="{ width: width, height: height }">
|
||||
<qiun-data-charts
|
||||
type="line"
|
||||
canvas2d
|
||||
:opts="opts"
|
||||
:chartData="chartData"
|
||||
ontouch
|
||||
tooltip-format="tooltipExer"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
interface SeriesType {
|
||||
name: string;
|
||||
date: any[];
|
||||
color?: string;
|
||||
}
|
||||
interface ChartDataType {
|
||||
categories: any[];
|
||||
series: SeriesType[];
|
||||
}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
chartData: any;
|
||||
tooltipFormatType?: string;
|
||||
}>(),
|
||||
{ height: '420rpx', categories: () => [] },
|
||||
);
|
||||
|
||||
const chartData = ref(props.chartData);
|
||||
const opts = ref({
|
||||
padding: [15, 20, 0, 15],
|
||||
enableScroll: false,
|
||||
dataLabel: false,
|
||||
dataPointShape: false,
|
||||
legend: { position: 'top', padding: 2 },
|
||||
xAxis: {
|
||||
disableGrid: true,
|
||||
labelCount: 6, // 标签数
|
||||
format: 'xAixsHoursMin',
|
||||
},
|
||||
yAxis: {
|
||||
gridType: 'dash',
|
||||
dashLength: 2,
|
||||
calibration: true,
|
||||
},
|
||||
extra: {
|
||||
line: {
|
||||
type: 'curve',
|
||||
width: 2,
|
||||
activeType: 'none',
|
||||
},
|
||||
tooltip: {
|
||||
bgColor: '#21D17A',
|
||||
fontColor: '#fff',
|
||||
bgOpacity: 1,
|
||||
borderRadius: 8,
|
||||
gridColor: '#21D17A',
|
||||
labelFontColor: '#333',
|
||||
legendShow: false,
|
||||
borderOpacity: 1,
|
||||
labelBgOpacity: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
86
src/pages/academicReport/Components/Echart/RingChart.vue
Normal file
86
src/pages/academicReport/Components/Echart/RingChart.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<view class="charts-box">
|
||||
<qiun-data-charts type="ring" canvas2d :opts="opts" :chartData="chartData" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { onReady } from '@dcloudio/uni-app';
|
||||
import { watch } from 'vue';
|
||||
interface SeriesType {
|
||||
data: any[];
|
||||
}
|
||||
interface ChartDataType {
|
||||
categories?: string[];
|
||||
series: SeriesType[];
|
||||
}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string;
|
||||
height?: string;
|
||||
chartData: ChartDataType;
|
||||
showLegend?: boolean;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}>(),
|
||||
{ height: '420rpx', categories: () => [], showLegend: true, title: '', subtitle: '' },
|
||||
);
|
||||
|
||||
const chartData = ref(props.chartData);
|
||||
|
||||
const opts = ref({
|
||||
rotate: false,
|
||||
rotateLock: false,
|
||||
padding: [0, 0, 0, 0],
|
||||
dataLabel: true,
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
show: props.showLegend,
|
||||
position: 'bottom',
|
||||
lineHeight: 16,
|
||||
},
|
||||
title: {
|
||||
name: props.title,
|
||||
fontSize: 15,
|
||||
color: 'rgba(102, 102, 102, 1)',
|
||||
},
|
||||
subtitle: {
|
||||
name: '',
|
||||
fontSize: 25,
|
||||
color: 'rgba(51, 51, 51, 1)',
|
||||
},
|
||||
extra: {
|
||||
ring: {
|
||||
ringWidth: 60,
|
||||
activeOpacity: 0.5,
|
||||
activeRadius: 10,
|
||||
offsetAngle: 0,
|
||||
labelWidth: 15,
|
||||
border: true,
|
||||
borderWidth: 3,
|
||||
borderColor: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
});
|
||||
watch(
|
||||
() => props.subtitle,
|
||||
val => {
|
||||
if (val) {
|
||||
opts.value.subtitle.name = val;
|
||||
} else {
|
||||
opts.value.subtitle.name = props.title ? '0%' : '';
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
}
|
||||
</style>
|
129
src/pages/academicReport/Components/childrenBox.vue
Normal file
129
src/pages/academicReport/Components/childrenBox.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="single_box children_box">
|
||||
<view class="children">
|
||||
<view class="left">
|
||||
<image
|
||||
:src="showChildren?.childAvatar ? showChildren?.childAvatar : defaultAvatar"
|
||||
mode=""
|
||||
class="children_avatar"
|
||||
></image>
|
||||
<view>
|
||||
<view class="name_vip">
|
||||
<text class="name">{{
|
||||
showChildren?.childName ? showChildren?.childName : '未绑定账号'
|
||||
}}</text>
|
||||
<image
|
||||
v-if="showChildren?.currentLevel"
|
||||
:src="`${OSS_URL}/iconfont/VIP/VIP_${showChildren?.currentLevel}.png`"
|
||||
class="vip"
|
||||
/>
|
||||
</view>
|
||||
<view class="time">
|
||||
<template v-if="showChildren?.vipEndTime">
|
||||
有效期至:{{
|
||||
showChildren?.vipEndTime
|
||||
? dayjs(showChildren?.vipEndTime).format('YYYY.MM.DD')
|
||||
: '-'
|
||||
}}
|
||||
</template>
|
||||
<template v-else>请先绑定孩子账号</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="list.length > 1">
|
||||
<image
|
||||
:class="{ icon: true, rotate: showAllChildren }"
|
||||
:src="arrow"
|
||||
@click="showAllChildren = !showAllChildren"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="showAllChildren">
|
||||
<view
|
||||
v-for="(i, idx) in otherChildrenList"
|
||||
:key="idx"
|
||||
class="children"
|
||||
@click="handleShowChildren(i)"
|
||||
>
|
||||
<view class="left">
|
||||
<image
|
||||
:src="i.childAvatar ? i.childAvatar : defaultAvatar"
|
||||
mode=""
|
||||
class="children_avatar"
|
||||
></image>
|
||||
<view>
|
||||
<view class="name_vip">
|
||||
<text class="name">{{ i.childName }}</text>
|
||||
<image
|
||||
v-if="i.currentLevel"
|
||||
:src="`${OSS_URL}/iconfont/VIP/VIP_${i.currentLevel}.png`"
|
||||
class="vip"
|
||||
/>
|
||||
</view>
|
||||
<view class="time">
|
||||
有效期至:{{ i.vipEndTime ? dayjs(i.vipEndTime).format('YYYY.MM.DD') : '-' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<image
|
||||
class="icon checked_icon"
|
||||
:src="
|
||||
checkedId === i.childId
|
||||
? `${OSS_URL}/iconfont/checked.png`
|
||||
: `${OSS_URL}/iconfont/unchecked.png`
|
||||
"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<view class="children_info">
|
||||
<view class="item">
|
||||
年级:<text class="level">{{ showChildren?.gradeName || '-' }}</text>
|
||||
</view>
|
||||
<view class="item">
|
||||
等级:<text class="level">{{ showChildren?.expLevel || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import type { ChildrenType } from '@/pages/mine/interface';
|
||||
import '../index.scss';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
list: ChildrenType[];
|
||||
showItem: ChildrenType;
|
||||
}>(),
|
||||
{
|
||||
list: () => [],
|
||||
showItem: () => ({}) as ChildrenType,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits(['updateShowItem']);
|
||||
const otherChildrenList = computed(() =>
|
||||
props.list.filter(i => i.childId !== props.showItem.childId),
|
||||
);
|
||||
|
||||
const showChildren = computed({
|
||||
get() {
|
||||
return props.showItem;
|
||||
},
|
||||
set(newVal) {
|
||||
emits('updateShowItem', newVal);
|
||||
},
|
||||
});
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST;
|
||||
const arrow = `${OSS_URL}/iconfont/down_arrow.png`;
|
||||
|
||||
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`;
|
||||
const showAllChildren = ref(false);
|
||||
const checkedId = ref<string>();
|
||||
function handleShowChildren(i: ChildrenType) {
|
||||
checkedId.value = i.childId;
|
||||
setTimeout(() => {
|
||||
showChildren.value = i;
|
||||
}, 200);
|
||||
}
|
||||
</script>
|
87
src/pages/academicReport/Components/dateFilter.vue
Normal file
87
src/pages/academicReport/Components/dateFilter.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="date_filter">
|
||||
<text
|
||||
v-for="(i, idx) in list"
|
||||
:key="idx"
|
||||
:class="{ label: true, active_label: i.value === selectVal }"
|
||||
@click="handleFilter(i)"
|
||||
>
|
||||
{{ i.label }}
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
export interface selectType {
|
||||
value: string | number;
|
||||
label: string | number;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
list?: selectType[]; // 0-今天 1-七天 2-30天 3-总计
|
||||
dateType?: number | string;
|
||||
}>(),
|
||||
{
|
||||
list: () => [
|
||||
{ label: '今天', value: 0 },
|
||||
{ label: '近一周', value: 1 },
|
||||
{ label: '近一月', value: 2 },
|
||||
{ label: '总计', value: 3 },
|
||||
],
|
||||
},
|
||||
);
|
||||
const emits = defineEmits(['filter']);
|
||||
const selectVal = ref<number | string>();
|
||||
function handleFilter(i: any) {
|
||||
emits('filter', i);
|
||||
selectVal.value = i.value;
|
||||
}
|
||||
watch(
|
||||
() => props.dateType,
|
||||
val => {
|
||||
selectVal.value = val;
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => props.list,
|
||||
val => {
|
||||
if (val) {
|
||||
selectVal.value = val[0].value;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.date_filter {
|
||||
.label {
|
||||
display: inline-block;
|
||||
width: 108rpx;
|
||||
height: 52rpx;
|
||||
background-color: #f7f8fa;
|
||||
font-size: 26rpx;
|
||||
line-height: 52rpx;
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
box-sizing: border-box;
|
||||
border-right: 1rpx solid #d9e1f2;
|
||||
|
||||
&:first-child {
|
||||
border-radius: 12rpx 0 0 12rpx;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 12rpx 12rpx 0;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.active_label {
|
||||
background-color: #615dff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user