‘add第一次提交’
This commit is contained in:
commit
1197377464
5
.env
Normal file
5
.env
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#VITE_HOST = http://192.168.0.114:9053
|
||||||
|
#VITE_HOST = http://43.136.52.196:9053
|
||||||
|
VITE_HOST= https://ai.xuexiaole.com
|
||||||
|
VITE_OSS_HOST = https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com
|
||||||
|
VITE_WS_URL = wss://test.qiaoying.vip/wss/websocket
|
||||||
4
.env.prod
Normal file
4
.env.prod
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# VITE_HOST = http://192.168.0.114:9053
|
||||||
|
VITE_HOST= https://ai.xuexiaole.com
|
||||||
|
VITE_OSS_HOST = https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com
|
||||||
|
VITE_WS_URL = wss://qiaoying.vip/wss/websocket
|
||||||
4
.env.test
Normal file
4
.env.test
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# VITE_HOST = http://192.168.0.114:9053
|
||||||
|
VITE_HOST= https://ai.xuexiaole.com
|
||||||
|
VITE_OSS_HOST = https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com
|
||||||
|
VITE_WS_URL = wss://test.qiaoying.vip/wss/websocket
|
||||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/uni_modules/**
|
||||||
47
.eslintrc.js
Normal file
47
.eslintrc.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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',
|
||||||
|
// 圈复杂度 每个函数的最高圈复杂度
|
||||||
|
complexity: [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
max: 12,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
defineProps: 'readonly',
|
||||||
|
defineEmits: 'readonly',
|
||||||
|
defineExpose: 'readonly',
|
||||||
|
withDefaults: 'readonly',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
};
|
||||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
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: 4,
|
||||||
|
useTabs: false,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
bracketSpacing: true,
|
||||||
|
bracketSameLine: false,
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
vueIndentScriptAndStyle: false,
|
||||||
|
endOfLine: 'lf',
|
||||||
|
};
|
||||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": "never",
|
||||||
|
"source.fixAll.eslint": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
101
package.json
Normal file
101
package.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"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",
|
||||||
|
"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.10",
|
||||||
|
"lint-staged": "^15.0.1",
|
||||||
|
"pinia": "2.0.36",
|
||||||
|
"sass": "^1.72.0",
|
||||||
|
"vue": "3.3.4",
|
||||||
|
"vue-demi": "0.14.7",
|
||||||
|
"vue-i18n": "^9.1.9"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
10923
pnpm-lock.yaml
generated
Normal file
10923
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 {}
|
||||||
|
}
|
||||||
25
src/App.vue
Normal file
25
src/App.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
|
||||||
|
onLaunch(() => {
|
||||||
|
console.log('App Launch');
|
||||||
|
// 设置横屏
|
||||||
|
uni.setPageOrientation({
|
||||||
|
orientation: 'landscape'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
onShow(() => {
|
||||||
|
console.log('App Show');
|
||||||
|
// 设置横屏
|
||||||
|
uni.setPageOrientation({
|
||||||
|
orientation: 'landscape'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
onHide(() => {
|
||||||
|
console.log('App Hide');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
47
src/api/global.ts
Normal file
47
src/api/global.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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' });
|
||||||
120
src/api/homework.ts
Normal file
120
src/api/homework.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import request from './request';
|
||||||
|
|
||||||
|
export interface PaperRecord {
|
||||||
|
id: string;
|
||||||
|
paperId: string;
|
||||||
|
paperName: string;
|
||||||
|
subjectId: string;
|
||||||
|
subjectName: string;
|
||||||
|
publisherName: string;
|
||||||
|
createTime: string;
|
||||||
|
endTime: string;
|
||||||
|
remark: string;
|
||||||
|
costTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubjectPaperRecord {
|
||||||
|
subjectId: string;
|
||||||
|
subjectName: string;
|
||||||
|
records: PaperRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取未完成作业列表
|
||||||
|
export const getUnfinishedHomework = () => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/userRecord/listExamRecord',
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始答题
|
||||||
|
export const startExam = (recordId: string) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/exam',
|
||||||
|
method: 'POST',
|
||||||
|
data: { recordId },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存单道题目答案
|
||||||
|
export const saveRecordAnswer = (data: {
|
||||||
|
answer: string;
|
||||||
|
answerPic: string;
|
||||||
|
costTime: number;
|
||||||
|
itemId: string;
|
||||||
|
recordId: string;
|
||||||
|
}) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/saveRecordAnswer',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交作业
|
||||||
|
export const submitExam = (data: {
|
||||||
|
answerDtoList: Array<{
|
||||||
|
itemId: string;
|
||||||
|
answer: string;
|
||||||
|
answerPic: string;
|
||||||
|
}>;
|
||||||
|
costTime: number;
|
||||||
|
recordId: string;
|
||||||
|
}) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/submit',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取已完成作业记录
|
||||||
|
export const getListCompleteRecord = (params?: {
|
||||||
|
gradingStatus?: string;
|
||||||
|
subjectId?: string;
|
||||||
|
}) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/userRecord/listCompleteRecord',
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取作业详情(用于查看报告)
|
||||||
|
export const getPaperRecordDetail = (data: {
|
||||||
|
recordId: string;
|
||||||
|
reportFlag: number;
|
||||||
|
}) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paper/getPaperRecordDetail',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取作业整体分析
|
||||||
|
export const getJobAnalyse = (params: { recordId: string }) => {
|
||||||
|
return request({
|
||||||
|
url: '/sc/job/getJobAnalyse',
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取知识点分析
|
||||||
|
export const getKnowledgeAnalyse = (params: { recordId: string }) => {
|
||||||
|
return request({
|
||||||
|
url: '/sc/job/statisticalKnowledge',
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据年级获取学科列表
|
||||||
|
export const getSubjectsByGrade = (gradeId: string) => {
|
||||||
|
return request({
|
||||||
|
url: '/sc/subject/getListByGrade',
|
||||||
|
method: 'GET',
|
||||||
|
params: { gradeId },
|
||||||
|
});
|
||||||
|
};
|
||||||
4
src/api/index.ts
Normal file
4
src/api/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './global';
|
||||||
|
export * from './login';
|
||||||
|
export * from './chat';
|
||||||
|
export * from './upload';
|
||||||
154
src/api/login.ts
Normal file
154
src/api/login.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import $req from './request';
|
||||||
|
|
||||||
|
// Base64 编码函数(兼容小程序环境)
|
||||||
|
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||||
|
const encode = (str: string): string => {
|
||||||
|
if (!str) return '';
|
||||||
|
let output = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
// UTF-8 编码
|
||||||
|
const utf8Str = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>
|
||||||
|
String.fromCharCode(parseInt(p1, 16))
|
||||||
|
);
|
||||||
|
|
||||||
|
while (i < utf8Str.length) {
|
||||||
|
const chr1 = utf8Str.charCodeAt(i++);
|
||||||
|
const chr2 = utf8Str.charCodeAt(i++);
|
||||||
|
const chr3 = utf8Str.charCodeAt(i++);
|
||||||
|
|
||||||
|
const enc1 = chr1 >> 2;
|
||||||
|
const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||||
|
let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||||
|
let enc4 = chr3 & 63;
|
||||||
|
|
||||||
|
if (isNaN(chr2)) {
|
||||||
|
enc3 = enc4 = 64;
|
||||||
|
} else if (isNaN(chr3)) {
|
||||||
|
enc4 = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += base64Chars.charAt(enc1) + base64Chars.charAt(enc2) +
|
||||||
|
base64Chars.charAt(enc3) + base64Chars.charAt(enc4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface smsLoginType {
|
||||||
|
phone: string;
|
||||||
|
companyId?: string;
|
||||||
|
verifyCode: string;
|
||||||
|
clientType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface accountLoginType {
|
||||||
|
account: string;
|
||||||
|
companyId?: string;
|
||||||
|
password?: string;
|
||||||
|
clientType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface updatePasswordType {
|
||||||
|
code: string;
|
||||||
|
companyId: number;
|
||||||
|
newPassword: string;
|
||||||
|
phoneNumbers: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手机号获取已绑定的公司
|
||||||
|
export const getBindCompanyList = (params: anyObj) =>
|
||||||
|
$req({
|
||||||
|
url: '/company/open/getCompanyListByPhone',
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手机验证码登录
|
||||||
|
export const phoneLogin = (data: smsLoginType) =>
|
||||||
|
$req({
|
||||||
|
url: '/smsLogin',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
clientType: 'APPLET',
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取图形验证码
|
||||||
|
export const getCaptcha = (time: string | number) =>
|
||||||
|
$req({
|
||||||
|
url: `/getCaptcha/${time}`,
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送短信验证码
|
||||||
|
export const sendCode = (data: anyObj) =>
|
||||||
|
$req({
|
||||||
|
url: `/sms/sendLoginMessage`,
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 微信获取手机号登录
|
||||||
|
export const getWxPhoneNumber = async (params: anyObj) =>
|
||||||
|
await $req({
|
||||||
|
url: '/wx/user/phone',
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 密码登录(小程序版本 - 学小乐专用)
|
||||||
|
export const psdLogin = (data: accountLoginType) =>
|
||||||
|
$req({
|
||||||
|
url: '/login',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
clientType: 'PC', // 小程序使用 PC 类型
|
||||||
|
simSerialNumber: 'web',
|
||||||
|
model: 'web',
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
export const logout = () =>
|
||||||
|
$req({
|
||||||
|
url: '/logout',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手机验证码修改密码
|
||||||
|
export const updatePwdByPhone = (data: anyObj) =>
|
||||||
|
$req({
|
||||||
|
url: '/sysUser/phoneUpdatePwd',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
clientType: 'APPLET',
|
||||||
|
...data,
|
||||||
|
newPassword: encode(data.newPassword || ''),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取用户信息(使用与原项目一致的接口)
|
||||||
|
export const getUserInfo = () =>
|
||||||
|
$req({
|
||||||
|
url: '/personal/getHomePage',
|
||||||
|
});
|
||||||
92
src/api/request.ts
Normal file
92
src/api/request.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { user } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { getCache } from '@/utils';
|
||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
host: import.meta.env.VITE_HOST,
|
||||||
|
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,
|
||||||
|
data: method === 'GET' ? config.params : config.data,
|
||||||
|
timeout: config.timeout || CONFIG.timeout,
|
||||||
|
header: {
|
||||||
|
Authorization: mergerToken ? `Bearer ${mergerToken}` : '',
|
||||||
|
...config.headers,
|
||||||
|
'x-tenant-id': '1966391196339204098',
|
||||||
|
},
|
||||||
|
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;
|
||||||
64
src/api/teacher.ts
Normal file
64
src/api/teacher.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import request from './request';
|
||||||
|
|
||||||
|
// 老师用户信息接口
|
||||||
|
export interface TeacherUserInfo {
|
||||||
|
id?: string;
|
||||||
|
account?: string;
|
||||||
|
nickName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
schoolId?: string;
|
||||||
|
schoolName?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作业记录项接口
|
||||||
|
export interface PaperReleaseRecord {
|
||||||
|
id: string;
|
||||||
|
paperId: string;
|
||||||
|
paperName: string;
|
||||||
|
schoolId: string;
|
||||||
|
schoolName: string;
|
||||||
|
classNames: string | null;
|
||||||
|
teacherName: string;
|
||||||
|
subjectName: string;
|
||||||
|
subjectId: string;
|
||||||
|
gradeId: string;
|
||||||
|
gradeName: string;
|
||||||
|
name: string;
|
||||||
|
status: number;
|
||||||
|
realTimeCheck: number;
|
||||||
|
remark: string | null;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
number: number;
|
||||||
|
completeNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作业记录列表响应接口
|
||||||
|
export interface PaperReleaseRecordListResponse {
|
||||||
|
rows: PaperReleaseRecord[];
|
||||||
|
total: number;
|
||||||
|
current: number;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取老师登录用户信息
|
||||||
|
export const getLoginUser = () => {
|
||||||
|
return request({
|
||||||
|
url: '/getLoginUser',
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取作业记录列表
|
||||||
|
export const getPaperReleaseRecordList = (params: {
|
||||||
|
schoolId: string;
|
||||||
|
current: number;
|
||||||
|
size: number;
|
||||||
|
}) => {
|
||||||
|
return request({
|
||||||
|
url: '/school/paperReleaseRecord/list',
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
39
src/api/upload.ts
Normal file
39
src/api/upload.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { user } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { getCache } from '@/utils';
|
||||||
|
|
||||||
|
const API_HOST = import.meta.env.VITE_HOST;
|
||||||
|
|
||||||
|
// 通用文件上传封装:返回后端 data 字段
|
||||||
|
export const uploadFile = (filePath: string): Promise<any> => {
|
||||||
|
const { token } = storeToRefs(user());
|
||||||
|
const mergerToken = token.value || getCache('token') || uni.getStorageSync('QY_TOKEN') || '';
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: `${API_HOST}/api/main/sysFileInfo/tenUploadAll`,
|
||||||
|
filePath,
|
||||||
|
name: 'file',
|
||||||
|
header: {
|
||||||
|
Authorization: mergerToken ? `Bearer ${mergerToken}` : '',
|
||||||
|
'x-tenant-id': '1966391196339204098',
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
try {
|
||||||
|
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
|
||||||
|
if (data.code === 200) {
|
||||||
|
resolve(data.data);
|
||||||
|
} else {
|
||||||
|
reject(new Error(data.message || '上传失败'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('上传返回格式异常'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
65
src/components/BackBar/index.vue
Normal file
65
src/components/BackBar/index.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<view class="back-bar">
|
||||||
|
<view class="back-btn" @click="handleBack">
|
||||||
|
<!-- 使用内联 SVG 箭头图标 -->
|
||||||
|
<view class="icon-arrow"></view>
|
||||||
|
<text class="text">{{ leftText }}</text>
|
||||||
|
</view>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
leftText?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
leftText: '返回',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
uni.navigateBack();
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.back-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 36rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8rpx 14rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.icon-arrow {
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
border-left: 3rpx solid #333;
|
||||||
|
border-bottom: 3rpx solid #333;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
93
src/components/Empty/index.vue
Normal file
93
src/components/Empty/index.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<view class="empty">
|
||||||
|
<view class="empty-box">
|
||||||
|
<!-- loading 状态使用 uni-app 原生 loading -->
|
||||||
|
<view v-if="type === 'loading'" class="loading-wrap">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
</view>
|
||||||
|
<!-- 空状态使用内联 SVG -->
|
||||||
|
<view v-else class="empty-icon">
|
||||||
|
<view class="icon-box">📭</view>
|
||||||
|
</view>
|
||||||
|
<text class="empty-text">{{ content }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
type?: 'loading' | 'task' | 'learn' | 'network' | 'search';
|
||||||
|
content?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
type: 'task',
|
||||||
|
content: '暂无数据',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.empty-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.loading-wrap {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border: 4rpx solid #e5e5e5;
|
||||||
|
border-top-color: #0ea5e9;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
|
||||||
|
.icon-box {
|
||||||
|
font-size: 56rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
600
src/components/ImageCropper/index.vue
Normal file
600
src/components/ImageCropper/index.vue
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['cropper-wrapper', { show: visible }]">
|
||||||
|
<!-- 显示层:用 image 展示图片,裁剪框覆盖其上,避免真机上 canvas 把裁剪框盖住 -->
|
||||||
|
<view
|
||||||
|
class="display-layer"
|
||||||
|
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||||
|
@touchstart="onTouchStart"
|
||||||
|
@touchmove="onTouchMove"
|
||||||
|
@touchend="onTouchEnd"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="imageSrc"
|
||||||
|
:src="imageSrc"
|
||||||
|
class="display-image"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 工作 canvas:放到屏幕外,只用于真正的绘制和导出 -->
|
||||||
|
<canvas
|
||||||
|
id="imageCropperCanvas"
|
||||||
|
canvas-id="imageCropperCanvas"
|
||||||
|
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||||
|
class="work-canvas"
|
||||||
|
></canvas>
|
||||||
|
|
||||||
|
<!-- 用于导出裁剪结果的离屏 canvas(必须有真实节点) -->
|
||||||
|
<canvas
|
||||||
|
id="tempCropCanvas"
|
||||||
|
canvas-id="tempCropCanvas"
|
||||||
|
:style="{ width: exportSize.width + 'px', height: exportSize.height + 'px' }"
|
||||||
|
class="temp-canvas"
|
||||||
|
></canvas>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="crop-frame"
|
||||||
|
:style="{
|
||||||
|
left: cropFrame.x + 'px',
|
||||||
|
top: cropFrame.y + 'px',
|
||||||
|
width: cropFrame.width + 'px',
|
||||||
|
height: cropFrame.height + 'px',
|
||||||
|
}"
|
||||||
|
@touchstart.stop.prevent="onFrameTouchStart"
|
||||||
|
@touchmove.stop.prevent="onFrameTouchMove"
|
||||||
|
@touchend.stop="onFrameTouchEnd"
|
||||||
|
>
|
||||||
|
<view class="resize-handle top-left" data-direction="tl"></view>
|
||||||
|
<view class="resize-handle top-right" data-direction="tr"></view>
|
||||||
|
<view class="resize-handle bottom-left" data-direction="bl"></view>
|
||||||
|
<view class="resize-handle bottom-right" data-direction="br"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="crop-controls" :style="{ bottom: 8 + safeAreaBottom + 'px' }">
|
||||||
|
<button @tap="cancelCrop">取消</button>
|
||||||
|
<button :disabled="isProcessing" @tap="confirmCrop">
|
||||||
|
{{ isProcessing ? '处理中...' : '确定' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
imageSrc: string;
|
||||||
|
outputWidth?: number; // 裁剪输出宽度
|
||||||
|
outputHeight?: number; // 裁剪输出高度
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
outputWidth: 750, // 默认输出宽度
|
||||||
|
outputHeight: 750, // 默认输出高度
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
(e: 'confirm', filePath: string): void;
|
||||||
|
(e: 'cancel'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const ctx = ref<UniApp.CanvasContext | null>(null);
|
||||||
|
const imgInfo = ref<UniApp.GetImageInfoSuccessData | null>(null);
|
||||||
|
|
||||||
|
const canvasWidth = ref(0);
|
||||||
|
const canvasHeight = ref(0);
|
||||||
|
const imageDisplay = ref({ x: 0, y: 0, width: 0, height: 0 }); // 图片在canvas上的显示位置和尺寸
|
||||||
|
const controlsReservePx = 64; // 给底部按钮预留空间,避免真机遮挡图片
|
||||||
|
|
||||||
|
const cropFrame = ref({ x: 0, y: 0, width: 0, height: 0 }); // 裁剪框位置和尺寸
|
||||||
|
const isDraggingImage = ref(false);
|
||||||
|
const isResizingFrame = ref(false);
|
||||||
|
const isMovingFrame = ref(false);
|
||||||
|
const startX = ref(0);
|
||||||
|
const startY = ref(0);
|
||||||
|
const lastFrame = ref({ x: 0, y: 0, width: 0, height: 0 }); // 用于记录上一次裁剪框的位置和大小
|
||||||
|
const resizeDirection = ref('');
|
||||||
|
const isProcessing = ref(false); // 裁剪处理中状态
|
||||||
|
|
||||||
|
const safeAreaTop = ref(0);
|
||||||
|
const safeAreaBottom = ref(0);
|
||||||
|
const componentProxy = getCurrentInstance()?.proxy as any;
|
||||||
|
const exportSize = ref({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
const rpxToPx = (rpx: number) => {
|
||||||
|
// 按小程序 rpx 规则:750rpx = windowWidth(px)
|
||||||
|
if (!canvasWidth.value) return Math.max(1, Math.round(rpx));
|
||||||
|
return Math.max(1, Math.round((canvasWidth.value / 750) * rpx));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTouchPoint = (e: any) => {
|
||||||
|
const t = e?.touches?.[0] || e?.changedTouches?.[0] || {};
|
||||||
|
const x = t.x ?? t.clientX ?? t.pageX ?? 0;
|
||||||
|
const y = t.y ?? t.clientY ?? t.pageY ?? 0;
|
||||||
|
return { x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryInitAndLoad = () => {
|
||||||
|
if (!props.visible) return;
|
||||||
|
if (!props.imageSrc) return;
|
||||||
|
if (!ctx.value) return;
|
||||||
|
if (!canvasWidth.value || !canvasHeight.value) return;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
exportSize.value = {
|
||||||
|
width: rpxToPx(props.outputWidth),
|
||||||
|
height: rpxToPx(props.outputHeight),
|
||||||
|
};
|
||||||
|
loadImage();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取设备信息,用于计算canvas大小和安全区
|
||||||
|
uni.getSystemInfo({
|
||||||
|
success: (res) => {
|
||||||
|
canvasWidth.value = res.windowWidth;
|
||||||
|
safeAreaTop.value = res.safeArea?.top || 0;
|
||||||
|
safeAreaBottom.value =
|
||||||
|
(res.screenHeight && res.safeArea ? res.screenHeight - res.safeArea.bottom : 0) || 0;
|
||||||
|
// 预留底部按钮区 + 安全区,避免按钮覆盖图片(尤其真机横屏)
|
||||||
|
const bottomReserve = controlsReservePx + safeAreaBottom.value;
|
||||||
|
canvasHeight.value = Math.max(1, res.windowHeight - bottomReserve);
|
||||||
|
// 自定义组件内的 canvas 必须传组件实例,否则会出现“黑屏/不绘制”
|
||||||
|
ctx.value = uni.createCanvasContext('imageCropperCanvas', componentProxy);
|
||||||
|
tryInitAndLoad();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.visible, () => props.imageSrc, canvasWidth, canvasHeight, ctx],
|
||||||
|
() => {
|
||||||
|
if (!props.visible) {
|
||||||
|
imgInfo.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tryInitAndLoad();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadImage = () => {
|
||||||
|
uni.showLoading({ title: '加载中...' });
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: props.imageSrc,
|
||||||
|
success: (res) => {
|
||||||
|
imgInfo.value = res;
|
||||||
|
drawImage();
|
||||||
|
uni.hideLoading();
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '图片加载失败', icon: 'none' });
|
||||||
|
console.error('图片加载失败', err);
|
||||||
|
cancelCrop();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawImage = () => {
|
||||||
|
if (!ctx.value || !imgInfo.value) return;
|
||||||
|
|
||||||
|
const { path, width: originalWidth, height: originalHeight } = imgInfo.value;
|
||||||
|
|
||||||
|
// 计算图片在Canvas上的显示尺寸,使其适应Canvas,并居中
|
||||||
|
const canvasRatio = canvasWidth.value / canvasHeight.value;
|
||||||
|
const imageRatio = originalWidth / originalHeight;
|
||||||
|
|
||||||
|
let displayWidth = 0;
|
||||||
|
let displayHeight = 0;
|
||||||
|
|
||||||
|
if (imageRatio > canvasRatio) {
|
||||||
|
displayWidth = canvasWidth.value;
|
||||||
|
displayHeight = canvasWidth.value / imageRatio;
|
||||||
|
} else {
|
||||||
|
displayHeight = canvasHeight.value;
|
||||||
|
displayWidth = canvasHeight.value * imageRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageDisplay.value = {
|
||||||
|
x: (canvasWidth.value - displayWidth) / 2,
|
||||||
|
y: (canvasHeight.value - displayHeight) / 2,
|
||||||
|
width: displayWidth,
|
||||||
|
height: displayHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
|
||||||
|
ctx.value.drawImage(
|
||||||
|
path,
|
||||||
|
imageDisplay.value.x,
|
||||||
|
imageDisplay.value.y,
|
||||||
|
imageDisplay.value.width,
|
||||||
|
imageDisplay.value.height
|
||||||
|
);
|
||||||
|
|
||||||
|
// 初始化裁剪框为图片显示区域的大小
|
||||||
|
cropFrame.value = {
|
||||||
|
x: imageDisplay.value.x,
|
||||||
|
y: imageDisplay.value.y,
|
||||||
|
width: imageDisplay.value.width,
|
||||||
|
height: imageDisplay.value.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.value.draw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchStart = (e: any) => {
|
||||||
|
if (!imgInfo.value) return;
|
||||||
|
const { x, y } = getTouchPoint(e);
|
||||||
|
startX.value = x;
|
||||||
|
startY.value = y;
|
||||||
|
|
||||||
|
// 判断是否在拖动图片
|
||||||
|
if (
|
||||||
|
x > imageDisplay.value.x &&
|
||||||
|
x < imageDisplay.value.x + imageDisplay.value.width &&
|
||||||
|
y > imageDisplay.value.y &&
|
||||||
|
y < imageDisplay.value.y + imageDisplay.value.height
|
||||||
|
) {
|
||||||
|
isDraggingImage.value = true;
|
||||||
|
// 记录图片当前位置,用于计算偏移
|
||||||
|
lastFrame.value = { ...imageDisplay.value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchMove = (e: any) => {
|
||||||
|
if (!isDraggingImage.value || !imgInfo.value || !ctx.value) return;
|
||||||
|
|
||||||
|
const { x, y } = getTouchPoint(e);
|
||||||
|
const deltaX = x - startX.value;
|
||||||
|
const deltaY = y - startY.value;
|
||||||
|
|
||||||
|
let newX = lastFrame.value.x + deltaX;
|
||||||
|
let newY = lastFrame.value.y + deltaY;
|
||||||
|
|
||||||
|
// 边界检查:确保图片不会完全移出canvas
|
||||||
|
if (newX > canvasWidth.value - imageDisplay.value.x) {
|
||||||
|
newX = canvasWidth.value - imageDisplay.value.x;
|
||||||
|
}
|
||||||
|
if (newY > canvasHeight.value - imageDisplay.value.y) {
|
||||||
|
newY = canvasHeight.value - imageDisplay.value.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newX < -imageDisplay.value.width + imageDisplay.value.x) {
|
||||||
|
newX = -imageDisplay.value.width + imageDisplay.value.x;
|
||||||
|
}
|
||||||
|
if (newY < -imageDisplay.value.height + imageDisplay.value.y) {
|
||||||
|
newY = -imageDisplay.value.height + imageDisplay.value.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageDisplay.value.x = newX;
|
||||||
|
imageDisplay.value.y = newY;
|
||||||
|
|
||||||
|
ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value);
|
||||||
|
ctx.value.drawImage(
|
||||||
|
imgInfo.value.path,
|
||||||
|
imageDisplay.value.x,
|
||||||
|
imageDisplay.value.y,
|
||||||
|
imageDisplay.value.width,
|
||||||
|
imageDisplay.value.height
|
||||||
|
);
|
||||||
|
ctx.value.draw(true); // true表示保留上一次绘制内容
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchEnd = () => {
|
||||||
|
isDraggingImage.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
|
const onFrameTouchMove = (e: any) => {
|
||||||
|
if (!imgInfo.value) return;
|
||||||
|
const { x, y } = getTouchPoint(e);
|
||||||
|
const deltaX = x - startX.value;
|
||||||
|
const deltaY = y - startY.value;
|
||||||
|
|
||||||
|
if (isMovingFrame.value) {
|
||||||
|
// 移动裁剪框
|
||||||
|
let newX = lastFrame.value.x + deltaX;
|
||||||
|
let newY = lastFrame.value.y + deltaY;
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
if (newX < 0) newX = 0;
|
||||||
|
if (newY < 0) newY = 0;
|
||||||
|
if (newX + cropFrame.value.width > canvasWidth.value)
|
||||||
|
newX = canvasWidth.value - cropFrame.value.width;
|
||||||
|
if (newY + cropFrame.value.height > canvasHeight.value)
|
||||||
|
newY = canvasHeight.value - cropFrame.value.height;
|
||||||
|
|
||||||
|
cropFrame.value.x = newX;
|
||||||
|
cropFrame.value.y = newY;
|
||||||
|
} else if (isResizingFrame.value) {
|
||||||
|
// 缩放裁剪框
|
||||||
|
let newX = lastFrame.value.x;
|
||||||
|
let newY = lastFrame.value.y;
|
||||||
|
let newWidth = lastFrame.value.width;
|
||||||
|
let newHeight = lastFrame.value.height;
|
||||||
|
|
||||||
|
const minSize = 50; // 最小尺寸
|
||||||
|
|
||||||
|
switch (resizeDirection.value) {
|
||||||
|
case 'tl': // Top-left
|
||||||
|
newX = Math.min(x, lastFrame.value.x + lastFrame.value.width - minSize);
|
||||||
|
newY = Math.min(y, lastFrame.value.y + lastFrame.value.height - minSize);
|
||||||
|
newWidth = lastFrame.value.x + lastFrame.value.width - newX;
|
||||||
|
newHeight = lastFrame.value.y + lastFrame.value.height - newY;
|
||||||
|
break;
|
||||||
|
case 'tr': // Top-right
|
||||||
|
newX = lastFrame.value.x;
|
||||||
|
newY = Math.min(startY.value, lastFrame.value.y + lastFrame.value.height - minSize, y);
|
||||||
|
newWidth = Math.max(minSize, lastFrame.value.width + deltaX);
|
||||||
|
newHeight = lastFrame.value.y + lastFrame.value.height - newY;
|
||||||
|
break;
|
||||||
|
case 'bl': // Bottom-left
|
||||||
|
newX = Math.min(startX.value, lastFrame.value.x + lastFrame.value.width - minSize, x);
|
||||||
|
newY = lastFrame.value.y;
|
||||||
|
newWidth = lastFrame.value.x + lastFrame.value.width - newX;
|
||||||
|
newHeight = Math.max(minSize, lastFrame.value.height + deltaY);
|
||||||
|
break;
|
||||||
|
case 'br': // Bottom-right
|
||||||
|
newX = lastFrame.value.x;
|
||||||
|
newY = lastFrame.value.y;
|
||||||
|
newWidth = Math.max(minSize, lastFrame.value.width + deltaX);
|
||||||
|
newHeight = Math.max(minSize, lastFrame.value.height + deltaY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 裁剪框不能超出canvas边界
|
||||||
|
if (newX < 0) newX = 0;
|
||||||
|
if (newY < 0) newY = 0;
|
||||||
|
if (newX + newWidth > canvasWidth.value) newWidth = canvasWidth.value - newX;
|
||||||
|
if (newY + newHeight > canvasHeight.value) newHeight = canvasHeight.value - newY;
|
||||||
|
|
||||||
|
cropFrame.value = { x: newX, y: newY, width: newWidth, height: newHeight };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFrameTouchEnd = () => {
|
||||||
|
isMovingFrame.value = false;
|
||||||
|
isResizingFrame.value = false;
|
||||||
|
resizeDirection.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 阻止事件冒泡,因为canvas的touchmove事件会在外部被阻止
|
||||||
|
// 这里需要处理裁剪框的触摸事件,不阻止冒泡会导致裁剪框无法拖动和缩放
|
||||||
|
const onFrameTouchStart = (e: any) => {
|
||||||
|
const { x, y } = getTouchPoint(e);
|
||||||
|
startX.value = x;
|
||||||
|
startY.value = y;
|
||||||
|
lastFrame.value = { ...cropFrame.value }; // 记录当前裁剪框状态
|
||||||
|
|
||||||
|
const targetDataset = e.target.dataset;
|
||||||
|
if (targetDataset && targetDataset.direction) {
|
||||||
|
isResizingFrame.value = true;
|
||||||
|
resizeDirection.value = targetDataset.direction;
|
||||||
|
} else {
|
||||||
|
isMovingFrame.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmCrop = () => {
|
||||||
|
if (!ctx.value || !imgInfo.value || isProcessing.value) return;
|
||||||
|
|
||||||
|
isProcessing.value = true;
|
||||||
|
uni.showLoading({ title: '裁剪中...', mask: true });
|
||||||
|
|
||||||
|
const { path: imagePath, width: originalWidth, height: originalHeight } = imgInfo.value;
|
||||||
|
|
||||||
|
// 计算实际图片上的裁剪区域
|
||||||
|
// 图片显示区域与原始图片的比例
|
||||||
|
const scaleX = originalWidth / imageDisplay.value.width;
|
||||||
|
const scaleY = originalHeight / imageDisplay.value.height;
|
||||||
|
|
||||||
|
const sourceX = (cropFrame.value.x - imageDisplay.value.x) * scaleX;
|
||||||
|
const sourceY = (cropFrame.value.y - imageDisplay.value.y) * scaleY;
|
||||||
|
const sourceWidth = cropFrame.value.width * scaleX;
|
||||||
|
const sourceHeight = cropFrame.value.height * scaleY;
|
||||||
|
|
||||||
|
// 创建一个新的canvas用于裁剪,确保尺寸和输出尺寸匹配
|
||||||
|
const tempCanvasId = 'tempCropCanvas';
|
||||||
|
const tempCtx = uni.createCanvasContext(tempCanvasId, componentProxy);
|
||||||
|
|
||||||
|
// 设置裁剪输出尺寸
|
||||||
|
const outputWidthPx = rpxToPx(props.outputWidth);
|
||||||
|
const outputHeightPx = rpxToPx(props.outputHeight);
|
||||||
|
exportSize.value = { width: outputWidthPx, height: outputHeightPx };
|
||||||
|
|
||||||
|
// 绘制图片到临时canvas,进行裁剪和缩放
|
||||||
|
tempCtx.clearRect(0, 0, outputWidthPx, outputHeightPx);
|
||||||
|
tempCtx.drawImage(
|
||||||
|
imagePath,
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourceWidth,
|
||||||
|
sourceHeight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
outputWidthPx,
|
||||||
|
outputHeightPx
|
||||||
|
);
|
||||||
|
|
||||||
|
// 确保绘制完成
|
||||||
|
tempCtx.draw(false, () => {
|
||||||
|
let done = false;
|
||||||
|
const finish = (ok: boolean, payload?: any) => {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
isProcessing.value = false;
|
||||||
|
uni.hideLoading();
|
||||||
|
if (!ok) return;
|
||||||
|
emit('confirm', payload);
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 兜底:防止部分机型 canvasToTempFilePath 不回调导致“一直处理中”
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
uni.showToast({ title: '裁剪超时,请重试', icon: 'none' });
|
||||||
|
finish(false);
|
||||||
|
}, 4000);
|
||||||
|
|
||||||
|
// 延迟一段时间,确保canvas内容已经渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
canvasId: tempCanvasId,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: outputWidthPx,
|
||||||
|
height: outputHeightPx,
|
||||||
|
destWidth: outputWidthPx,
|
||||||
|
destHeight: outputHeightPx,
|
||||||
|
success: (res) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
finish(true, res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
uni.showToast({ title: '裁剪失败', icon: 'none' });
|
||||||
|
console.error('canvasToTempFilePath failed', err);
|
||||||
|
finish(false);
|
||||||
|
},
|
||||||
|
}, componentProxy);
|
||||||
|
}, 100); // 短暂延迟
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelCrop = () => {
|
||||||
|
emit('cancel');
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cropper-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: #000;
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden; // 隐藏超出部分
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: block;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-canvas {
|
||||||
|
position: fixed;
|
||||||
|
left: -99999px;
|
||||||
|
top: -99999px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-canvas {
|
||||||
|
position: fixed;
|
||||||
|
left: -99999px;
|
||||||
|
top: -99999px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-frame {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px solid #00c0ff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: none; // 禁用浏览器默认触摸行为
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #00c0ff;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: 0.9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-left {
|
||||||
|
top: -16px;
|
||||||
|
left: -16px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
.top-right {
|
||||||
|
top: -16px;
|
||||||
|
right: -16px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.bottom-left {
|
||||||
|
bottom: -16px;
|
||||||
|
left: -16px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.bottom-right {
|
||||||
|
bottom: -16px;
|
||||||
|
right: -16px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-controls {
|
||||||
|
position: absolute;
|
||||||
|
// left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: 12px;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-controls button {
|
||||||
|
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-controls button:first-child {
|
||||||
|
background: #3c3c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crop-controls button[disabled] {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
src/components/ProgressBar/index.vue
Normal file
55
src/components/ProgressBar/index.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<view class="progress-bar" :style="{ width: `${width}rpx` }">
|
||||||
|
<view class="progress-bg" :style="{ background: bg, borderColor: border }">
|
||||||
|
<view
|
||||||
|
class="progress-active"
|
||||||
|
:style="{
|
||||||
|
width: `${rate * 100}%`,
|
||||||
|
background: activeBg
|
||||||
|
}"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
rate: number;
|
||||||
|
width?: string | number;
|
||||||
|
color?: string;
|
||||||
|
border?: string;
|
||||||
|
bg?: string;
|
||||||
|
activeBg?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
rate: 0,
|
||||||
|
width: 196,
|
||||||
|
color: '#2D39BC',
|
||||||
|
border: '#2D39BC',
|
||||||
|
bg: '#6B73E5',
|
||||||
|
activeBg: '#90FCF7',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.progress-bar {
|
||||||
|
height: 12rpx;
|
||||||
|
|
||||||
|
.progress-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
border: 1rpx solid;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.progress-active {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
179
src/components/Select/index.vue
Normal file
179
src/components/Select/index.vue
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<view class="select-wrap">
|
||||||
|
<view class="select-btn" @click="showPicker = true">
|
||||||
|
<text class="select-text">{{ currentLabel }}</text>
|
||||||
|
<image class="select-arrow" :src="`${OSS_URL}/icon/icon_arrow_down.svg`" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<uni-popup ref="popupRef" type="bottom" @change="onPopupChange">
|
||||||
|
<view class="picker-content">
|
||||||
|
<view class="picker-header">
|
||||||
|
<text class="cancel" @click="handleCancel">取消</text>
|
||||||
|
<text class="title">请选择</text>
|
||||||
|
<text class="confirm" @click="handleConfirm">确定</text>
|
||||||
|
</view>
|
||||||
|
<picker-view
|
||||||
|
class="picker-view"
|
||||||
|
:value="pickerValue"
|
||||||
|
@change="onPickerChange"
|
||||||
|
>
|
||||||
|
<picker-view-column>
|
||||||
|
<view
|
||||||
|
v-for="item in selectList"
|
||||||
|
:key="item.value"
|
||||||
|
class="picker-item"
|
||||||
|
>
|
||||||
|
<text>{{ item.label }}</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
</picker-view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
|
||||||
|
interface SelectOption {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string | number;
|
||||||
|
selectList: SelectOption[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
selectList: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'selectChange']);
|
||||||
|
|
||||||
|
const popupRef = ref();
|
||||||
|
const showPicker = ref(false);
|
||||||
|
const tempIndex = ref(0);
|
||||||
|
const pickerValue = ref([0]);
|
||||||
|
|
||||||
|
const currentLabel = computed(() => {
|
||||||
|
const item = props.selectList.find(i => i.value === props.modelValue);
|
||||||
|
return item?.label || '请选择';
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
const index = props.selectList.findIndex(i => i.value === val);
|
||||||
|
if (index >= 0) {
|
||||||
|
pickerValue.value = [index];
|
||||||
|
tempIndex.value = index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(showPicker, (val) => {
|
||||||
|
if (val) {
|
||||||
|
popupRef.value?.open();
|
||||||
|
} else {
|
||||||
|
popupRef.value?.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPopupChange = (e: any) => {
|
||||||
|
showPicker.value = e.show;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPickerChange = (e: any) => {
|
||||||
|
tempIndex.value = e.detail.value[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
showPicker.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
const item = props.selectList[tempIndex.value];
|
||||||
|
if (item) {
|
||||||
|
emit('update:modelValue', item.value);
|
||||||
|
emit('selectChange', item);
|
||||||
|
}
|
||||||
|
showPicker.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.select-wrap {
|
||||||
|
.select-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6rpx 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.select-text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-arrow {
|
||||||
|
width: 14rpx;
|
||||||
|
height: 14rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-content {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
|
||||||
|
.cancel {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $font-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: $theme-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-view {
|
||||||
|
height: 260rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 52rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
96
src/components/Sidebar/index.vue
Normal file
96
src/components/Sidebar/index.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="sidebar" scroll-y :show-scrollbar="false">
|
||||||
|
<view
|
||||||
|
v-for="item in config"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['sidebar-item', { active: modelValue === item.id, warning: item.warning }]"
|
||||||
|
@click="handleSelect(item)"
|
||||||
|
>
|
||||||
|
<view class="sidebar-item-content">
|
||||||
|
<text class="name">{{ prop?.name ? item[prop.name] : item.name }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="item.warning" class="warning-dot"></view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string | number;
|
||||||
|
config: any[];
|
||||||
|
prop?: {
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
config: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'handleChange']);
|
||||||
|
|
||||||
|
const handleSelect = (item: any) => {
|
||||||
|
emit('update:modelValue', item.id);
|
||||||
|
emit('handleChange', item);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.sidebar-item {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
padding: 10rpx 4rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
border-radius: 10rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(135deg, #ffe60f 0%, #ffd000 100%);
|
||||||
|
border-color: #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 230, 15, 0.4);
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: $font-color;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-align: center;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 4rpx;
|
||||||
|
right: 4rpx;
|
||||||
|
width: 6rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
background: #ff4d4f;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
15
src/main.ts
Normal file
15
src/main.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
app.use(Pinia.createPinia());
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
Pinia,
|
||||||
|
};
|
||||||
|
}
|
||||||
77
src/manifest.json
Normal file
77
src/manifest.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"name" : "学小乐作业助手",
|
||||||
|
"appid" : "__UNI__C5ADC5F",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.0",
|
||||||
|
"versionCode" : "100",
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"screenOrientation" : [ "landscape-primary", "landscape-secondary" ],
|
||||||
|
"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" : "wxa5522671f30c5891",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false,
|
||||||
|
"postcss" : true,
|
||||||
|
"minified" : true
|
||||||
|
},
|
||||||
|
"usingComponents" : true,
|
||||||
|
"resizable" : false,
|
||||||
|
"pageOrientation" : "landscape"
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
|
},
|
||||||
|
"vueVersion" : "3"
|
||||||
|
}
|
||||||
140
src/pages.json
Normal file
140
src/pages.json
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"app-plus": {
|
||||||
|
"bounce": "none"
|
||||||
|
},
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "登录",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/homework/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "我的作业",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/homework/history/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "作业记录",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/homework/reports/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "作业报告",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/doWork/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "答题",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "学小乐AI",
|
||||||
|
"navigationBarBackgroundColor": "#fff",
|
||||||
|
"backgroundColor": "#F8F8F8",
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
},
|
||||||
|
"easycom": {
|
||||||
|
"autoscan": true,
|
||||||
|
"custom": {
|
||||||
|
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
|
||||||
|
"^qy-image-cropper$": "~@/components/ImageCropper/index.vue",
|
||||||
|
"^qy-(.*)": "~@/components/$1/index.vue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subPackages": [
|
||||||
|
{
|
||||||
|
"root": "pages/teacher",
|
||||||
|
"name": "teacher",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "老师首页",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "homework/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "作业记录",
|
||||||
|
"disableScroll": true,
|
||||||
|
"pageOrientation": "landscape",
|
||||||
|
"allowsBounceVertical": "NO",
|
||||||
|
"mp-weixin": {
|
||||||
|
"pageOrientation": "landscape"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preloadRule": {
|
||||||
|
"pages/index/index": {
|
||||||
|
"network": "all",
|
||||||
|
"packages": ["teacher"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1574
src/pages/doWork/components/Question.vue
Normal file
1574
src/pages/doWork/components/Question.vue
Normal file
File diff suppressed because it is too large
Load Diff
616
src/pages/doWork/index.vue
Normal file
616
src/pages/doWork/index.vue
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
<template>
|
||||||
|
<view class="dowork">
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<view class="top">
|
||||||
|
<qy-BackBar @click="handleBack" />
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="main-top">
|
||||||
|
<view class="title" :data-content="title">{{ title }}</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="!pageLoading"
|
||||||
|
class="top-timeout"
|
||||||
|
:style="{ paddingRight: menuButtonRightRpx + 'rpx' }"
|
||||||
|
>
|
||||||
|
<image :src="`${OSS_URL}/icon/clock.svg`" mode="aspectFit" />
|
||||||
|
<text>{{ timeFilter }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<view v-if="!pageLoading" class="main">
|
||||||
|
<view class="main-box">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 题目区域 -->
|
||||||
|
<view class="book-box">
|
||||||
|
<view class="book">
|
||||||
|
<template v-if="paperList.length && paperList[activeIdx]">
|
||||||
|
<Question
|
||||||
|
:data="paperList[activeIdx]"
|
||||||
|
:idx="activeIdx + 1"
|
||||||
|
:total="paperList.length"
|
||||||
|
:resourceType="resourceType"
|
||||||
|
:reportFlag="reportFlag"
|
||||||
|
:subjectId="paperInfo.subjectId"
|
||||||
|
@submit="submitQuestion"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="bottom">
|
||||||
|
<scroll-view class="indicator" scroll-x :show-scrollbar="false">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in paperList"
|
||||||
|
v-show="!item.unLook"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[
|
||||||
|
'indicator-item',
|
||||||
|
{
|
||||||
|
active: activeIdx === idx,
|
||||||
|
success: reportFlag && (item.correctResult || item.markFlag),
|
||||||
|
error: reportFlag && !(item.correctResult || item.markFlag),
|
||||||
|
empty: !item.submitAnswer && !item.submitAnswerPic,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@click="switchQuestion(idx)"
|
||||||
|
>
|
||||||
|
<text>{{ idx + 1 }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="!reportFlag"
|
||||||
|
class="btn-next"
|
||||||
|
:class="{ disabled: submitLoading }"
|
||||||
|
@click="handleNext"
|
||||||
|
>
|
||||||
|
<text>{{ activeIdx < paperList.length - 1 ? '下一题' : '提交' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<view v-else class="loading-page">
|
||||||
|
<qy-Empty
|
||||||
|
type="loading"
|
||||||
|
:content="reportFlag ? '正在加载报告中...' : '正在精选题目中...'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { startExam, saveRecordAnswer, submitExam, getPaperRecordDetail } from '@/api/homework';
|
||||||
|
import Question from './components/Question.vue';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
const resource_type_homework = 'homework';
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const pageLoading = ref(true);
|
||||||
|
const submitLoading = ref(false);
|
||||||
|
|
||||||
|
// 胶囊按钮位置信息(用于避免遮挡,单位:rpx)
|
||||||
|
const menuButtonRightRpx = ref(0);
|
||||||
|
|
||||||
|
// 题目列表
|
||||||
|
const paperList = ref<any[]>([]);
|
||||||
|
const paperInfo = ref<any>({});
|
||||||
|
|
||||||
|
// 当前所在题目
|
||||||
|
const activeIdx = ref(0);
|
||||||
|
|
||||||
|
// 是否为答题页面 0答题 1查看作业结果
|
||||||
|
const reportFlag = ref<0 | 1>(0);
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const params = ref<any>({});
|
||||||
|
|
||||||
|
// 计时器
|
||||||
|
let timeout: any = null;
|
||||||
|
|
||||||
|
// 资源类型
|
||||||
|
const resourceType = computed(() => {
|
||||||
|
return params.value.resourceType || resource_type_homework;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = computed(() => {
|
||||||
|
const name = paperInfo.value.paperName;
|
||||||
|
if (name) {
|
||||||
|
// 去除日期
|
||||||
|
return name.replace(/-\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/, '');
|
||||||
|
}
|
||||||
|
return '作业';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 时间显示
|
||||||
|
const timeFilter = computed(() => {
|
||||||
|
const costTime = paperInfo.value.costTime || 0;
|
||||||
|
const hours = Math.floor(costTime / 3600);
|
||||||
|
const minutes = Math.floor((costTime % 3600) / 60);
|
||||||
|
const seconds = costTime % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始跳转的题目索引
|
||||||
|
const initialIdx = ref(0);
|
||||||
|
|
||||||
|
// 页面加载获取参数
|
||||||
|
onLoad((options: any) => {
|
||||||
|
// 保存初始跳转的题目索引
|
||||||
|
initialIdx.value = Number(options.idx) || 0;
|
||||||
|
|
||||||
|
if (options.id) {
|
||||||
|
// 查看已完成的作业
|
||||||
|
reportFlag.value = 1;
|
||||||
|
params.value = {
|
||||||
|
...options,
|
||||||
|
id: options.id,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 开始新答题
|
||||||
|
reportFlag.value = Number(options.reportFlag || 0) as 0 | 1;
|
||||||
|
params.value = { ...options };
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回处理
|
||||||
|
const handleBack = () => {
|
||||||
|
if (reportFlag.value) {
|
||||||
|
uni.navigateBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '是否确认退出,退出后不保留答题记录',
|
||||||
|
confirmColor: '#ffe60f',
|
||||||
|
success: res => {
|
||||||
|
if (res.confirm) {
|
||||||
|
clearTimer();
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换题目
|
||||||
|
const switchQuestion = async (idx?: number) => {
|
||||||
|
if (idx === activeIdx.value) {
|
||||||
|
// 点击当前题目,保存答案
|
||||||
|
if (!reportFlag.value && resourceType.value === resource_type_homework) {
|
||||||
|
await saveAnswer();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx !== undefined) {
|
||||||
|
// 保存当前题答案
|
||||||
|
if (!reportFlag.value && resourceType.value === resource_type_homework) {
|
||||||
|
await saveAnswer();
|
||||||
|
}
|
||||||
|
activeIdx.value = idx;
|
||||||
|
} else {
|
||||||
|
// 下一题
|
||||||
|
if (activeIdx.value < paperList.value.length - 1) {
|
||||||
|
if (!reportFlag.value && resourceType.value === resource_type_homework) {
|
||||||
|
await saveAnswer();
|
||||||
|
}
|
||||||
|
activeIdx.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reportFlag.value && paperList.value[activeIdx.value]) {
|
||||||
|
paperList.value[activeIdx.value].unLook = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下一题/提交
|
||||||
|
const handleNext = async () => {
|
||||||
|
if (submitLoading.value) return;
|
||||||
|
|
||||||
|
if (activeIdx.value < paperList.value.length - 1) {
|
||||||
|
// 下一题
|
||||||
|
await switchQuestion();
|
||||||
|
} else {
|
||||||
|
// 提交
|
||||||
|
if (paperList.value.some(i => !i.submitAnswer && !i.submitAnswerPic)) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '还有题目未作答,确定要提交答案吗?',
|
||||||
|
confirmColor: '#ffe60f',
|
||||||
|
success: res => {
|
||||||
|
if (res.confirm) {
|
||||||
|
submitTrain();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
submitTrain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存当前题答案
|
||||||
|
const saveAnswer = async () => {
|
||||||
|
const currentQuestion = paperList.value[activeIdx.value];
|
||||||
|
if (!currentQuestion) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await saveRecordAnswer({
|
||||||
|
answer: currentQuestion.submitAnswer || '',
|
||||||
|
answerPic: currentQuestion.submitAnswerPic || '',
|
||||||
|
costTime: (paperInfo.value.costTime || 0) * 1000,
|
||||||
|
itemId: currentQuestion.itemId || currentQuestion.id,
|
||||||
|
recordId: params.value.recordId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存答案失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交每道题目答案
|
||||||
|
const submitQuestion = (answer: any) => {
|
||||||
|
if (reportFlag.value) return;
|
||||||
|
|
||||||
|
const idx = activeIdx.value;
|
||||||
|
if (answer.submitAnswer !== undefined) {
|
||||||
|
paperList.value[idx].submitAnswer = answer.submitAnswer;
|
||||||
|
}
|
||||||
|
if (answer.submitAnswerPic !== undefined) {
|
||||||
|
paperList.value[idx].submitAnswerPic = answer.submitAnswerPic;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单选题直接跳转下一题
|
||||||
|
if (answer.toNext && activeIdx.value < paperList.value.length - 1) {
|
||||||
|
setTimeout(() => switchQuestion(), 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交整个卷子
|
||||||
|
const submitTrain = async () => {
|
||||||
|
clearTimer();
|
||||||
|
submitLoading.value = true;
|
||||||
|
|
||||||
|
uni.showLoading({ title: '提交中...', mask: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先保存当前题
|
||||||
|
await saveAnswer();
|
||||||
|
|
||||||
|
// 提交作业
|
||||||
|
await submitExam({
|
||||||
|
answerDtoList: paperList.value.map(item => ({
|
||||||
|
itemId: item.itemId || item.id,
|
||||||
|
answer: item.submitAnswer || '',
|
||||||
|
answerPic: item.submitAnswerPic || '',
|
||||||
|
})),
|
||||||
|
costTime: (paperInfo.value.costTime || 0) * 1000,
|
||||||
|
recordId: params.value.recordId,
|
||||||
|
});
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '试卷提交成功请等待批改',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '好的',
|
||||||
|
confirmColor: '#ffe60f',
|
||||||
|
success: () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: error?.message || '提交失败', icon: 'none' });
|
||||||
|
startTimer(); // 重新开始计时
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始计时
|
||||||
|
const startTimer = (init?: boolean) => {
|
||||||
|
clearTimer();
|
||||||
|
if (init) {
|
||||||
|
paperInfo.value.costTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
paperInfo.value.costTime = (paperInfo.value.costTime || 0) + 1;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除计时器
|
||||||
|
const clearTimer = () => {
|
||||||
|
if (timeout) {
|
||||||
|
clearInterval(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化报告
|
||||||
|
const initReport = async () => {
|
||||||
|
const res = await getPaperRecordDetail({
|
||||||
|
recordId: params.value.recordId || params.value.id,
|
||||||
|
reportFlag: 1,
|
||||||
|
});
|
||||||
|
paperInfo.value = res.data;
|
||||||
|
paperList.value = res.data.subjectTitleVoList || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化新的答题
|
||||||
|
const initNewTrain = async () => {
|
||||||
|
const res = await startExam(params.value.recordId);
|
||||||
|
paperInfo.value = res.data || res;
|
||||||
|
if (paperInfo.value.costTime) {
|
||||||
|
paperInfo.value.costTime = Math.floor(paperInfo.value.costTime / 1000);
|
||||||
|
}
|
||||||
|
paperList.value = (res.data?.subjectTitleVoList || res.subjectTitleVoList || []).map(
|
||||||
|
(item: any, idx: number) => ({
|
||||||
|
...item,
|
||||||
|
unLook: idx > 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取胶囊按钮位置(避免时间被遮挡)
|
||||||
|
const getMenuButtonInfo = () => {
|
||||||
|
try {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
if (menuButtonInfo && systemInfo) {
|
||||||
|
// 计算胶囊按钮距离右边的距离(px)
|
||||||
|
const rightPx = systemInfo.windowWidth - menuButtonInfo.right;
|
||||||
|
// 胶囊按钮宽度(px)
|
||||||
|
const widthPx = menuButtonInfo.width;
|
||||||
|
// 转换为 rpx:750rpx = windowWidth(px)
|
||||||
|
const pxToRpx = 750 / systemInfo.windowWidth;
|
||||||
|
// 右边距 + 按钮宽度 + 额外间距(16rpx)
|
||||||
|
menuButtonRightRpx.value = Math.ceil((rightPx + widthPx) * pxToRpx) + 16;
|
||||||
|
} else {
|
||||||
|
// 默认值:约 120rpx(胶囊按钮宽度约 87px + 右边距约 7px + 间距)
|
||||||
|
menuButtonRightRpx.value = 120;
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
// 非微信小程序环境,不需要预留空间
|
||||||
|
menuButtonRightRpx.value = 0;
|
||||||
|
// #endif
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取胶囊按钮信息失败', error);
|
||||||
|
// 默认值:约 120rpx
|
||||||
|
menuButtonRightRpx.value = 120;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
pageLoading.value = true;
|
||||||
|
|
||||||
|
if (reportFlag.value) {
|
||||||
|
await initReport();
|
||||||
|
} else {
|
||||||
|
await initNewTrain();
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用传递的idx参数跳转到对应题目,确保不超过题目总数
|
||||||
|
const targetIdx = Math.min(initialIdx.value, paperList.value.length - 1);
|
||||||
|
activeIdx.value = Math.max(0, targetIdx);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('初始化失败', error);
|
||||||
|
uni.showToast({ title: error?.message || '加载失败', icon: 'none' });
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
} finally {
|
||||||
|
pageLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getMenuButtonInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimer();
|
||||||
|
paperInfo.value = {};
|
||||||
|
paperList.value = [];
|
||||||
|
activeIdx.value = 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dowork {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/main_bg.svg');
|
||||||
|
background-size: cover;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 10rpx 16rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&-timeout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-shadow: 0 2rpx 2rpx rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
height: calc(100vh - 44rpx);
|
||||||
|
|
||||||
|
&-box {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-top {
|
||||||
|
padding: 4rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2rpx 2rpx rgba(0, 0, 0, 0.25);
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
@include font-stroke($font-color, 2rpx);
|
||||||
|
@include single-ellipsis;
|
||||||
|
max-width: 400rpx;
|
||||||
|
|
||||||
|
// 修复伪元素溢出导致的黑色块问题
|
||||||
|
&::after {
|
||||||
|
@include single-ellipsis;
|
||||||
|
max-width: 400rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-box {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6rpx 0 8rpx;
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
flex: 1;
|
||||||
|
width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 4rpx 0;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
background: #bed5fe;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
background: #febebf;
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: #f00809;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: #ddff8c;
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: #8ecc00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
background: #cccccc;
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #ebf1fd;
|
||||||
|
border: 2rpx solid #7eacfe;
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: #7eacfe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-next {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 90rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background: linear-gradient(180deg, #4cd964 0%, #2db84d 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
box-shadow: 0 3rpx 8rpx rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-page {
|
||||||
|
height: calc(100vh - 44rpx);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
src/pages/home/index.vue
Normal file
15
src/pages/home/index.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<web-view src="http://192.168.0.109:80/"></web-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
onLoad(() => {
|
||||||
|
uni.setPageOrientation({
|
||||||
|
orientation: 'landscape'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
>
|
||||||
|
|
||||||
525
src/pages/homework/history/index.vue
Normal file
525
src/pages/homework/history/index.vue
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
<template>
|
||||||
|
<view class="history-page">
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<view class="top">
|
||||||
|
<qy-BackBar leftText="作业记录" />
|
||||||
|
<qy-Select
|
||||||
|
v-model="activeStatus"
|
||||||
|
:selectList="statusList"
|
||||||
|
@selectChange="handleChangeStatus"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 有学科数据时显示主体内容 -->
|
||||||
|
<view v-if="subjectData.length" class="main">
|
||||||
|
<!-- 侧边栏 - 学科选择 -->
|
||||||
|
<view class="main-sidebar">
|
||||||
|
<qy-Sidebar
|
||||||
|
v-model="activeClass"
|
||||||
|
:config="subjectData"
|
||||||
|
@handleChange="handleSelectClass"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 作业列表区域 -->
|
||||||
|
<view class="list-box">
|
||||||
|
<template v-if="jobList.length">
|
||||||
|
<scroll-view class="list" scroll-y :show-scrollbar="false">
|
||||||
|
<view
|
||||||
|
v-for="item in jobList"
|
||||||
|
:key="item.id"
|
||||||
|
class="list-item"
|
||||||
|
@click="toReports(item)"
|
||||||
|
>
|
||||||
|
<view class="list-item-info">
|
||||||
|
<view class="list-item-title">{{ activeSubjectName }}</view>
|
||||||
|
<view class="list-item-name">{{ item.paperName }}</view>
|
||||||
|
<view class="list-item-time">
|
||||||
|
{{ item.createTime ? `发布时间:${formatTime(item.createTime)}` : '' }}
|
||||||
|
|
||||||
|
{{ item.submitEndTime ? `截止时间:${formatTime(item.submitEndTime)}` : '' }}
|
||||||
|
|
||||||
|
{{ item.markTime ? `批改时间:${formatTime(item.markTime)}` : '' }}
|
||||||
|
</view>
|
||||||
|
<!-- 已批改时显示得分率 -->
|
||||||
|
<view v-if="item.gradingStatus === 1" class="list-item-rate" :style="{ color: getRateConfig(item).color }">
|
||||||
|
<text>得分率:{{ getScoreRate(item) }}%</text>
|
||||||
|
<qy-ProgressBar
|
||||||
|
:rate="item.score / item.totalScore"
|
||||||
|
width="196"
|
||||||
|
v-bind="getRateConfig(item)"
|
||||||
|
/>
|
||||||
|
<!-- 奖励显示 -->
|
||||||
|
<view class="reward-list">
|
||||||
|
<view v-if="item.addDiamond" class="reward-list-item">
|
||||||
|
<image :src="`${OSS_URL}/icon/diamond_nobg.svg`" mode="aspectFit" />
|
||||||
|
<text class="text">+{{ item.addDiamond }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="item.addExp" class="reward-list-item">
|
||||||
|
<image :src="`${OSS_URL}/icon/exp_nobg.svg`" mode="aspectFit" />
|
||||||
|
<text class="text">+{{ item.addExp }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
:class="['list-item-status', { active: item.gradingStatus === 1 }]"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: item.gradingStatus === 1
|
||||||
|
? `url(${OSS_URL}/urm/homeWork/corrected.svg)`
|
||||||
|
: `url(${OSS_URL}/urm/homeWork/uncorrected.svg)`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<text>{{ item.gradingStatus === 1 ? '已批改' : '未批改' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<view class="page" v-if="page.total > page.pageSize">
|
||||||
|
<view v-if="page.pageNum > 1" class="page-btn" @click="changePage(0)">
|
||||||
|
<text>上一页</text>
|
||||||
|
</view>
|
||||||
|
<text class="page-info">{{ page.pageNum }}/{{ Math.ceil(page.total / page.pageSize) }}</text>
|
||||||
|
<view
|
||||||
|
v-if="page.pageNum * page.pageSize < page.total"
|
||||||
|
class="page-btn success"
|
||||||
|
@click="changePage(1)"
|
||||||
|
>
|
||||||
|
<text>下一页</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<qy-Empty
|
||||||
|
v-else
|
||||||
|
class="empty"
|
||||||
|
:type="listLoading ? 'loading' : pageError ? 'network' : 'learn'"
|
||||||
|
:content="pageError ? '网络出问题了啦' : '暂无作业记录,快去做作业吧!'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 无学科数据时空状态 -->
|
||||||
|
<qy-Empty
|
||||||
|
v-else
|
||||||
|
class="empty-page"
|
||||||
|
:type="pageLoading ? 'loading' : 'learn'"
|
||||||
|
content="暂无作业记录,快去做作业吧!"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, reactive, onMounted } from 'vue';
|
||||||
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
import { getListCompleteRecord } from '@/api/homework';
|
||||||
|
import { user } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
|
||||||
|
const userStore = user();
|
||||||
|
const { userInfo } = storeToRefs(userStore);
|
||||||
|
|
||||||
|
// 颜色配置
|
||||||
|
const colorConfig: Record<string, string[]> = {
|
||||||
|
primary: ['#2D39BC', '#2D39BC', '#6B73E5', '#90FCF7'],
|
||||||
|
success: ['#2F8A5B', '#27A363', '#25BF6E', '#1FFF8A'],
|
||||||
|
error: ['#BC2D37', '#B22731', '#CB3741', '#FF3542'],
|
||||||
|
warning: ['#a13c00', '#C08129', '#CC8A33', '#FFFE36'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 状态选项
|
||||||
|
const statusList = [
|
||||||
|
{ label: '全部', value: '' },
|
||||||
|
{ label: '已批改', value: '1' },
|
||||||
|
{ label: '未批改', value: '0' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
const activeStatus = ref('');
|
||||||
|
// 学科数据(与 pages/homework/index 保持一致结构)
|
||||||
|
const subjectData = ref<any[]>([]);
|
||||||
|
// 当前选中的学科 id(string,与 homework/index 一致)
|
||||||
|
const activeClass = ref<string>('');
|
||||||
|
// 作业列表
|
||||||
|
const jobList = ref<any[]>([]);
|
||||||
|
// 分页
|
||||||
|
const page = reactive({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNum: 1,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
// 加载状态
|
||||||
|
const listLoading = ref(false);
|
||||||
|
const pageLoading = ref(false);
|
||||||
|
const pageError = ref(false);
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (time: string) => {
|
||||||
|
return time ? dayjs(time).format('YYYY/MM/DD HH:mm') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算得分率
|
||||||
|
const getScoreRate = (item: any) => {
|
||||||
|
if (!item.totalScore) return 0;
|
||||||
|
return ((item.score / item.totalScore) * 100).toFixed(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取得分率类型
|
||||||
|
const rateType = (rate: number) => {
|
||||||
|
if (rate >= 80) return 'success';
|
||||||
|
if (rate >= 60) return 'primary';
|
||||||
|
if (rate >= 40) return 'warning';
|
||||||
|
return 'error';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取颜色配置
|
||||||
|
const getRateConfig = (item: any) => {
|
||||||
|
const rate = item.totalScore ? (item.score / item.totalScore) * 100 : 0;
|
||||||
|
const type = rateType(rate);
|
||||||
|
const config = colorConfig[type] || colorConfig.primary;
|
||||||
|
return {
|
||||||
|
color: config[0],
|
||||||
|
border: config[1],
|
||||||
|
bg: config[2],
|
||||||
|
activeBg: config[3],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前选中学科名称
|
||||||
|
const activeSubjectName = computed(() => {
|
||||||
|
return subjectData.value.find(i => i.id === activeClass.value)?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化学科:从已完成作业记录中按学科分组,结构与 pages/homework/index 一致
|
||||||
|
const initSubject = async () => {
|
||||||
|
try {
|
||||||
|
// 只按 subjectId 维度拉一遍记录,用于构造学科列表
|
||||||
|
const res = await getListCompleteRecord({});
|
||||||
|
const records = res.data || [];
|
||||||
|
|
||||||
|
const subjectsMap = new Map<string, any>();
|
||||||
|
const subjects: any[] = [];
|
||||||
|
|
||||||
|
records.forEach((item: any) => {
|
||||||
|
const sid = String(item.subjectId);
|
||||||
|
if (!subjectsMap.has(sid)) {
|
||||||
|
subjectsMap.set(sid, true);
|
||||||
|
subjects.push({
|
||||||
|
id: sid,
|
||||||
|
name: item.subjectName,
|
||||||
|
warning: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subjectData.value = subjects;
|
||||||
|
if (subjects.length && !activeClass.value) {
|
||||||
|
activeClass.value = subjects[0].id;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化学科失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取作业列表
|
||||||
|
const getJobList = async () => {
|
||||||
|
try {
|
||||||
|
const params: Record<string, any> = {};
|
||||||
|
// 只有选择了“已批改/未批改”时才传 gradingStatus,避免出现 gradingStatus=undefined
|
||||||
|
if (activeStatus.value !== '') {
|
||||||
|
params.gradingStatus = activeStatus.value;
|
||||||
|
}
|
||||||
|
if (activeClass.value) {
|
||||||
|
params.subjectId = activeClass.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getListCompleteRecord(params);
|
||||||
|
jobList.value = res.data || [];
|
||||||
|
page.total = res.data?.length || 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取作业记录失败', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换状态
|
||||||
|
const handleChangeStatus = () => {
|
||||||
|
refreshJobList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换学科
|
||||||
|
const handleSelectClass = (item: any) => {
|
||||||
|
refreshJobList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页切换
|
||||||
|
const changePage = async (type: 0 | 1) => {
|
||||||
|
pageError.value = false;
|
||||||
|
const { total, pageNum, pageSize } = page;
|
||||||
|
if (type) {
|
||||||
|
if (pageNum * pageSize >= total) return;
|
||||||
|
page.pageNum++;
|
||||||
|
} else {
|
||||||
|
if (pageNum <= 1) return;
|
||||||
|
page.pageNum--;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
listLoading.value = true;
|
||||||
|
await getJobList();
|
||||||
|
} catch {
|
||||||
|
pageError.value = true;
|
||||||
|
}
|
||||||
|
listLoading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
const refreshJobList = async () => {
|
||||||
|
pageError.value = false;
|
||||||
|
listLoading.value = true;
|
||||||
|
jobList.value = [];
|
||||||
|
page.pageNum = 1;
|
||||||
|
try {
|
||||||
|
await getJobList();
|
||||||
|
} catch {
|
||||||
|
pageError.value = true;
|
||||||
|
}
|
||||||
|
listLoading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转报告页
|
||||||
|
const toReports = (item: any) => {
|
||||||
|
if (item.gradingStatus !== 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '作业未批改,请耐心等待老师批改',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/homework/reports/index?id=${item.id}&name=${encodeURIComponent(item.paperName)}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
pageLoading.value = true;
|
||||||
|
listLoading.value = true;
|
||||||
|
try {
|
||||||
|
await initSubject();
|
||||||
|
if (subjectData.value.length) {
|
||||||
|
await getJobList();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化失败', error);
|
||||||
|
}
|
||||||
|
listLoading.value = false;
|
||||||
|
pageLoading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面显示时刷新
|
||||||
|
onShow(() => {
|
||||||
|
if (subjectData.value.length) {
|
||||||
|
refreshJobList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.history-page {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/main_bg.svg');
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 12rpx 20rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: calc(100vh - 52rpx);
|
||||||
|
padding-left: 12rpx;
|
||||||
|
padding-right: 12rpx;
|
||||||
|
padding-top: 8rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-sidebar {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-box {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.page {
|
||||||
|
margin-top: 6rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
|
||||||
|
&-btn {
|
||||||
|
padding: 6rpx 14rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin: 0 10rpx;
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: linear-gradient(180deg, #4cd964 0%, #2db84d 100%);
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
background: #7effff;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
width: auto;
|
||||||
|
max-width: 200rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
border-top-left-radius: 10rpx;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: #666;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-time {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 70rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-rate {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
padding-bottom: 6rpx;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 11rpx;
|
||||||
|
|
||||||
|
.reward-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 18rpx;
|
||||||
|
height: 18rpx;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 11rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-page {
|
||||||
|
height: calc(100vh - 200rpx);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
537
src/pages/homework/index.vue
Normal file
537
src/pages/homework/index.vue
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
<template>
|
||||||
|
<view class="homework-page">
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<view class="top">
|
||||||
|
<qy-BackBar leftText="作业" />
|
||||||
|
<!-- <view class="top-history" @click="goHistory">
|
||||||
|
<image :src="`${OSS_URL}/icon/homeWork_hisJob.svg`" mode="aspectFit" />
|
||||||
|
</view> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 有学科数据时显示主体内容 -->
|
||||||
|
<view v-if="subjectList.length" class="main">
|
||||||
|
<!-- 侧边栏 - 学科选择 -->
|
||||||
|
<view class="main-sidebar">
|
||||||
|
<qy-Sidebar v-model="activeSubjectId" :config="subjectList" @handleChange="handleSubjectChange" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 作业本区域 -->
|
||||||
|
<view class="book">
|
||||||
|
<template v-if="paperList.length">
|
||||||
|
<!-- 左侧作业列表 -->
|
||||||
|
<view class="book-sidebar-box">
|
||||||
|
<scroll-view class="book-sidebar" scroll-y :show-scrollbar="false">
|
||||||
|
<view ref="bookSidebarRef" class="scroll-box">
|
||||||
|
<view
|
||||||
|
v-for="item in paperList"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['book-sidebar-item', { active: curPaper.id === item.id }]"
|
||||||
|
@click="handleSelectPaper(item)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="icon"
|
||||||
|
:src="curPaper.id === item.id
|
||||||
|
? `${OSS_URL}/icon/homeWork_job-item-active.svg`
|
||||||
|
: `${OSS_URL}/icon/homeWork_job-item.svg`"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
<view class="info">
|
||||||
|
<text class="info-title">{{ item.paperName }}</text>
|
||||||
|
<text class="info-time">{{ formatTime(item.createTime) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view v-if="listLoading" class="loading-box">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧作业详情 -->
|
||||||
|
<view class="book-main">
|
||||||
|
<view class="info flex-end">
|
||||||
|
<text class="info-title">{{ curPaper.paperName }}</text>
|
||||||
|
<text class="info-time">发布日期:{{ formatTime(curPaper.createTime) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info center">
|
||||||
|
<text class="info-name">
|
||||||
|
<text style="color: #999">来自:</text>{{ curPaper.publisherName }}
|
||||||
|
</text>
|
||||||
|
<text class="info-time">截止日期:{{ formatTime(curPaper.endTime) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="content">
|
||||||
|
<text>{{ curPaper.remark || '暂无作业说明' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="tab">
|
||||||
|
<view class="tab-icon">
|
||||||
|
<!-- <image :src="`${OSS_URL}/urm/homeWork/tab.svg`" mode="aspectFit" /> -->
|
||||||
|
<text>试卷</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="content paper">
|
||||||
|
<text>{{ curPaper.paperName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="btn-box">
|
||||||
|
<view
|
||||||
|
:class="['btn', curPaper.costTime ? 'success' : 'primary']"
|
||||||
|
@click="startDoWork"
|
||||||
|
>
|
||||||
|
<text>{{ curPaper.costTime ? '继续答题' : '开始答题' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 无作业时空状态 -->
|
||||||
|
<qy-Empty
|
||||||
|
v-else
|
||||||
|
class="empty"
|
||||||
|
:type="listLoading ? 'loading' : 'task'"
|
||||||
|
content="还没有作业哦~"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 无学科数据时空状态 -->
|
||||||
|
<qy-Empty
|
||||||
|
v-else
|
||||||
|
class="empty-page"
|
||||||
|
:type="pageLoading ? 'loading' : 'task'"
|
||||||
|
content="还没有作业哦~"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from 'vue';
|
||||||
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
import { getUnfinishedHomework } from '@/api/homework';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
|
||||||
|
// 资源类型常量
|
||||||
|
const resource_type_homework = 'homework';
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const pageLoading = ref(false);
|
||||||
|
const pageError = ref(false);
|
||||||
|
const listLoading = ref(false);
|
||||||
|
|
||||||
|
// 作业科目列表
|
||||||
|
const subjectList = ref<any[]>([]);
|
||||||
|
// 所有的作业记录
|
||||||
|
const subjectPaperMap = ref(new Map<string, any>());
|
||||||
|
// 当前选中的科目
|
||||||
|
const activeSubjectId = ref('');
|
||||||
|
// 作业列表
|
||||||
|
const paperList = ref<any[]>([]);
|
||||||
|
// 当前作业
|
||||||
|
const curPaper = ref<any>({});
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (time: string) => {
|
||||||
|
return time ? dayjs(time).format('YYYY/MM/DD HH:mm') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听学科切换
|
||||||
|
watch(activeSubjectId, (newVal) => {
|
||||||
|
if (!newVal) return;
|
||||||
|
paperList.value = subjectPaperMap.value.get(newVal)?.records || [];
|
||||||
|
curPaper.value = paperList.value[0] || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 学科变化
|
||||||
|
const handleSubjectChange = (item: any) => {
|
||||||
|
// 切换学科时可以做一些额外操作
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择作业
|
||||||
|
const handleSelectPaper = (item: any) => {
|
||||||
|
curPaper.value = item;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始答题
|
||||||
|
const startDoWork = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/doWork/index?resourceType=${resource_type_homework}&recordId=${curPaper.value.id}&reportFlag=0&subjectId=${activeSubjectId.value}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转历史记录
|
||||||
|
const goHistory = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/homework/history/index',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
pageLoading.value = true;
|
||||||
|
listLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getUnfinishedHomework();
|
||||||
|
const records = res.data || [];
|
||||||
|
|
||||||
|
// 按学科分组
|
||||||
|
const map = new Map<string, any>();
|
||||||
|
const subjects: any[] = [];
|
||||||
|
|
||||||
|
records.forEach((item: any) => {
|
||||||
|
const record = map.get(item.subjectId);
|
||||||
|
if (record) {
|
||||||
|
record.records.push(item);
|
||||||
|
} else {
|
||||||
|
map.set(item.subjectId, {
|
||||||
|
subjectId: item.subjectId,
|
||||||
|
subjectName: item.subjectName,
|
||||||
|
records: [item],
|
||||||
|
});
|
||||||
|
subjects.push({
|
||||||
|
id: item.subjectId,
|
||||||
|
name: item.subjectName,
|
||||||
|
warning: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subjectPaperMap.value = map;
|
||||||
|
subjectList.value = subjects;
|
||||||
|
|
||||||
|
// 选中第一个学科
|
||||||
|
if (subjects.length) {
|
||||||
|
activeSubjectId.value = subjects[0].id;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取作业列表失败', error);
|
||||||
|
pageError.value = true;
|
||||||
|
} finally {
|
||||||
|
pageLoading.value = false;
|
||||||
|
listLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initData();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每次进入页面刷新
|
||||||
|
onShow(() => {
|
||||||
|
if (subjectList.value.length) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.homework-page {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/mail_bg.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 12rpx 20rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-history {
|
||||||
|
image {
|
||||||
|
width: 64rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: calc(100vh - 52rpx);
|
||||||
|
padding-left: 16rpx;
|
||||||
|
padding-right: 20rpx;
|
||||||
|
padding-top: 8rpx;
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-sidebar {
|
||||||
|
width: 110rpx;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.book {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
// 渐变背景模拟便签效果
|
||||||
|
background: linear-gradient(135deg, #7dd3fc 0%, #38bdf8 50%, #0ea5e9 100%);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
// 添加阴影和边框效果
|
||||||
|
box-shadow:
|
||||||
|
0 8rpx 24rpx rgba(14, 165, 233, 0.3),
|
||||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.3);
|
||||||
|
border: 3rpx solid rgba(255, 255, 255, 0.4);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// 装饰效果 - 左上角折角
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background: linear-gradient(135deg, rgba(255,255,255,0.6) 50%, transparent 50%);
|
||||||
|
border-radius: 0 0 12rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-sidebar-box {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 5rpx;
|
||||||
|
border-right: 2rpx solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.scroll-box {
|
||||||
|
padding-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-sidebar-item {
|
||||||
|
padding: 12rpx 10rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 3rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: rgb(255, 240, 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #1c2a3a;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: block;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-time {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-box {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-main {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 10rpx 15rpx;
|
||||||
|
margin-bottom: 5rpx;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: 3rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.flex-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding-bottom: 6rpx;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.center {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
|
||||||
|
padding-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-time {
|
||||||
|
font-size: 8rpx;
|
||||||
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 6rpx 0;
|
||||||
|
text-indent: 2em;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.paper {
|
||||||
|
flex: none;
|
||||||
|
max-height: 24rpx;
|
||||||
|
text-indent: 0;
|
||||||
|
@include multiple-ellipsis(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
&-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// padding: 5rpx 15rpx;
|
||||||
|
width: 50rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/homeWork/tab.svg');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
// 脱离flex布局;
|
||||||
|
// display: inline-block;
|
||||||
|
|
||||||
|
// margin: 5rpx 0;
|
||||||
|
|
||||||
|
// image {
|
||||||
|
// // width: 30rpx;
|
||||||
|
// // height: 40rpx;
|
||||||
|
// margin-right: 8rpx;
|
||||||
|
// background-size: 100% 100%;
|
||||||
|
// background-repeat: no-repeat;
|
||||||
|
// background-position: center;
|
||||||
|
// }
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: 'PingFang SC';
|
||||||
|
font-size: 10rpx;
|
||||||
|
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-box {
|
||||||
|
padding-top: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: auto;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 25rpx;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background: linear-gradient(180deg, #ffe60f 0%, #ffd000 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: linear-gradient(180deg, #4cd964 0%, #2db84d 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-page {
|
||||||
|
height: calc(100vh - 200rpx);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
900
src/pages/homework/reports/index.vue
Normal file
900
src/pages/homework/reports/index.vue
Normal file
@ -0,0 +1,900 @@
|
|||||||
|
<template>
|
||||||
|
<view class="reports-page">
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<view class="top">
|
||||||
|
<qy-BackBar :leftText="paperName || '作业报告'" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<view v-if="!pageLoading" class="main">
|
||||||
|
<!-- 侧边栏 -->
|
||||||
|
<view class="main-sidebar">
|
||||||
|
<qy-Sidebar v-model="activeType" :config="sidebarConfig" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<scroll-view class="main-box" scroll-y :show-scrollbar="false">
|
||||||
|
<!-- 整体分析 -->
|
||||||
|
<view v-if="activeType === '0'" class="analyse-content">
|
||||||
|
<!-- 作业信息 + 得分概览 -->
|
||||||
|
<view class="analyse-header">
|
||||||
|
<view class="score-panel">
|
||||||
|
<view class="score-ring" :style="{ '--progress': correctRate + '%', '--color': scoreColor }">
|
||||||
|
<view class="score-inner">
|
||||||
|
<text class="score-num">{{ Number(jobAnalyse?.jobReportVo?.markScore) || 0 }}</text>
|
||||||
|
<text class="score-total">/{{ jobAnalyse?.jobReportVo?.totalScore || 100 }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="score-info">
|
||||||
|
<view class="score-rate" :style="{ color: scoreColor }">
|
||||||
|
<text class="rate-num">{{ correctRate }}</text>
|
||||||
|
<text class="rate-unit">%</text>
|
||||||
|
</view>
|
||||||
|
<text class="score-label">正确率</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="job-info">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">作业名称</text>
|
||||||
|
<text class="info-value ellipsis">{{ jobAnalyse?.jobReportVo?.jobName || paperName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">总分</text>
|
||||||
|
<text class="info-value">{{ jobAnalyse?.jobReportVo?.totalScore || 0 }}分</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">作答时长</text>
|
||||||
|
<text class="info-value">{{ costTimeText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 答题统计 -->
|
||||||
|
<view class="analyse-stats">
|
||||||
|
<view class="stats-title">答题统计</view>
|
||||||
|
<view class="stats-grid">
|
||||||
|
<view class="stat-card correct">
|
||||||
|
<view class="stat-icon">✓</view>
|
||||||
|
<view class="stat-num">{{ correctNum }}</view>
|
||||||
|
<view class="stat-label">答对</view>
|
||||||
|
</view>
|
||||||
|
<view class="stat-card wrong">
|
||||||
|
<view class="stat-icon">✕</view>
|
||||||
|
<view class="stat-num">{{ wrongNum }}</view>
|
||||||
|
<view class="stat-label">答错</view>
|
||||||
|
</view>
|
||||||
|
<view class="stat-card unanswered">
|
||||||
|
<view class="stat-icon">-</view>
|
||||||
|
<view class="stat-num">{{ unansweredNum }}</view>
|
||||||
|
<view class="stat-label">未答</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 题型分布 -->
|
||||||
|
<view v-if="channelAnalyzeList.length" class="analyse-chart">
|
||||||
|
<view class="chart-title">题型分布</view>
|
||||||
|
<view class="pie-chart-container">
|
||||||
|
<view class="pie-chart" :style="pieChartStyle"></view>
|
||||||
|
<view class="pie-legend">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in channelAnalyzeList"
|
||||||
|
:key="idx"
|
||||||
|
class="legend-item"
|
||||||
|
>
|
||||||
|
<view class="legend-dot" :style="{ background: pieColors[idx % pieColors.length] }"></view>
|
||||||
|
<text class="legend-name">{{ item.chanelTypeName }}</text>
|
||||||
|
<text class="legend-count">{{ item.titleCount }}题</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 难度分布 -->
|
||||||
|
<view v-if="difficultyAnalyzeList.length" class="analyse-chart">
|
||||||
|
<view class="chart-title">难度分布</view>
|
||||||
|
<view class="bar-chart-container">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in difficultyAnalyzeList"
|
||||||
|
:key="idx"
|
||||||
|
class="bar-item"
|
||||||
|
>
|
||||||
|
<view class="bar-label">{{ item.difficultyName }}</view>
|
||||||
|
<view class="bar-track">
|
||||||
|
<view
|
||||||
|
class="bar-fill"
|
||||||
|
:style="{
|
||||||
|
width: getBarWidth(item.score) + '%',
|
||||||
|
background: difficultyColors[item.difficultyName] || '#8FCC00'
|
||||||
|
}"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
<view class="bar-value">{{ item.score }}分</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 答题详情 -->
|
||||||
|
<view v-else-if="activeType === '1'" class="detail-content">
|
||||||
|
<view class="detail-title">答题情况</view>
|
||||||
|
<view v-if="paperList.length" class="detail-grid">
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in paperList"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['detail-card', getStatusClass(item)]"
|
||||||
|
@click="viewQuestion(idx)"
|
||||||
|
>
|
||||||
|
<text class="card-num">{{ idx + 1 }}</text>
|
||||||
|
<view class="card-status">
|
||||||
|
<text>{{ getStatusText(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<qy-Empty v-else type="task" content="暂无题目数据" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 知识点分析 -->
|
||||||
|
<view v-else-if="activeType === '2'" class="knowledge-content">
|
||||||
|
<view v-if="knowledgeList.length" class="knowledge-table">
|
||||||
|
<view class="table-header">
|
||||||
|
<view class="th name">知识点名称</view>
|
||||||
|
<view class="th score">满分</view>
|
||||||
|
<view class="th score">得分</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-for="(item, idx) in knowledgeList"
|
||||||
|
:key="idx"
|
||||||
|
class="table-row"
|
||||||
|
>
|
||||||
|
<view class="td name">
|
||||||
|
<text>{{ item.knowledgeName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="td score">
|
||||||
|
<text>{{ item.titleScore }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="td score">
|
||||||
|
<text
|
||||||
|
class="score-text"
|
||||||
|
:style="{ color: getKnowledgeScoreColor(item) }"
|
||||||
|
>{{ item.answerScore }}</text>
|
||||||
|
<text class="score-divider">/</text>
|
||||||
|
<text class="score-total">{{ item.titleScore }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<qy-Empty v-else type="task" content="暂无知识点数据" />
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<qy-Empty v-else class="empty" type="loading" content="加载中..." />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { getJobAnalyse, getKnowledgeAnalyse, getPaperRecordDetail } from '@/api/homework';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
|
||||||
|
// 参数
|
||||||
|
const recordId = ref('');
|
||||||
|
const paperName = ref('');
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const pageLoading = ref(false);
|
||||||
|
const activeType = ref('0');
|
||||||
|
|
||||||
|
// 数据
|
||||||
|
const jobAnalyse = ref<any>({});
|
||||||
|
const paperList = ref<any[]>([]);
|
||||||
|
const knowledgeList = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 侧边栏配置
|
||||||
|
const sidebarConfig = ref([
|
||||||
|
{ id: '0', name: '整体分析' },
|
||||||
|
{ id: '1', name: '答题详情' },
|
||||||
|
{ id: '2', name: '知识点分析' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 颜色配置
|
||||||
|
const pieColors = ['#4F8EF7', '#8FCC00', '#FFD04D', '#FF8200', '#FF5151', '#48A3FE', '#9B59B6'];
|
||||||
|
const difficultyColors: Record<string, string> = {
|
||||||
|
'容易': '#8FCC00',
|
||||||
|
'较易': '#48A3FE',
|
||||||
|
'普通': '#FFD04D',
|
||||||
|
'较难': '#FF8200',
|
||||||
|
'困难': '#FF5151',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 题型分析列表
|
||||||
|
const channelAnalyzeList = computed(() => jobAnalyse.value?.paperChanelAnalyzeList || []);
|
||||||
|
|
||||||
|
// 难度分析列表
|
||||||
|
const difficultyAnalyzeList = computed(() => jobAnalyse.value?.paperDifficultyAnalyzeList || []);
|
||||||
|
|
||||||
|
// 饼图样式
|
||||||
|
const pieChartStyle = computed(() => {
|
||||||
|
const data = channelAnalyzeList.value;
|
||||||
|
if (!data.length) return {};
|
||||||
|
|
||||||
|
const total = data.reduce((sum: number, item: any) => sum + (item.titleCount || 0), 0);
|
||||||
|
if (total === 0) return {};
|
||||||
|
|
||||||
|
let gradientParts: string[] = [];
|
||||||
|
let currentAngle = 0;
|
||||||
|
|
||||||
|
data.forEach((item: any, idx: number) => {
|
||||||
|
const percentage = (item.titleCount / total) * 100;
|
||||||
|
const color = pieColors[idx % pieColors.length];
|
||||||
|
const nextAngle = currentAngle + percentage;
|
||||||
|
gradientParts.push(`${color} ${currentAngle}% ${nextAngle}%`);
|
||||||
|
currentAngle = nextAngle;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
background: `conic-gradient(${gradientParts.join(', ')})`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 答对数量
|
||||||
|
const correctNum = computed(() => {
|
||||||
|
return paperList.value.filter(item => item.correctResult === 1 || item.correctResult === true).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 答错数量
|
||||||
|
const wrongNum = computed(() => {
|
||||||
|
return paperList.value.filter(item =>
|
||||||
|
(item.correctResult === 0 || item.correctResult === false) &&
|
||||||
|
(item.submitAnswer || item.submitAnswerPic)
|
||||||
|
).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未答数量
|
||||||
|
const unansweredNum = computed(() => {
|
||||||
|
return paperList.value.filter(item => !item.submitAnswer && !item.submitAnswerPic).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性 - 正确率(用答对题数 / 总题数计算)
|
||||||
|
const correctRate = computed(() => {
|
||||||
|
const total = paperList.value.length;
|
||||||
|
if (total === 0) return 0;
|
||||||
|
return Math.round((correctNum.value / total) * 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 得分颜色
|
||||||
|
const scoreColor = computed(() => {
|
||||||
|
const rate = Number(correctRate.value) || 0;
|
||||||
|
if (rate >= 80) return '#4cd964';
|
||||||
|
if (rate >= 60) return '#4F8EF7';
|
||||||
|
if (rate >= 40) return '#ffa200';
|
||||||
|
return '#ff4d4f';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用时文本
|
||||||
|
const costTimeText = computed(() => {
|
||||||
|
const ms = Number(jobAnalyse.value?.jobReportVo?.costTime || 0);
|
||||||
|
const total = Math.floor(ms / 1000);
|
||||||
|
const h = Math.floor(total / 3600);
|
||||||
|
const m = Math.floor((total % 3600) / 60);
|
||||||
|
const s = total % 60;
|
||||||
|
|
||||||
|
if (h > 0) {
|
||||||
|
return `${h}时${m}分${s}秒`;
|
||||||
|
}
|
||||||
|
if (m > 0) {
|
||||||
|
return `${m}分${s}秒`;
|
||||||
|
}
|
||||||
|
return `${s}秒`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取柱状图宽度
|
||||||
|
const getBarWidth = (score: number) => {
|
||||||
|
const maxScore = Math.max(...difficultyAnalyzeList.value.map((i: any) => i.score || 0), 1);
|
||||||
|
return Math.round((score / maxScore) * 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onLoad((options: any) => {
|
||||||
|
recordId.value = options.id || '';
|
||||||
|
paperName.value = options.name ? decodeURIComponent(options.name) : '';
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取状态样式类
|
||||||
|
const getStatusClass = (item: any) => {
|
||||||
|
if (item.correctResult === 1 || item.correctResult === true) return 'correct';
|
||||||
|
if (!item.submitAnswer && !item.submitAnswerPic) return 'unanswered';
|
||||||
|
return 'wrong';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (item: any) => {
|
||||||
|
if (item.correctResult === 1 || item.correctResult === true) return '正确';
|
||||||
|
if (!item.submitAnswer && !item.submitAnswerPic) return '未答';
|
||||||
|
return '错误';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取知识点分数颜色
|
||||||
|
const getKnowledgeScoreColor = (item: any) => {
|
||||||
|
const score = Number(item.answerScore) || 0;
|
||||||
|
const total = Number(item.titleScore) || 1;
|
||||||
|
const rate = (score / total) * 100;
|
||||||
|
|
||||||
|
if (rate >= 80) return '#4cd964';
|
||||||
|
if (rate >= 60) return '#4F8EF7';
|
||||||
|
if (rate >= 40) return '#ffa200';
|
||||||
|
return '#ff4d4f';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看题目详情
|
||||||
|
const viewQuestion = (idx: number) => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/doWork/index?recordId=${recordId.value}&reportFlag=1&idx=${idx}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
const init = async () => {
|
||||||
|
pageLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 获取整体分析
|
||||||
|
const analyseRes = await getJobAnalyse({ recordId: recordId.value });
|
||||||
|
jobAnalyse.value = analyseRes.data || {};
|
||||||
|
console.log('整体分析数据:', jobAnalyse.value);
|
||||||
|
|
||||||
|
// 获取题目列表
|
||||||
|
const detailRes = await getPaperRecordDetail({
|
||||||
|
recordId: recordId.value,
|
||||||
|
reportFlag: 1,
|
||||||
|
});
|
||||||
|
paperList.value = detailRes.data?.subjectTitleVoList || [];
|
||||||
|
console.log('题目列表:', paperList.value);
|
||||||
|
|
||||||
|
// 获取知识点分析
|
||||||
|
try {
|
||||||
|
const knowledgeRes = await getKnowledgeAnalyse({ recordId: recordId.value });
|
||||||
|
knowledgeList.value = knowledgeRes.data || [];
|
||||||
|
console.log('知识点分析:', knowledgeList.value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取知识点分析失败', e);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载报告失败', error);
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' });
|
||||||
|
} finally {
|
||||||
|
pageLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.reports-page {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/main_bg.svg');
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 12rpx 20rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: calc(100vh - 52rpx);
|
||||||
|
padding-left: 12rpx;
|
||||||
|
padding-right: 12rpx;
|
||||||
|
padding-top: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-sidebar {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-box {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 整体分析
|
||||||
|
.analyse-content {
|
||||||
|
.analyse-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding-bottom: 16rpx;
|
||||||
|
border-bottom: 2rpx solid #eef2f7;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
background: linear-gradient(135deg, #f0f7ff 0%, #e8f4fd 100%);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
min-width: 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-ring {
|
||||||
|
width: 70rpx;
|
||||||
|
height: 70rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: conic-gradient(
|
||||||
|
var(--color) 0% var(--progress),
|
||||||
|
#e8e8e8 var(--progress) 100%
|
||||||
|
);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 54rpx;
|
||||||
|
height: 54rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-inner {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.score-num {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-total {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.score-rate {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.rate-num {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-unit {
|
||||||
|
font-size: 12rpx;
|
||||||
|
margin-left: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 11rpx;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #999;
|
||||||
|
width: 70rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: $font-color;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.ellipsis {
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 答题统计
|
||||||
|
.analyse-stats {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
.stats-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
padding-left: 8rpx;
|
||||||
|
border-left: 4rpx solid $theme-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 10rpx 12rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
border: 2rpx solid;
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.correct {
|
||||||
|
background: linear-gradient(135deg, #f0fff4 0%, #e6ffed 100%);
|
||||||
|
border-color: #b7eb8f;
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wrong {
|
||||||
|
background: linear-gradient(135deg, #fff1f0 0%, #ffeded 100%);
|
||||||
|
border-color: #ffa39e;
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
background: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unanswered {
|
||||||
|
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
background: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图表区域
|
||||||
|
.analyse-chart {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
padding-left: 8rpx;
|
||||||
|
border-left: 4rpx solid $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 饼图
|
||||||
|
.pie-chart-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 12rpx;
|
||||||
|
background: #f9fbfd;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-legend {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8rpx 16rpx;
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 10rpx;
|
||||||
|
height: 10rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-name {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-count {
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 柱状图
|
||||||
|
.bar-chart-container {
|
||||||
|
padding: 12rpx;
|
||||||
|
background: #f9fbfd;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-label {
|
||||||
|
width: 50rpx;
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-track {
|
||||||
|
flex: 1;
|
||||||
|
height: 16rpx;
|
||||||
|
background: #e8e8e8;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-value {
|
||||||
|
width: 50rpx;
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 答题详情
|
||||||
|
.detail-content {
|
||||||
|
.detail-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 60rpx);
|
||||||
|
gap: 10rpx;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
border: 2rpx solid;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 18rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-status {
|
||||||
|
font-size: 8rpx;
|
||||||
|
margin-top: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.correct {
|
||||||
|
background: linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%);
|
||||||
|
border-color: #52c41a;
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-status {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wrong {
|
||||||
|
background: linear-gradient(135deg, #fff2f0 0%, #ffccc7 100%);
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-status {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unanswered {
|
||||||
|
background: linear-gradient(135deg, #fafafa 0%, #e8e8e8 100%);
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-status {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 知识点分析
|
||||||
|
.knowledge-content {
|
||||||
|
.knowledge-table {
|
||||||
|
border: 2rpx solid #4F8EF7;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
background: linear-gradient(135deg, #e8f4fd 0%, #d6ebfa 100%);
|
||||||
|
|
||||||
|
.th {
|
||||||
|
padding: 10rpx 8rpx;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.name {
|
||||||
|
flex: 2;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.score {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx dashed #e8e8e8;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: #fafcff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td {
|
||||||
|
padding: 10rpx 8rpx;
|
||||||
|
font-size: 11rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.name {
|
||||||
|
flex: 2;
|
||||||
|
padding-left: 12rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.score {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.score-text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-divider {
|
||||||
|
color: #999;
|
||||||
|
margin: 0 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-total {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
height: calc(100vh - 120rpx);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
405
src/pages/index/index.vue
Normal file
405
src/pages/index/index.vue
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 左侧区域 -->
|
||||||
|
<view class="left-section">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="header">
|
||||||
|
<view class="title" data-content="学小乐AI">学小乐AI</view>
|
||||||
|
<view class="subtitle">智能学习助手</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 用户信息区 -->
|
||||||
|
<view class="user-section" v-if="isLoggedIn">
|
||||||
|
<view class="user-info">
|
||||||
|
<image class="avatar" :src="userInfo.avatarUrl || defaultAvatar" mode="aspectFill" />
|
||||||
|
<view class="user-detail">
|
||||||
|
<text class="nickname">{{ userInfo.nickName || '学小乐用户' }}</text>
|
||||||
|
<text class="school">{{ userInfo.schoolName || '' }} {{ userInfo.className || '' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="logout-btn" @click="handleLogout">
|
||||||
|
<text>退出登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="user-section login-section" v-else @click="goLogin">
|
||||||
|
<view class="login-hint">
|
||||||
|
<!-- <view class="login-icon">👤</view> -->
|
||||||
|
<image class="login-icon" :src="defaultAvatar" mode="aspectFill" />
|
||||||
|
<text>点击登录账号</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部装饰 -->
|
||||||
|
<view class="footer">
|
||||||
|
<text>— 学习更轻松 —</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧功能入口 -->
|
||||||
|
<view class="right-section">
|
||||||
|
<view class="content">
|
||||||
|
<view class="entry-card" @click="handleNavigate('/pages/homework/index')">
|
||||||
|
<view class="card-icon homework-icon">
|
||||||
|
<image :src="`${OSS_URL}/icon/homeWork_job-item.svg`" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="card-info">
|
||||||
|
<text class="card-title">我的作业</text>
|
||||||
|
<text class="card-desc">
|
||||||
|
待完成作业
|
||||||
|
<text v-if="unfinishedCount > 0" class="badge">{{ unfinishedCount }}</text>
|
||||||
|
<text v-else>0</text>
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-arrow"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="entry-card" @click="handleNavigate('/pages/homework/history/index')">
|
||||||
|
<view class="card-icon history-icon">
|
||||||
|
<image :src="`${OSS_URL}/icon/homeWork_hisJob.svg`" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="card-info">
|
||||||
|
<text class="card-title">作业记录</text>
|
||||||
|
<text class="card-desc">查看已完成的作业历史</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-arrow"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
import { user } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { getUnfinishedHomework } from '@/api/homework';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
const defaultAvatar = `${OSS_URL}/urm/logo.png`;
|
||||||
|
const logoUrl = `${OSS_URL}/urm/logo.png`;
|
||||||
|
|
||||||
|
const userStore = user();
|
||||||
|
const { userInfo, token } = storeToRefs(userStore);
|
||||||
|
|
||||||
|
const isLoggedIn = computed(() => !!token.value);
|
||||||
|
|
||||||
|
// 未完成作业数量
|
||||||
|
const unfinishedCount = ref(0);
|
||||||
|
|
||||||
|
// 获取未完成作业数量
|
||||||
|
const fetchUnfinishedCount = async () => {
|
||||||
|
if (!token.value) {
|
||||||
|
unfinishedCount.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res: any = await getUnfinishedHomework();
|
||||||
|
// 根据后端返回结构计算数量,常见两种:直接列表或按学科分组
|
||||||
|
if (Array.isArray(res?.data)) {
|
||||||
|
unfinishedCount.value = res.data.length;
|
||||||
|
} else if (Array.isArray(res?.data?.records)) {
|
||||||
|
unfinishedCount.value = res.data.records.length;
|
||||||
|
} else if (Array.isArray(res)) {
|
||||||
|
unfinishedCount.value = res.length;
|
||||||
|
} else {
|
||||||
|
unfinishedCount.value = 0;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取未完成作业数量失败', error);
|
||||||
|
unfinishedCount.value = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导航处理,检查登录状态
|
||||||
|
const handleNavigate = (url: string) => {
|
||||||
|
if (!token.value) {
|
||||||
|
// 未登录跳转登录页,携带回调地址
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/login/index?redirect=${encodeURIComponent(url)}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 去登录
|
||||||
|
const goLogin = () => {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
userStore.logout();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面显示时刷新用户信息
|
||||||
|
onShow(() => {
|
||||||
|
if (token.value) {
|
||||||
|
userStore.getUserInfo().catch(() => {});
|
||||||
|
fetchUnfinishedCount();
|
||||||
|
} else {
|
||||||
|
unfinishedCount.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/bg_home.svg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧区域
|
||||||
|
.left-section {
|
||||||
|
width: 280rpx;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
// background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
@include font-stroke($font-color, 3rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin-top: 8rpx;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-section {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 8rpx;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-detail {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 10rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.login-section {
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-hint {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #000;
|
||||||
|
// text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧区域
|
||||||
|
.right-section {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.homework-icon {
|
||||||
|
background: linear-gradient(135deg, #ffe60f 0%, #ffd000 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.history-icon {
|
||||||
|
background: linear-gradient(135deg, #6dd5fa 0%, #2980b9 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-desc {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-arrow {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-right: 3rpx solid #ccc;
|
||||||
|
border-bottom: 3rpx solid #ccc;
|
||||||
|
transform: translate(-70%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
394
src/pages/login/index.vue
Normal file
394
src/pages/login/index.vue
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- 返回按钮 -->
|
||||||
|
<view v-if="showBack" class="back-bar" @click="goBack">
|
||||||
|
<view class="icon-arrow"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 登录框 -->
|
||||||
|
<view class="box">
|
||||||
|
<image class="logo" :src="logoUrl" mode="aspectFit" @click="onEnter" />
|
||||||
|
|
||||||
|
<!-- 角色切换 -->
|
||||||
|
<view class="role-switch">
|
||||||
|
<!-- <view
|
||||||
|
class="role-item"
|
||||||
|
:class="{ active: userRole === 'student' }"
|
||||||
|
@click="userRole = 'student'"
|
||||||
|
>
|
||||||
|
<text>学生</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="role-item"
|
||||||
|
:class="{ active: userRole === 'teacher' }"
|
||||||
|
@click="userRole = 'teacher'"
|
||||||
|
>
|
||||||
|
<text>老师</text>
|
||||||
|
</view> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form">
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">账号:</text>
|
||||||
|
<input
|
||||||
|
v-model="account"
|
||||||
|
class="input"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
@confirm="onLogin"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">密码:</text>
|
||||||
|
<input
|
||||||
|
v-model="password"
|
||||||
|
class="input"
|
||||||
|
:password="hidePassword"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
@confirm="onLogin"
|
||||||
|
/>
|
||||||
|
<view class="eye" @click="hidePassword = !hidePassword">
|
||||||
|
<image :src="hidePassword ? eyeCloseIcon : eyeOpenIcon" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="btn-login" @click="onLogin">
|
||||||
|
<text>登 录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 二维码 -->
|
||||||
|
<!-- <view class="qr">
|
||||||
|
<view class="qr-border">
|
||||||
|
<image :src="qrCodeUrl" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="qr-info">
|
||||||
|
<text>更多内容关注</text>
|
||||||
|
<text>学小乐AI公众号</text>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { user } from '@/store';
|
||||||
|
import { teacher } from '@/store/teacher';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
const logoUrl = `${OSS_URL}/urm/logo.png`;
|
||||||
|
const qrCodeUrl = `${OSS_URL}/urm/qrcode.png`;
|
||||||
|
const eyeCloseIcon = `${OSS_URL}/icon/eye_close.svg`;
|
||||||
|
const eyeOpenIcon = `${OSS_URL}/icon/eye_open.svg`;
|
||||||
|
|
||||||
|
const account = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const hidePassword = ref(true);
|
||||||
|
const showBack = ref(false);
|
||||||
|
const redirect = ref('');
|
||||||
|
const enterDebug = ref(0);
|
||||||
|
const userRole = ref<'student' | 'teacher'>('student'); // 默认学生
|
||||||
|
|
||||||
|
const userStore = user();
|
||||||
|
const teacherStore = teacher();
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onLoad((options: any) => {
|
||||||
|
if (options?.redirect) {
|
||||||
|
redirect.value = decodeURIComponent(options.redirect);
|
||||||
|
showBack.value = true;
|
||||||
|
}
|
||||||
|
if (options?.a) {
|
||||||
|
account.value = options.a;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
// debug入口
|
||||||
|
const onEnter = () => {
|
||||||
|
enterDebug.value++;
|
||||||
|
if (enterDebug.value >= 10) {
|
||||||
|
enterDebug.value = 0;
|
||||||
|
uni.showToast({ title: 'Debug模式', icon: 'none' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const onLogin = async () => {
|
||||||
|
if (!account.value) {
|
||||||
|
uni.showToast({ title: '请输入账号', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!password.value) {
|
||||||
|
uni.showToast({ title: '请输入密码', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({ title: '登录中...', mask: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginData: any = {
|
||||||
|
account: account.value,
|
||||||
|
password: password.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果是老师登录,添加 clientType 参数
|
||||||
|
if (userRole.value === 'teacher') {
|
||||||
|
loginData.clientType = 'MANAGE';
|
||||||
|
}
|
||||||
|
|
||||||
|
await userStore.accordLogin(loginData);
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '登录成功', icon: 'success' });
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
// 如果是老师登录,跳转到老师首页
|
||||||
|
if (userRole.value === 'teacher') {
|
||||||
|
// 获取老师信息
|
||||||
|
try {
|
||||||
|
await teacherStore.getLoginUser();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取老师信息失败', error);
|
||||||
|
}
|
||||||
|
if (redirect.value) {
|
||||||
|
uni.redirectTo({ url: redirect.value });
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({ url: '/pages/teacher/index/index' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 学生登录,跳转到学生首页
|
||||||
|
if (redirect.value) {
|
||||||
|
uni.redirectTo({ url: redirect.value });
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: error?.message || '登录失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/bg_login.svg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 20rpx;
|
||||||
|
left: 24rpx;
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.icon-arrow {
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-left: 4rpx solid #333;
|
||||||
|
border-bottom: 4rpx solid #333;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
margin-left: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 300rpx;
|
||||||
|
padding: 0 12px 12rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
border: 3rpx solid #fff;
|
||||||
|
background: linear-gradient(
|
||||||
|
133deg,
|
||||||
|
rgba(240, 240, 240, 0.9) 2.9%,
|
||||||
|
rgba(240, 240, 240, 0.65) 49.82%,
|
||||||
|
#f0f0f0 96.89%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
position: absolute;
|
||||||
|
top: -50rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.25);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
|
||||||
|
.role-item {
|
||||||
|
flex: 1;
|
||||||
|
height: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(180deg, #ffeb3b 0%, #ffc107 100%);
|
||||||
|
box-shadow: 0 2rpx 6rpx rgba(255, 193, 7, 0.3);
|
||||||
|
|
||||||
|
text {
|
||||||
|
color: $font-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 56rpx;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 12rpx;
|
||||||
|
color: $font-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eye {
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
height: 40rpx;
|
||||||
|
background: linear-gradient(180deg, #ffeb3b 0%, #ffc107 100%);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba(255, 193, 7, 0.4);
|
||||||
|
margin-top: 8rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 14rpx;
|
||||||
|
color: $font-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr {
|
||||||
|
width: 140rpx;
|
||||||
|
position: absolute;
|
||||||
|
top: 20rpx;
|
||||||
|
right: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.qr-border {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
padding: 4rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
border: 3rpx solid transparent;
|
||||||
|
background-image: linear-gradient(#fff, #fff),
|
||||||
|
linear-gradient(180deg, #fff01f, #4de75c);
|
||||||
|
background-origin: border-box;
|
||||||
|
background-clip: padding-box, border-box;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-info {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.25);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
443
src/pages/teacher/homework/index.vue
Normal file
443
src/pages/teacher/homework/index.vue
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
<template>
|
||||||
|
<view class="homework-page">
|
||||||
|
<!-- 顶部栏 -->
|
||||||
|
<view class="top">
|
||||||
|
<qy-BackBar leftText="作业记录" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<view class="main">
|
||||||
|
<view class="list-box">
|
||||||
|
<template v-if="recordList.length">
|
||||||
|
<scroll-view
|
||||||
|
class="list"
|
||||||
|
scroll-y
|
||||||
|
:show-scrollbar="false"
|
||||||
|
@scrolltolower="loadMore"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in recordList"
|
||||||
|
:key="item.id"
|
||||||
|
class="list-item-wrapper"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="list-item"
|
||||||
|
@click="handleItemClick(item)"
|
||||||
|
>
|
||||||
|
<view class="list-item-content">
|
||||||
|
<!-- 科目标签 -->
|
||||||
|
<view class="list-item-subject">{{ item.subjectName }}</view>
|
||||||
|
|
||||||
|
<!-- 作业标题 -->
|
||||||
|
<view class="list-item-name">{{ item.name }}</view>
|
||||||
|
|
||||||
|
<!-- 试卷名称 -->
|
||||||
|
<view class="list-item-paper">{{ item.paperName }}</view>
|
||||||
|
|
||||||
|
<!-- 时间信息 -->
|
||||||
|
<view class="list-item-time">
|
||||||
|
<text class="time-label">开始时间:</text>
|
||||||
|
<text class="time-value">{{ formatTime(item.startTime) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="list-item-time">
|
||||||
|
<text class="time-label">结束时间:</text>
|
||||||
|
<text class="time-value">{{ formatTime(item.endTime) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 数量统计 -->
|
||||||
|
<view class="list-item-stats">
|
||||||
|
<text class="stat-item">发布数量: {{ item.number }}</text>
|
||||||
|
<text class="stat-item">完成数量: {{ item.completeNumber }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 状态指示器 -->
|
||||||
|
<view
|
||||||
|
:class="['list-item-status', getStatusClass(item.status)]"
|
||||||
|
>
|
||||||
|
<text>{{ getStatusText(item.status) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<view v-if="index < recordList.length - 1" class="divider"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view class="load-more-box">
|
||||||
|
<uni-load-more
|
||||||
|
:status="loadMoreStatus"
|
||||||
|
:content-text="loadMoreText"
|
||||||
|
@clickLoadMore="loadMore"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<qy-Empty
|
||||||
|
v-else
|
||||||
|
class="empty"
|
||||||
|
:type="listLoading ? 'loading' : pageError ? 'network' : 'learn'"
|
||||||
|
:content="pageError ? '网络出问题了啦' : '暂无作业记录'"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
import { getPaperReleaseRecordList } from '@/api/teacher';
|
||||||
|
import type { PaperReleaseRecord } from '@/api/teacher';
|
||||||
|
import { teacher } from '@/store/teacher';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
|
||||||
|
const teacherStore = teacher();
|
||||||
|
const { schoolId } = storeToRefs(teacherStore);
|
||||||
|
|
||||||
|
// 作业记录列表
|
||||||
|
const recordList = ref<PaperReleaseRecord[]>([]);
|
||||||
|
// 分页参数
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
// 加载状态
|
||||||
|
const listLoading = ref(false);
|
||||||
|
const pageError = ref(false);
|
||||||
|
// 加载更多状态
|
||||||
|
const loadMoreStatus = ref<'more' | 'loading' | 'noMore'>('more');
|
||||||
|
|
||||||
|
// 加载更多文本配置
|
||||||
|
const loadMoreText = {
|
||||||
|
contentdown: '上拉加载更多',
|
||||||
|
contentrefresh: '正在加载...',
|
||||||
|
contentnomore: '没有更多数据了',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (time: string) => {
|
||||||
|
return time ? dayjs(time).format('YYYY/MM/DD HH:mm') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status: number) => {
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
0: '进行中',
|
||||||
|
1: '已结束',
|
||||||
|
};
|
||||||
|
return statusMap[status] || '未知';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态样式类
|
||||||
|
const getStatusClass = (status: number) => {
|
||||||
|
return status === 0 ? 'status-active' : 'status-ended';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取作业记录列表
|
||||||
|
const fetchRecordList = async (isLoadMore = false) => {
|
||||||
|
if (!schoolId.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请先登录',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoadMore) {
|
||||||
|
// 加载更多
|
||||||
|
if (loadMoreStatus.value === 'loading' || loadMoreStatus.value === 'noMore') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadMoreStatus.value = 'loading';
|
||||||
|
pagination.current++;
|
||||||
|
} else {
|
||||||
|
// 首次加载或刷新
|
||||||
|
listLoading.value = true;
|
||||||
|
pagination.current = 1;
|
||||||
|
recordList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getPaperReleaseRecordList({
|
||||||
|
schoolId: schoolId.value,
|
||||||
|
current: pagination.current,
|
||||||
|
size: pagination.size,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = res.data || {};
|
||||||
|
const rows = data.rows || [];
|
||||||
|
const total = data.total || 0;
|
||||||
|
|
||||||
|
if (isLoadMore) {
|
||||||
|
recordList.value = [...recordList.value, ...rows];
|
||||||
|
} else {
|
||||||
|
recordList.value = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination.total = total;
|
||||||
|
|
||||||
|
// 更新加载更多状态
|
||||||
|
if (recordList.value.length >= total) {
|
||||||
|
loadMoreStatus.value = 'noMore';
|
||||||
|
} else {
|
||||||
|
loadMoreStatus.value = 'more';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取作业记录失败', error);
|
||||||
|
pageError.value = true;
|
||||||
|
if (isLoadMore) {
|
||||||
|
pagination.current--;
|
||||||
|
loadMoreStatus.value = 'more';
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载失败,请重试',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false;
|
||||||
|
if (isLoadMore && loadMoreStatus.value === 'loading') {
|
||||||
|
loadMoreStatus.value = 'more';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载更多
|
||||||
|
const loadMore = () => {
|
||||||
|
if (loadMoreStatus.value === 'more') {
|
||||||
|
fetchRecordList(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击列表项
|
||||||
|
const handleItemClick = (item: PaperReleaseRecord) => {
|
||||||
|
// 可以在这里添加跳转到详情页的逻辑
|
||||||
|
uni.showToast({
|
||||||
|
title: item.name,
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
const refreshList = () => {
|
||||||
|
pageError.value = false;
|
||||||
|
fetchRecordList(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (schoolId.value) {
|
||||||
|
refreshList();
|
||||||
|
} else {
|
||||||
|
// 如果没有schoolId,尝试获取老师信息
|
||||||
|
teacherStore.getLoginUser().then(() => {
|
||||||
|
if (schoolId.value) {
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面显示时刷新
|
||||||
|
onShow(() => {
|
||||||
|
if (schoolId.value && recordList.value.length === 0) {
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.homework-page {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/main_bg.svg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 12rpx 20rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: calc(100vh - 52rpx);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-top: 8rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.list-box {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
|
||||||
|
.list-item-wrapper {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||||
|
position: relative;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 18rpx 20rpx;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-subject {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
background: #7effff;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
width: auto;
|
||||||
|
max-width: 120rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 11rpx;
|
||||||
|
color: $font-color;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: $font-color;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-paper {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 11rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
@include single-ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-time {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
gap: 8rpx;
|
||||||
|
|
||||||
|
.time-label {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #999;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-value {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
font-family: 'OPPO Sans';
|
||||||
|
font-size: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
width: 80rpx;
|
||||||
|
min-width: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
background: rgba(240, 240, 240, 0.8);
|
||||||
|
|
||||||
|
&.status-active {
|
||||||
|
background: rgba(240, 240, 240, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-ended {
|
||||||
|
background: rgba(224, 224, 224, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 11rpx;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
text-orientation: upright;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
height: 2rpx;
|
||||||
|
background: #7effff;
|
||||||
|
margin: 16rpx 0;
|
||||||
|
opacity: 0.4;
|
||||||
|
border-radius: 1rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-box {
|
||||||
|
padding: 20rpx 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
358
src/pages/teacher/index/index.vue
Normal file
358
src/pages/teacher/index/index.vue
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 左侧区域 -->
|
||||||
|
<view class="left-section">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="header">
|
||||||
|
<view class="title" data-content="学小乐AI">学小乐AI</view>
|
||||||
|
<view class="subtitle">教师端</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 用户信息区 -->
|
||||||
|
<view class="user-section" v-if="isLoggedIn">
|
||||||
|
<view class="user-info">
|
||||||
|
<image class="avatar" :src="teacherInfo.avatarUrl || defaultAvatar" mode="aspectFill" />
|
||||||
|
<view class="user-detail">
|
||||||
|
<text class="nickname">{{ teacherInfo.nickName || teacherInfo.account || '老师' }}</text>
|
||||||
|
<text class="school">{{ teacherInfo.schoolName || '' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="logout-btn" @click="handleLogout">
|
||||||
|
<text>退出登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="user-section login-section" v-else @click="goLogin">
|
||||||
|
<view class="login-hint">
|
||||||
|
<image class="login-icon" :src="defaultAvatar" mode="aspectFill" />
|
||||||
|
<text>点击登录账号</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部装饰 -->
|
||||||
|
<view class="footer">
|
||||||
|
<text>— 教学更高效 —</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧功能入口 -->
|
||||||
|
<view class="right-section">
|
||||||
|
<view class="content">
|
||||||
|
<view class="entry-card" @click="handleNavigate('/pages/teacher/homework/index')">
|
||||||
|
<view class="card-icon homework-icon">
|
||||||
|
<image :src="`${OSS_URL}/icon/homeWork_hisJob.svg`" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="card-info">
|
||||||
|
<text class="card-title">作业记录</text>
|
||||||
|
<text class="card-desc">查看发布的作业记录</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-arrow"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { onShow } from '@dcloudio/uni-app';
|
||||||
|
import { teacher } from '@/store/teacher';
|
||||||
|
import { user } from '@/store';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const OSS_URL = 'https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com';
|
||||||
|
const defaultAvatar = `${OSS_URL}/urm/logo.png`;
|
||||||
|
|
||||||
|
const teacherStore = teacher();
|
||||||
|
const userStore = user();
|
||||||
|
const { teacherInfo } = storeToRefs(teacherStore);
|
||||||
|
const { token } = storeToRefs(userStore);
|
||||||
|
|
||||||
|
const isLoggedIn = computed(() => !!token.value && !!teacherInfo.value.id);
|
||||||
|
|
||||||
|
// 导航处理
|
||||||
|
const handleNavigate = (url: string) => {
|
||||||
|
if (!token.value) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/login/index?redirect=${encodeURIComponent(url)}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 去登录
|
||||||
|
const goLogin = () => {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
teacherStore.clear();
|
||||||
|
userStore.logout();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取老师用户信息
|
||||||
|
const fetchTeacherInfo = async () => {
|
||||||
|
if (!token.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await teacherStore.getLoginUser();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取老师信息失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (token.value) {
|
||||||
|
fetchTeacherInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面显示时刷新用户信息
|
||||||
|
onShow(() => {
|
||||||
|
if (token.value) {
|
||||||
|
fetchTeacherInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-image: url('https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/urm/bg_home.svg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧区域
|
||||||
|
.left-section {
|
||||||
|
width: 280rpx;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
@include font-stroke($font-color, 3rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
margin-top: 8rpx;
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-section {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 8rpx;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-detail {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 10rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.login-section {
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-hint {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 16rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧区域
|
||||||
|
.right-section {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.homework-icon {
|
||||||
|
background: linear-gradient(135deg, #6dd5fa 0%, #2980b9 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: $font-special;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $font-color;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-desc {
|
||||||
|
font-family: $font-text;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-arrow {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-right: 3rpx solid #ccc;
|
||||||
|
border-bottom: 3rpx solid #ccc;
|
||||||
|
transform: translate(-70%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
24
src/plugins/dayjs/index.ts
Normal file
24
src/plugins/dayjs/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import calendar from 'dayjs/plugin/calendar';
|
||||||
|
import updateLocale from 'dayjs/plugin/updateLocale';
|
||||||
|
import 'dayjs/locale/zh-cn';
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
dayjs.extend(calendar);
|
||||||
|
dayjs.extend(updateLocale);
|
||||||
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
|
// 修改语言配置
|
||||||
|
dayjs.updateLocale('zh-cn', {
|
||||||
|
calendar: {
|
||||||
|
lastDay: '昨天 HH:mm',
|
||||||
|
sameDay: '今天 HH:mm',
|
||||||
|
nextDay: '明天 HH:mm',
|
||||||
|
lastWeek: 'ddd HH:mm',
|
||||||
|
nextWeek: '[下]ddd HH:mm',
|
||||||
|
sameElse: 'YYYY/MM/DD',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default dayjs;
|
||||||
6
src/shime-uni.d.ts
vendored
Normal file
6
src/shime-uni.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export {};
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
interface ComponentCustomOptions extends Hooks {}
|
||||||
|
}
|
||||||
233
src/static/logo.svg
Normal file
233
src/static/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 124 KiB |
43
src/store/global.ts
Normal file
43
src/store/global.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { setCache, getCache, removeCache, ArrToObj } from '@/utils';
|
||||||
|
|
||||||
|
export const global = defineStore('global', () => {
|
||||||
|
const dict = ref(getCache('dict') || {});
|
||||||
|
|
||||||
|
// 获取字典列表(简化实现)
|
||||||
|
const getDicts = async () => {
|
||||||
|
// 作业功能暂不需要字典
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取字典
|
||||||
|
const getDict = (key: string, full = false) => {
|
||||||
|
const d = dict.value.find ? dict.value.find((i: anyObj) => i.code === key) : null;
|
||||||
|
const child = d ? d.children : [];
|
||||||
|
if (full) {
|
||||||
|
return {
|
||||||
|
dict: child,
|
||||||
|
dictObj: ArrToObj(child, 'value', 'code'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取未读(简化实现)
|
||||||
|
const getUnread = async () => {
|
||||||
|
// 作业功能暂不需要未读数
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
dict.value = {};
|
||||||
|
removeCache('dict');
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
dict,
|
||||||
|
getDicts,
|
||||||
|
getDict,
|
||||||
|
getUnread,
|
||||||
|
clear,
|
||||||
|
};
|
||||||
|
});
|
||||||
3
src/store/index.ts
Normal file
3
src/store/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './user';
|
||||||
|
export * from './global';
|
||||||
|
export * from './socket';
|
||||||
30
src/store/socket.ts
Normal file
30
src/store/socket.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
// Socket 状态枚举
|
||||||
|
const SOCKET_STATUS = {
|
||||||
|
OFFLINE: 0,
|
||||||
|
CONNECTED: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const socket = defineStore('socket', () => {
|
||||||
|
const socketRef = ref<any>(null);
|
||||||
|
const status = ref(SOCKET_STATUS.OFFLINE);
|
||||||
|
|
||||||
|
// 初始化 socket(当前作业功能不需要,保留空实现)
|
||||||
|
function initSocket() {
|
||||||
|
console.log('Socket init - not implemented for homework');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭 socket
|
||||||
|
function closeSocket() {
|
||||||
|
socketRef.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
socket: socketRef,
|
||||||
|
status,
|
||||||
|
initSocket,
|
||||||
|
closeSocket,
|
||||||
|
};
|
||||||
|
});
|
||||||
53
src/store/teacher.ts
Normal file
53
src/store/teacher.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { getLoginUser as getLoginUserApi } from '@/api/teacher';
|
||||||
|
import type { TeacherUserInfo } from '@/api/teacher';
|
||||||
|
import { setCache, getCache, removeCache } from '@/utils';
|
||||||
|
|
||||||
|
export const teacher = defineStore('teacher', () => {
|
||||||
|
// State
|
||||||
|
const teacherInfo = ref<TeacherUserInfo>(getCache('teacherInfo') || {});
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const isTeacherLoggedIn = computed(() => !!teacherInfo.value.id);
|
||||||
|
const schoolId = computed(() => teacherInfo.value.schoolId || '');
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
// 获取老师用户信息
|
||||||
|
const getLoginUser = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getLoginUserApi();
|
||||||
|
const info = res.data || {};
|
||||||
|
teacherInfo.value = info;
|
||||||
|
setCache('teacherInfo', info);
|
||||||
|
return info;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取老师用户信息失败', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
const clear = async () => {
|
||||||
|
teacherInfo.value = {};
|
||||||
|
removeCache('teacherInfo');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新老师信息
|
||||||
|
const updateTeacherInfo = (info: Partial<TeacherUserInfo>) => {
|
||||||
|
teacherInfo.value = { ...teacherInfo.value, ...info };
|
||||||
|
setCache('teacherInfo', teacherInfo.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
teacherInfo,
|
||||||
|
// Getters
|
||||||
|
isTeacherLoggedIn,
|
||||||
|
schoolId,
|
||||||
|
// Actions
|
||||||
|
getLoginUser,
|
||||||
|
clear,
|
||||||
|
updateTeacherInfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
141
src/store/user.ts
Normal file
141
src/store/user.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import {
|
||||||
|
psdLogin as psdLoginApi,
|
||||||
|
getUserInfo as getUserInfoApi,
|
||||||
|
logout as logoutApi,
|
||||||
|
} from '@/api/login';
|
||||||
|
import type { accountLoginType } from '@/api/login';
|
||||||
|
import { setCache, getCache, removeCache } from '@/utils';
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id?: string;
|
||||||
|
account?: string;
|
||||||
|
nickName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
learnGradeId?: string;
|
||||||
|
schoolName?: string;
|
||||||
|
className?: string;
|
||||||
|
classId?: string;
|
||||||
|
diamond?: number;
|
||||||
|
experience?: number;
|
||||||
|
schoolClass?: any[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const user = defineStore('user', () => {
|
||||||
|
// State
|
||||||
|
const userInfo = ref<UserInfo>(getCache('userInfo') || {});
|
||||||
|
const token = ref(getCache('token') || '');
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const isLoggedIn = computed(() => !!token.value);
|
||||||
|
const gradeId = computed(() => userInfo.value.learnGradeId || '');
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
// 账号密码登录
|
||||||
|
const accordLogin = async (data: anyObj) => {
|
||||||
|
await beforeLogin();
|
||||||
|
const res = await psdLoginApi({
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
token.value = res.data;
|
||||||
|
setCache('token', res.data);
|
||||||
|
await afterLogin();
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeLogin = async () => {
|
||||||
|
await clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const afterLogin = async () => {
|
||||||
|
await getUserInfo();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const getUserInfo = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getUserInfoApi();
|
||||||
|
let info = res.data || {};
|
||||||
|
|
||||||
|
// 处理学校班级信息
|
||||||
|
if (typeof info.schoolClass === 'string') {
|
||||||
|
try {
|
||||||
|
info.schoolClass = JSON.parse(info.schoolClass);
|
||||||
|
} catch (e) {
|
||||||
|
info.schoolClass = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.schoolClass?.length) {
|
||||||
|
const school = info.schoolClass[0];
|
||||||
|
info.schoolName = school.schoolName;
|
||||||
|
const cls = school.classList?.[0];
|
||||||
|
if (cls) {
|
||||||
|
info.className = cls.className;
|
||||||
|
info.classId = cls.classId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo.value = info;
|
||||||
|
setCache('userInfo', info);
|
||||||
|
return info;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const logout = async () => {
|
||||||
|
try {
|
||||||
|
await logoutApi();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('退出登录失败', e);
|
||||||
|
}
|
||||||
|
await clear();
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除缓存
|
||||||
|
const clear = async () => {
|
||||||
|
token.value = '';
|
||||||
|
userInfo.value = {};
|
||||||
|
removeCache('token');
|
||||||
|
removeCache('userInfo');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查登录状态,未登录则跳转登录页
|
||||||
|
const checkLogin = (redirectUrl?: string): boolean => {
|
||||||
|
if (!token.value) {
|
||||||
|
const url = redirectUrl
|
||||||
|
? `/pages/login/index?redirect=${encodeURIComponent(redirectUrl)}`
|
||||||
|
: '/pages/login/index';
|
||||||
|
uni.navigateTo({ url });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
const updateUserInfo = (info: Partial<UserInfo>) => {
|
||||||
|
userInfo.value = { ...userInfo.value, ...info };
|
||||||
|
setCache('userInfo', userInfo.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
userInfo,
|
||||||
|
token,
|
||||||
|
// Getters
|
||||||
|
isLoggedIn,
|
||||||
|
gradeId,
|
||||||
|
// Actions
|
||||||
|
accordLogin,
|
||||||
|
getUserInfo,
|
||||||
|
logout,
|
||||||
|
clear,
|
||||||
|
checkLogin,
|
||||||
|
updateUserInfo,
|
||||||
|
};
|
||||||
|
});
|
||||||
25
src/styles/font.scss
Normal file
25
src/styles/font.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 本地字体包是全量
|
||||||
|
* 网络字体包仅包含如下文字:
|
||||||
|
* Hello!手机号登录个人信息修改密码
|
||||||
|
*/
|
||||||
|
/* 小程序平台使用在线字体 */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'AlibabaHealth';
|
||||||
|
src: url(https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/font/AlibabaHealth.ttf)
|
||||||
|
format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'AlimamaShuHeiTi';
|
||||||
|
src: url(https://xxl-1313840333.cos.ap-guangzhou.myqcloud.com/font/AlimamaShuHeiTi.otf)
|
||||||
|
format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
15
src/styles/global.scss
Normal file
15
src/styles/global.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
:root,
|
||||||
|
page {
|
||||||
|
// color: $font-color;
|
||||||
|
--navbar-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifdef APP-PLUS */
|
||||||
|
* {
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
.uni-navbar {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
175
src/styles/iconfont.scss
Normal file
175
src/styles/iconfont.scss
Normal file
File diff suppressed because one or more lines are too long
8
src/styles/theme.scss
Normal file
8
src/styles/theme.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// 主色调
|
||||||
|
// $primary-color: $theme-color;
|
||||||
|
$primary-color-end: #fffde9;
|
||||||
|
|
||||||
|
// $button-border-radius: $radius;
|
||||||
|
// $button-primary-color: $font-color;
|
||||||
|
|
||||||
|
$navbar-box-shadow: none;
|
||||||
208
src/styles/variate.scss
Normal file
208
src/styles/variate.scss
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// 主题
|
||||||
|
$theme-color: #ffe60f; // 品牌色/主操作按钮/按钮移入/文字链
|
||||||
|
|
||||||
|
// 辅助颜色
|
||||||
|
$success-color: #07cc89;
|
||||||
|
$success-color-1: #bdf6e3;
|
||||||
|
$success-color-2: #e7fef8;
|
||||||
|
$warning-color: #ffa200;
|
||||||
|
$warning-color-1: #ffd282;
|
||||||
|
$warning-color-2: #fff2dc;
|
||||||
|
$error-color: #f96950;
|
||||||
|
$error-color-1: #ffaa9c;
|
||||||
|
$error-color-2: #ffece9;
|
||||||
|
$aux-color: #5170fe;
|
||||||
|
$info-color: #999;
|
||||||
|
$label-color: #00c1f6;
|
||||||
|
$other-color: #b03afe;
|
||||||
|
|
||||||
|
$warning-fill: #fff3d4;
|
||||||
|
$aux-fill: #e7ebff;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 文字
|
||||||
|
$font-color: #1a1a1a; // 主要颜色/图标移入
|
||||||
|
$font-aux-color: #666; // 辅助色/提示文字
|
||||||
|
$font-minor-color: #999; // 次要颜色
|
||||||
|
$font-disabled-color: #c2c2c2; // 禁用文字
|
||||||
|
$font-reverse-color: #fff; // rgba(#fff, 0.85); // 反差色
|
||||||
|
|
||||||
|
$font-size-xm: 36rpx;
|
||||||
|
$font-size-m: 32rpx;
|
||||||
|
$font-size: 28rpx;
|
||||||
|
$font-size-s: 24rpx;
|
||||||
|
|
||||||
|
$font-weight: 500;
|
||||||
|
|
||||||
|
$font-text: AlibabaHealth;
|
||||||
|
// $font-text: AlimamaShuHeiTi;
|
||||||
|
$font-special: AlimamaShuHeiTi;
|
||||||
|
|
||||||
|
// 分割色
|
||||||
|
$split-color: #e6e6e6;
|
||||||
|
// 边框色
|
||||||
|
$border-color: $split-color;
|
||||||
|
$border-input-color: #e8eaec;
|
||||||
|
|
||||||
|
// 背景色
|
||||||
|
$page-fill: #fafafa;
|
||||||
|
$nav-fill: #fff;
|
||||||
|
$bg-fill: #fff;
|
||||||
|
$bg-aux-fill: #f5f5f5;
|
||||||
|
$bg-theme-aux-fill: #fffdee;
|
||||||
|
$bg-reverse-fill: #2d2d2d;
|
||||||
|
$input-fill: #fafafa;
|
||||||
|
$menuItem-hover-fill: #f7f7f7;
|
||||||
|
$hover-fill: #f9f9f9;
|
||||||
|
$error-fill: $error-color;
|
||||||
|
$hover-theme-fill: #eed715;
|
||||||
|
$disabled-theme-fill: #fff387;
|
||||||
|
$bg-minor-theme-fill: #fffde9;
|
||||||
|
$click-active-fill: #fbfbfb;
|
||||||
|
|
||||||
|
// 按钮
|
||||||
|
$btn-fill: #eeeeee; //#f5f5f5;
|
||||||
|
$btn-hover-fill: #e6e6e6;
|
||||||
|
$btn-disabled-fill: #dbdbdb; //#f5f5f5;
|
||||||
|
$btn-error-fill: #ffefec;
|
||||||
|
$btn-primary-fill: $theme-color;
|
||||||
|
$btn-primary-hover-fill: #f8e00e; //#eed715;
|
||||||
|
$btn-primary-disabled-fill: #fff387;
|
||||||
|
|
||||||
|
// 滚动条颜色
|
||||||
|
$scroll-color: #f5f5f5;
|
||||||
|
// 图标
|
||||||
|
$icon-color: #4d4d4d;
|
||||||
|
$icon-minor-color: #999;
|
||||||
|
$icon-aux-color: #ccc;
|
||||||
|
$icon-hover-fill: $hover-fill;
|
||||||
|
// tab
|
||||||
|
$tab-active-color: #333; // 分页激活色
|
||||||
|
|
||||||
|
/* 色彩 */
|
||||||
|
$bgc-common: #fff; // #141414
|
||||||
|
$minor-color: #333333;
|
||||||
|
|
||||||
|
$main-text-color: #535353; //主要字体颜色
|
||||||
|
$minor-text-color: #68738a; //次要字体颜色
|
||||||
|
|
||||||
|
// 字体
|
||||||
|
$font-small: PingFangSC-Regular; // 细体
|
||||||
|
$font-bold: PingFangSC-Medium; // 粗体
|
||||||
|
|
||||||
|
// 间隔
|
||||||
|
$space-m: 48rpx;
|
||||||
|
$space: 32rpx;
|
||||||
|
$space-s: 24rpx;
|
||||||
|
$space-xs: 16rpx;
|
||||||
|
$space-xxs: 8rpx;
|
||||||
|
// 圆角
|
||||||
|
$radius-xxm: 64rpx;
|
||||||
|
$radius-xm: 48rpx;
|
||||||
|
$radius-m: 32rpx;
|
||||||
|
$radius: 16rpx;
|
||||||
|
$radius-s: 8rpx;
|
||||||
|
|
||||||
|
// 圆边
|
||||||
|
$b-radius: 8px;
|
||||||
|
$b-radius-s: 4px;
|
||||||
|
|
||||||
|
// 盒子间隔
|
||||||
|
$box-margin: 16px;
|
||||||
|
|
||||||
|
// 按钮
|
||||||
|
$btn-margin: $box-margin;
|
||||||
|
|
||||||
|
// 控制台页面内边距
|
||||||
|
$page-padding: 24px 24px 0;
|
||||||
|
|
||||||
|
@mixin safe-bottom($h) {
|
||||||
|
bottom: $h;
|
||||||
|
bottom: calc($h + constant(safe-area-inset-bottom));
|
||||||
|
bottom: calc($h + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
@mixin safe-height($h) {
|
||||||
|
height: calc($h);
|
||||||
|
height: calc($h - constant(safe-area-inset-bottom));
|
||||||
|
height: calc($h - env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
@mixin safe-padding-bottom($h: 0rpx) {
|
||||||
|
padding-bottom: calc($h);
|
||||||
|
padding-bottom: calc($h + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc($h + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin safe-margin-bottom($h: 0rpx) {
|
||||||
|
margin-bottom: calc($h);
|
||||||
|
margin-bottom: calc($h + constant(safe-area-inset-bottom));
|
||||||
|
margin-bottom: calc($h + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单行省略
|
||||||
|
@mixin single-ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多行省略
|
||||||
|
@mixin multiple-ellipsis($line: 3) {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $line;
|
||||||
|
}
|
||||||
|
@mixin hidden-scrollbar {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 顶部通栏样式
|
||||||
|
@mixin nav-bar {
|
||||||
|
:deep(.uni-navbar) {
|
||||||
|
width: 100%;
|
||||||
|
background: $nav-fill;
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.uni-navbar__header-container {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.uni-nav-bar-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.uni-navbar--border {
|
||||||
|
border-bottom-color: $split-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mixin font-stroke($color: #424155, $stroke: 3px) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
&::after {
|
||||||
|
width: 100%;
|
||||||
|
content: attr(data-content);
|
||||||
|
-webkit-text-stroke: $stroke * 2 $color;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/types/global.d.ts
vendored
Normal file
18
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export {};
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
existLoading: boolean;
|
||||||
|
unique: number;
|
||||||
|
tokenRefreshing: boolean;
|
||||||
|
requests: <T = any>() => [];
|
||||||
|
eventSource: EventSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.json';
|
||||||
|
declare module 'lodash-es';
|
||||||
|
declare module 'uuid';
|
||||||
|
declare module 'dayjs';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
type anyObj = Record<string, any>;
|
||||||
|
}
|
||||||
0
src/types/index.ts
Normal file
0
src/types/index.ts
Normal file
76
src/uni.scss
Normal file
76
src/uni.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color: #333; // 基本色
|
||||||
|
$uni-text-color-inverse: #fff; // 反色
|
||||||
|
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable: #c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color: #fff;
|
||||||
|
$uni-bg-color-grey: #f8f8f8;
|
||||||
|
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
|
||||||
|
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color: #c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm: 12px;
|
||||||
|
$uni-font-size-base: 14px;
|
||||||
|
$uni-font-size-lg: 16;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm: 20px;
|
||||||
|
$uni-img-size-base: 26px;
|
||||||
|
$uni-img-size-lg: 40px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 2px;
|
||||||
|
$uni-border-radius-base: 3px;
|
||||||
|
$uni-border-radius-lg: 6px;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 5px;
|
||||||
|
$uni-spacing-row-base: 10px;
|
||||||
|
$uni-spacing-row-lg: 15px;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 4px;
|
||||||
|
$uni-spacing-col-base: 8px;
|
||||||
|
$uni-spacing-col-lg: 12px;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2c405a; // 文章标题颜色
|
||||||
|
$uni-font-size-title: 20px;
|
||||||
|
$uni-color-subtitle: #555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle: 18px;
|
||||||
|
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph: 15px;
|
||||||
33
src/uni_modules/uni-badge/changelog.md
Normal file
33
src/uni_modules/uni-badge/changelog.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
## 1.2.2(2023-01-28)
|
||||||
|
- 修复 运行/打包 控制台警告问题
|
||||||
|
## 1.2.1(2022-09-05)
|
||||||
|
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
|
||||||
|
## 1.2.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||||
|
## 1.1.7(2021-11-08)
|
||||||
|
- 优化 升级ui
|
||||||
|
- 修改 size 属性默认值调整为 small
|
||||||
|
- 修改 type 属性,默认值调整为 error,info 替换 default
|
||||||
|
## 1.1.6(2021-09-22)
|
||||||
|
- 修复 在字节小程序上样式不生效的 bug
|
||||||
|
## 1.1.5(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.1.4(2021-07-29)
|
||||||
|
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
|
||||||
|
## 1.1.3(2021-06-24)
|
||||||
|
- 优化 示例项目
|
||||||
|
## 1.1.1(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.1.0(2021-05-12)
|
||||||
|
- 新增 uni-badge 的 absolute 属性,支持定位
|
||||||
|
- 新增 uni-badge 的 offset 属性,支持定位偏移
|
||||||
|
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
|
||||||
|
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
|
||||||
|
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
|
||||||
|
## 1.0.7(2021-05-07)
|
||||||
|
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
|
||||||
|
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
|
||||||
|
- 新增 uni-badge 属性 custom-style, 支持自定义样式
|
||||||
|
## 1.0.6(2021-02-04)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
268
src/uni_modules/uni-badge/components/uni-badge/uni-badge.vue
Normal file
268
src/uni_modules/uni-badge/components/uni-badge/uni-badge.vue
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-badge--x">
|
||||||
|
<slot />
|
||||||
|
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
|
||||||
|
class="uni-badge" @click="onClick()">{{displayValue}}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Badge 数字角标
|
||||||
|
* @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
|
||||||
|
* @property {String} text 角标内容
|
||||||
|
* @property {String} size = [normal|small] 角标内容
|
||||||
|
* @property {String} type = [info|primary|success|warning|error] 颜色类型
|
||||||
|
* @value info 灰色
|
||||||
|
* @value primary 蓝色
|
||||||
|
* @value success 绿色
|
||||||
|
* @value warning 黄色
|
||||||
|
* @value error 红色
|
||||||
|
* @property {String} inverted = [true|false] 是否无需背景颜色
|
||||||
|
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
|
||||||
|
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
|
||||||
|
* @value rightTop 右上
|
||||||
|
* @value rightBottom 右下
|
||||||
|
* @value leftTop 左上
|
||||||
|
* @value leftBottom 左下
|
||||||
|
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
|
||||||
|
* @property {String} isDot = [true|false] 是否显示为一个小点
|
||||||
|
* @event {Function} click 点击 Badge 触发事件
|
||||||
|
* @example <uni-badge text="1"></uni-badge>
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UniBadge',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'error'
|
||||||
|
},
|
||||||
|
inverted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isDot: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
maxNum: {
|
||||||
|
type: Number,
|
||||||
|
default: 99
|
||||||
|
},
|
||||||
|
absolute: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return [0, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'small'
|
||||||
|
},
|
||||||
|
customStyle: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
width() {
|
||||||
|
return String(this.text).length * 8 + 12
|
||||||
|
},
|
||||||
|
classNames() {
|
||||||
|
const {
|
||||||
|
inverted,
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
absolute
|
||||||
|
} = this
|
||||||
|
return [
|
||||||
|
inverted ? 'uni-badge--' + type + '-inverted' : '',
|
||||||
|
'uni-badge--' + type,
|
||||||
|
'uni-badge--' + size,
|
||||||
|
absolute ? 'uni-badge--absolute' : ''
|
||||||
|
].join(' ')
|
||||||
|
},
|
||||||
|
positionStyle() {
|
||||||
|
if (!this.absolute) return {}
|
||||||
|
let w = this.width / 2,
|
||||||
|
h = 10
|
||||||
|
if (this.isDot) {
|
||||||
|
w = 5
|
||||||
|
h = 5
|
||||||
|
}
|
||||||
|
const x = `${- w + this.offset[0]}px`
|
||||||
|
const y = `${- h + this.offset[1]}px`
|
||||||
|
|
||||||
|
const whiteList = {
|
||||||
|
rightTop: {
|
||||||
|
right: x,
|
||||||
|
top: y
|
||||||
|
},
|
||||||
|
rightBottom: {
|
||||||
|
right: x,
|
||||||
|
bottom: y
|
||||||
|
},
|
||||||
|
leftBottom: {
|
||||||
|
left: x,
|
||||||
|
bottom: y
|
||||||
|
},
|
||||||
|
leftTop: {
|
||||||
|
left: x,
|
||||||
|
top: y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const match = whiteList[this.absolute]
|
||||||
|
return match ? match : whiteList['rightTop']
|
||||||
|
},
|
||||||
|
dotStyle() {
|
||||||
|
if (!this.isDot) return {}
|
||||||
|
return {
|
||||||
|
width: '10px',
|
||||||
|
minWidth: '0',
|
||||||
|
height: '10px',
|
||||||
|
padding: '0',
|
||||||
|
borderRadius: '10px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
displayValue() {
|
||||||
|
const {
|
||||||
|
isDot,
|
||||||
|
text,
|
||||||
|
maxNum
|
||||||
|
} = this
|
||||||
|
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.$emit('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" >
|
||||||
|
$uni-primary: #2979ff !default;
|
||||||
|
$uni-success: #4cd964 !default;
|
||||||
|
$uni-warning: #f0ad4e !default;
|
||||||
|
$uni-error: #dd524d !default;
|
||||||
|
$uni-info: #909399 !default;
|
||||||
|
|
||||||
|
|
||||||
|
$bage-size: 12px;
|
||||||
|
$bage-small: scale(0.8);
|
||||||
|
|
||||||
|
.uni-badge--x {
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
// align-self: flex-start;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: inline-block;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-badge--absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-badge--small {
|
||||||
|
transform: $bage-small;
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-badge {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-feature-settings: "tnum";
|
||||||
|
min-width: 20px;
|
||||||
|
/* #endif */
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 100px;
|
||||||
|
background-color: $uni-info;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
font-size: $bage-size;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
z-index: 999;
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
&--info {
|
||||||
|
color: #fff;
|
||||||
|
background-color: $uni-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background-color: $uni-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success {
|
||||||
|
background-color: $uni-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning {
|
||||||
|
background-color: $uni-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
background-color: $uni-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--inverted {
|
||||||
|
padding: 0 5px 0 0;
|
||||||
|
color: $uni-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info-inverted {
|
||||||
|
color: $uni-info;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary-inverted {
|
||||||
|
color: $uni-primary;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success-inverted {
|
||||||
|
color: $uni-success;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning-inverted {
|
||||||
|
color: $uni-warning;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error-inverted {
|
||||||
|
color: $uni-error;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
85
src/uni_modules/uni-badge/package.json
Normal file
85
src/uni_modules/uni-badge/package.json
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-badge",
|
||||||
|
"displayName": "uni-badge 数字角标",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
|
||||||
|
"keywords": [
|
||||||
|
"",
|
||||||
|
"badge",
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"数字角标",
|
||||||
|
"徽章"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": ["uni-scss"],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "y",
|
||||||
|
"联盟": "y"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/uni_modules/uni-badge/readme.md
Normal file
10
src/uni_modules/uni-badge/readme.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
## Badge 数字角标
|
||||||
|
> **组件名:uni-badge**
|
||||||
|
> 代码块: `uBadge`
|
||||||
|
|
||||||
|
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
|
|
||||||
|
|
||||||
28
src/uni_modules/uni-calendar/changelog.md
Normal file
28
src/uni_modules/uni-calendar/changelog.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
## 1.4.11(2024-01-10)
|
||||||
|
- 修复 回到今天时,月份显示不一致问题
|
||||||
|
## 1.4.10(2023-04-10)
|
||||||
|
- 修复 某些情况 monthSwitch 未触发的Bug
|
||||||
|
## 1.4.9(2023-02-02)
|
||||||
|
- 修复 某些情况切换月份错误的Bug
|
||||||
|
## 1.4.8(2023-01-30)
|
||||||
|
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
|
||||||
|
## 1.4.7(2022-09-16)
|
||||||
|
- 优化 支持使用 uni-scss 控制主题色
|
||||||
|
## 1.4.6(2022-09-08)
|
||||||
|
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
|
||||||
|
## 1.4.5(2022-02-25)
|
||||||
|
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||||
|
## 1.4.4(2022-02-25)
|
||||||
|
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||||
|
## 1.4.3(2021-09-22)
|
||||||
|
- 修复 startDate、 endDate 属性失效的Bug
|
||||||
|
## 1.4.2(2021-08-24)
|
||||||
|
- 新增 支持国际化
|
||||||
|
## 1.4.1(2021-08-05)
|
||||||
|
- 修复 弹出层被 tabbar 遮盖的Bug
|
||||||
|
## 1.4.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.3.16(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.3.15(2021-02-04)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
546
src/uni_modules/uni-calendar/components/uni-calendar/calendar.js
Normal file
546
src/uni_modules/uni-calendar/components/uni-calendar/calendar.js
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
/**
|
||||||
|
* @1900-2100区间内的公历、农历互转
|
||||||
|
* @charset UTF-8
|
||||||
|
* @github https://github.com/jjonline/calendar.js
|
||||||
|
* @Author Jea杨(JJonline@JJonline.Cn)
|
||||||
|
* @Time 2014-7-21
|
||||||
|
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
|
||||||
|
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
|
||||||
|
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
|
||||||
|
* @Version 1.0.3
|
||||||
|
* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
|
||||||
|
* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
|
||||||
|
*/
|
||||||
|
/* eslint-disable */
|
||||||
|
var calendar = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 农历1900-2100的润大小信息表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return Hex
|
||||||
|
*/
|
||||||
|
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
|
||||||
|
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
|
||||||
|
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
|
||||||
|
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
|
||||||
|
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
|
||||||
|
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
|
||||||
|
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
|
||||||
|
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
|
||||||
|
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
|
||||||
|
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
|
||||||
|
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
|
||||||
|
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
|
||||||
|
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
|
||||||
|
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
|
||||||
|
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
|
||||||
|
/** Add By JJonline@JJonline.Cn**/
|
||||||
|
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
|
||||||
|
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
|
||||||
|
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
|
||||||
|
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
|
||||||
|
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
|
||||||
|
0x0d520], // 2100
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公历每个月份的天数普通表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return Number
|
||||||
|
*/
|
||||||
|
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 天干地支之天干速查表
|
||||||
|
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 天干地支之地支速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 天干地支之地支速查表<=>生肖
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24节气速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1900-2100各年的24节气日期速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return 0x string For splice
|
||||||
|
*/
|
||||||
|
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
|
||||||
|
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
|
||||||
|
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||||
|
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
|
||||||
|
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
|
||||||
|
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
|
||||||
|
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||||
|
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||||
|
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
|
||||||
|
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||||
|
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
|
||||||
|
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
|
||||||
|
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
|
||||||
|
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
|
||||||
|
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
|
||||||
|
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
|
||||||
|
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
|
||||||
|
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
|
||||||
|
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字转中文速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期转农历称呼速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['初','十','廿','卅']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 月份转农历称呼速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回农历y年一整年的总天数
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number
|
||||||
|
* @eg:var count = calendar.lYearDays(1987) ;//count=387
|
||||||
|
*/
|
||||||
|
lYearDays: function (y) {
|
||||||
|
var i; var sum = 348
|
||||||
|
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
|
||||||
|
return (sum + this.leapDays(y))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (0-12)
|
||||||
|
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||||
|
*/
|
||||||
|
leapMonth: function (y) { // 闰字编码 \u95f0
|
||||||
|
return (this.lunarInfo[y - 1900] & 0xf)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (0、29、30)
|
||||||
|
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||||
|
*/
|
||||||
|
leapDays: function (y) {
|
||||||
|
if (this.leapMonth(y)) {
|
||||||
|
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
|
||||||
|
}
|
||||||
|
return (0)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (-1、29、30)
|
||||||
|
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||||
|
*/
|
||||||
|
monthDays: function (y, m) {
|
||||||
|
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
|
||||||
|
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回公历(!)y年m月的天数
|
||||||
|
* @param solar Year
|
||||||
|
* @return Number (-1、28、29、30、31)
|
||||||
|
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||||
|
*/
|
||||||
|
solarDays: function (y, m) {
|
||||||
|
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||||
|
var ms = m - 1
|
||||||
|
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
|
||||||
|
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
|
||||||
|
} else {
|
||||||
|
return (this.solarMonth[ms])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 农历年份转换为干支纪年
|
||||||
|
* @param lYear 农历年的年份数
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toGanZhiYear: function (lYear) {
|
||||||
|
var ganKey = (lYear - 3) % 10
|
||||||
|
var zhiKey = (lYear - 3) % 12
|
||||||
|
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
|
||||||
|
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
|
||||||
|
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公历月、日判断所属星座
|
||||||
|
* @param cMonth [description]
|
||||||
|
* @param cDay [description]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toAstro: function (cMonth, cDay) {
|
||||||
|
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
|
||||||
|
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
|
||||||
|
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入offset偏移量返回干支
|
||||||
|
* @param offset 相对甲子的偏移量
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toGanZhi: function (offset) {
|
||||||
|
return this.Gan[offset % 10] + this.Zhi[offset % 12]
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||||
|
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||||
|
* @return day Number
|
||||||
|
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
|
||||||
|
*/
|
||||||
|
getTerm: function (y, n) {
|
||||||
|
if (y < 1900 || y > 2100) { return -1 }
|
||||||
|
if (n < 1 || n > 24) { return -1 }
|
||||||
|
var _table = this.sTermInfo[y - 1900]
|
||||||
|
var _info = [
|
||||||
|
parseInt('0x' + _table.substr(0, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(5, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(10, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(15, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(20, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(25, 5)).toString()
|
||||||
|
]
|
||||||
|
var _calday = [
|
||||||
|
_info[0].substr(0, 1),
|
||||||
|
_info[0].substr(1, 2),
|
||||||
|
_info[0].substr(3, 1),
|
||||||
|
_info[0].substr(4, 2),
|
||||||
|
|
||||||
|
_info[1].substr(0, 1),
|
||||||
|
_info[1].substr(1, 2),
|
||||||
|
_info[1].substr(3, 1),
|
||||||
|
_info[1].substr(4, 2),
|
||||||
|
|
||||||
|
_info[2].substr(0, 1),
|
||||||
|
_info[2].substr(1, 2),
|
||||||
|
_info[2].substr(3, 1),
|
||||||
|
_info[2].substr(4, 2),
|
||||||
|
|
||||||
|
_info[3].substr(0, 1),
|
||||||
|
_info[3].substr(1, 2),
|
||||||
|
_info[3].substr(3, 1),
|
||||||
|
_info[3].substr(4, 2),
|
||||||
|
|
||||||
|
_info[4].substr(0, 1),
|
||||||
|
_info[4].substr(1, 2),
|
||||||
|
_info[4].substr(3, 1),
|
||||||
|
_info[4].substr(4, 2),
|
||||||
|
|
||||||
|
_info[5].substr(0, 1),
|
||||||
|
_info[5].substr(1, 2),
|
||||||
|
_info[5].substr(3, 1),
|
||||||
|
_info[5].substr(4, 2)
|
||||||
|
]
|
||||||
|
return parseInt(_calday[n - 1])
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入农历数字月份返回汉语通俗表示法
|
||||||
|
* @param lunar month
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||||
|
*/
|
||||||
|
toChinaMonth: function (m) { // 月 => \u6708
|
||||||
|
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||||
|
var s = this.nStr3[m - 1]
|
||||||
|
s += '\u6708'// 加上月字
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入农历日期数字返回汉字表示法
|
||||||
|
* @param lunar day
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||||
|
*/
|
||||||
|
toChinaDay: function (d) { // 日 => \u65e5
|
||||||
|
var s
|
||||||
|
switch (d) {
|
||||||
|
case 10:
|
||||||
|
s = '\u521d\u5341'; break
|
||||||
|
case 20:
|
||||||
|
s = '\u4e8c\u5341'; break
|
||||||
|
break
|
||||||
|
case 30:
|
||||||
|
s = '\u4e09\u5341'; break
|
||||||
|
break
|
||||||
|
default :
|
||||||
|
s = this.nStr2[Math.floor(d / 10)]
|
||||||
|
s += this.nStr1[d % 10]
|
||||||
|
}
|
||||||
|
return (s)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||||
|
* @param y year
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||||
|
*/
|
||||||
|
getAnimal: function (y) {
|
||||||
|
return this.Animals[(y - 4) % 12]
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
|
||||||
|
* @param y solar year
|
||||||
|
* @param m solar month
|
||||||
|
* @param d solar day
|
||||||
|
* @return JSON object
|
||||||
|
* @eg:console.log(calendar.solar2lunar(1987,11,01));
|
||||||
|
*/
|
||||||
|
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
|
||||||
|
// 年份限定、上限
|
||||||
|
if (y < 1900 || y > 2100) {
|
||||||
|
return -1// undefined转换为数字变为NaN
|
||||||
|
}
|
||||||
|
// 公历传参最下限
|
||||||
|
if (y == 1900 && m == 1 && d < 31) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
// 未传参 获得当天
|
||||||
|
if (!y) {
|
||||||
|
var objDate = new Date()
|
||||||
|
} else {
|
||||||
|
var objDate = new Date(y, parseInt(m) - 1, d)
|
||||||
|
}
|
||||||
|
var i; var leap = 0; var temp = 0
|
||||||
|
// 修正ymd参数
|
||||||
|
var y = objDate.getFullYear()
|
||||||
|
var m = objDate.getMonth() + 1
|
||||||
|
var d = objDate.getDate()
|
||||||
|
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
|
||||||
|
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||||
|
temp = this.lYearDays(i)
|
||||||
|
offset -= temp
|
||||||
|
}
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += temp; i--
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否今天
|
||||||
|
var isTodayObj = new Date()
|
||||||
|
var isToday = false
|
||||||
|
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||||
|
isToday = true
|
||||||
|
}
|
||||||
|
// 星期几
|
||||||
|
var nWeek = objDate.getDay()
|
||||||
|
var cWeek = this.nStr1[nWeek]
|
||||||
|
// 数字表示周几顺应天朝周一开始的惯例
|
||||||
|
if (nWeek == 0) {
|
||||||
|
nWeek = 7
|
||||||
|
}
|
||||||
|
// 农历年
|
||||||
|
var year = i
|
||||||
|
var leap = this.leapMonth(i) // 闰哪个月
|
||||||
|
var isLeap = false
|
||||||
|
|
||||||
|
// 效验闰月
|
||||||
|
for (i = 1; i < 13 && offset > 0; i++) {
|
||||||
|
// 闰月
|
||||||
|
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||||
|
--i
|
||||||
|
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
|
||||||
|
} else {
|
||||||
|
temp = this.monthDays(year, i)// 计算农历普通月天数
|
||||||
|
}
|
||||||
|
// 解除闰月
|
||||||
|
if (isLeap == true && i == (leap + 1)) { isLeap = false }
|
||||||
|
offset -= temp
|
||||||
|
}
|
||||||
|
// 闰月导致数组下标重叠取反
|
||||||
|
if (offset == 0 && leap > 0 && i == leap + 1) {
|
||||||
|
if (isLeap) {
|
||||||
|
isLeap = false
|
||||||
|
} else {
|
||||||
|
isLeap = true; --i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += temp; --i
|
||||||
|
}
|
||||||
|
// 农历月
|
||||||
|
var month = i
|
||||||
|
// 农历日
|
||||||
|
var day = offset + 1
|
||||||
|
// 天干地支处理
|
||||||
|
var sm = m - 1
|
||||||
|
var gzY = this.toGanZhiYear(year)
|
||||||
|
|
||||||
|
// 当月的两个节气
|
||||||
|
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
|
||||||
|
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
|
||||||
|
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
|
||||||
|
|
||||||
|
// 依据12节气修正干支月
|
||||||
|
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
|
||||||
|
if (d >= firstNode) {
|
||||||
|
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传入的日期的节气与否
|
||||||
|
var isTerm = false
|
||||||
|
var Term = null
|
||||||
|
if (firstNode == d) {
|
||||||
|
isTerm = true
|
||||||
|
Term = this.solarTerm[m * 2 - 2]
|
||||||
|
}
|
||||||
|
if (secondNode == d) {
|
||||||
|
isTerm = true
|
||||||
|
Term = this.solarTerm[m * 2 - 1]
|
||||||
|
}
|
||||||
|
// 日柱 当月一日与 1900/1/1 相差天数
|
||||||
|
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
|
||||||
|
var gzD = this.toGanZhi(dayCyclical + d - 1)
|
||||||
|
// 该日期所属的星座
|
||||||
|
var astro = this.toAstro(m, d)
|
||||||
|
|
||||||
|
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
|
||||||
|
* @param y lunar year
|
||||||
|
* @param m lunar month
|
||||||
|
* @param d lunar day
|
||||||
|
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
|
||||||
|
* @return JSON object
|
||||||
|
* @eg:console.log(calendar.lunar2solar(1987,9,10));
|
||||||
|
*/
|
||||||
|
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
|
||||||
|
var isLeapMonth = !!isLeapMonth
|
||||||
|
var leapOffset = 0
|
||||||
|
var leapMonth = this.leapMonth(y)
|
||||||
|
var leapDay = this.leapDays(y)
|
||||||
|
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||||
|
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
|
||||||
|
var day = this.monthDays(y, m)
|
||||||
|
var _day = day
|
||||||
|
// bugFix 2016-9-25
|
||||||
|
// if month is leap, _day use leapDays method
|
||||||
|
if (isLeapMonth) {
|
||||||
|
_day = this.leapDays(y, m)
|
||||||
|
}
|
||||||
|
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
|
||||||
|
|
||||||
|
// 计算农历的时间差
|
||||||
|
var offset = 0
|
||||||
|
for (var i = 1900; i < y; i++) {
|
||||||
|
offset += this.lYearDays(i)
|
||||||
|
}
|
||||||
|
var leap = 0; var isAdd = false
|
||||||
|
for (var i = 1; i < m; i++) {
|
||||||
|
leap = this.leapMonth(y)
|
||||||
|
if (!isAdd) { // 处理闰月
|
||||||
|
if (leap <= i && leap > 0) {
|
||||||
|
offset += this.leapDays(y); isAdd = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += this.monthDays(y, i)
|
||||||
|
}
|
||||||
|
// 转换闰月农历 需补充该年闰月的前一个月的时差
|
||||||
|
if (isLeapMonth) { offset += day }
|
||||||
|
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||||
|
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
|
||||||
|
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
|
||||||
|
var cY = calObj.getUTCFullYear()
|
||||||
|
var cM = calObj.getUTCMonth() + 1
|
||||||
|
var cD = calObj.getUTCDate()
|
||||||
|
|
||||||
|
return this.solar2lunar(cY, cM, cD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default calendar
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"uni-calender.ok": "ok",
|
||||||
|
"uni-calender.cancel": "cancel",
|
||||||
|
"uni-calender.today": "today",
|
||||||
|
"uni-calender.MON": "MON",
|
||||||
|
"uni-calender.TUE": "TUE",
|
||||||
|
"uni-calender.WED": "WED",
|
||||||
|
"uni-calender.THU": "THU",
|
||||||
|
"uni-calender.FRI": "FRI",
|
||||||
|
"uni-calender.SAT": "SAT",
|
||||||
|
"uni-calender.SUN": "SUN"
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import en from './en.json'
|
||||||
|
import zhHans from './zh-Hans.json'
|
||||||
|
import zhHant from './zh-Hant.json'
|
||||||
|
export default {
|
||||||
|
en,
|
||||||
|
'zh-Hans': zhHans,
|
||||||
|
'zh-Hant': zhHant
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"uni-calender.ok": "确定",
|
||||||
|
"uni-calender.cancel": "取消",
|
||||||
|
"uni-calender.today": "今日",
|
||||||
|
"uni-calender.SUN": "日",
|
||||||
|
"uni-calender.MON": "一",
|
||||||
|
"uni-calender.TUE": "二",
|
||||||
|
"uni-calender.WED": "三",
|
||||||
|
"uni-calender.THU": "四",
|
||||||
|
"uni-calender.FRI": "五",
|
||||||
|
"uni-calender.SAT": "六"
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"uni-calender.ok": "確定",
|
||||||
|
"uni-calender.cancel": "取消",
|
||||||
|
"uni-calender.today": "今日",
|
||||||
|
"uni-calender.SUN": "日",
|
||||||
|
"uni-calender.MON": "一",
|
||||||
|
"uni-calender.TUE": "二",
|
||||||
|
"uni-calender.WED": "三",
|
||||||
|
"uni-calender.THU": "四",
|
||||||
|
"uni-calender.FRI": "五",
|
||||||
|
"uni-calender.SAT": "六"
|
||||||
|
}
|
||||||
@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-calendar-item__weeks-box" :class="{
|
||||||
|
'uni-calendar-item--disable':weeks.disable,
|
||||||
|
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||||
|
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
|
||||||
|
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||||
|
'uni-calendar-item--multiple': weeks.multiple,
|
||||||
|
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||||
|
}"
|
||||||
|
@click="choiceDate(weeks)">
|
||||||
|
<view class="uni-calendar-item__weeks-box-item">
|
||||||
|
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||||
|
<text class="uni-calendar-item__weeks-box-text" :class="{
|
||||||
|
'uni-calendar-item--isDay-text': weeks.isDay,
|
||||||
|
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||||
|
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||||
|
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||||
|
'uni-calendar-item--multiple': weeks.multiple,
|
||||||
|
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||||
|
'uni-calendar-item--disable':weeks.disable,
|
||||||
|
}">{{weeks.date}}</text>
|
||||||
|
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||||
|
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||||
|
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||||
|
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||||
|
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||||
|
'uni-calendar-item--multiple': weeks.multiple,
|
||||||
|
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||||
|
}">{{todayText}}</text>
|
||||||
|
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||||
|
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||||
|
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||||
|
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||||
|
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||||
|
'uni-calendar-item--multiple': weeks.multiple,
|
||||||
|
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||||
|
'uni-calendar-item--disable':weeks.disable,
|
||||||
|
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
|
||||||
|
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||||
|
'uni-calendar-item--extra':weeks.extraInfo.info,
|
||||||
|
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||||
|
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||||
|
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||||
|
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||||
|
'uni-calendar-item--multiple': weeks.multiple,
|
||||||
|
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||||
|
'uni-calendar-item--disable':weeks.disable,
|
||||||
|
}">{{weeks.extraInfo.info}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||||
|
import i18nMessages from './i18n/index.js'
|
||||||
|
const { t } = initVueI18n(i18nMessages)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits:['change'],
|
||||||
|
props: {
|
||||||
|
weeks: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lunar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
todayText() {
|
||||||
|
return t("uni-calender.today")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
choiceDate(weeks) {
|
||||||
|
this.$emit('change', weeks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$uni-font-size-base:14px;
|
||||||
|
$uni-text-color:#333;
|
||||||
|
$uni-font-size-sm:12px;
|
||||||
|
$uni-color-error: #e43d33;
|
||||||
|
$uni-opacity-disabled: 0.3;
|
||||||
|
$uni-text-color-disable:#c0c0c0;
|
||||||
|
$uni-primary: #2979ff !default;
|
||||||
|
.uni-calendar-item__weeks-box {
|
||||||
|
flex: 1;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item__weeks-box-text {
|
||||||
|
font-size: $uni-font-size-base;
|
||||||
|
color: $uni-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item__weeks-lunar-text {
|
||||||
|
font-size: $uni-font-size-sm;
|
||||||
|
color: $uni-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item__weeks-box-item {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item__weeks-box-circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $uni-color-error;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--disable {
|
||||||
|
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
|
||||||
|
color: $uni-text-color-disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--isDay-text {
|
||||||
|
color: $uni-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--isDay {
|
||||||
|
background-color: $uni-primary;
|
||||||
|
opacity: 0.8;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--extra {
|
||||||
|
color: $uni-color-error;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--checked {
|
||||||
|
background-color: $uni-primary;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar-item--multiple {
|
||||||
|
background-color: $uni-primary;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.uni-calendar-item--before-checked {
|
||||||
|
background-color: #ff5a5f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.uni-calendar-item--after-checked {
|
||||||
|
background-color: #ff5a5f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,567 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-calendar">
|
||||||
|
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
|
||||||
|
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
|
||||||
|
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
|
||||||
|
<view class="uni-calendar__header-btn-box" @click="close">
|
||||||
|
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__header-btn-box" @click="confirm">
|
||||||
|
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__header">
|
||||||
|
<view class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||||
|
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||||
|
</view>
|
||||||
|
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||||
|
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
|
||||||
|
</picker>
|
||||||
|
<view class="uni-calendar__header-btn-box" @click.stop="next">
|
||||||
|
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||||
|
</view>
|
||||||
|
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__box">
|
||||||
|
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||||
|
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks">
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks-day">
|
||||||
|
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||||
|
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||||
|
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Calendar from './util.js';
|
||||||
|
import CalendarItem from './uni-calendar-item.vue'
|
||||||
|
|
||||||
|
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||||
|
import i18nMessages from './i18n/index.js'
|
||||||
|
const { t } = initVueI18n(i18nMessages)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar 日历
|
||||||
|
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||||
|
* @property {String} date 自定义当前时间,默认为今天
|
||||||
|
* @property {Boolean} lunar 显示农历
|
||||||
|
* @property {String} startDate 日期选择范围-开始日期
|
||||||
|
* @property {String} endDate 日期选择范围-结束日期
|
||||||
|
* @property {Boolean} range 范围选择
|
||||||
|
* @property {Boolean} insert = [true|false] 插入模式,默认为false
|
||||||
|
* @value true 弹窗模式
|
||||||
|
* @value false 插入模式
|
||||||
|
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||||
|
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||||
|
* @property {Boolean} showMonth 是否选择月份为背景
|
||||||
|
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||||
|
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||||
|
* @event {Function} monthSwitch 切换月份时触发
|
||||||
|
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CalendarItem
|
||||||
|
},
|
||||||
|
emits:['close','confirm','change','monthSwitch'],
|
||||||
|
props: {
|
||||||
|
date: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lunar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
range: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
insert: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showMonth: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
clearDate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
weeks: [],
|
||||||
|
calendar: {},
|
||||||
|
nowDate: '',
|
||||||
|
aniMaskShow: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
/**
|
||||||
|
* for i18n
|
||||||
|
*/
|
||||||
|
|
||||||
|
okText() {
|
||||||
|
return t("uni-calender.ok")
|
||||||
|
},
|
||||||
|
cancelText() {
|
||||||
|
return t("uni-calender.cancel")
|
||||||
|
},
|
||||||
|
todayText() {
|
||||||
|
return t("uni-calender.today")
|
||||||
|
},
|
||||||
|
monText() {
|
||||||
|
return t("uni-calender.MON")
|
||||||
|
},
|
||||||
|
TUEText() {
|
||||||
|
return t("uni-calender.TUE")
|
||||||
|
},
|
||||||
|
WEDText() {
|
||||||
|
return t("uni-calender.WED")
|
||||||
|
},
|
||||||
|
THUText() {
|
||||||
|
return t("uni-calender.THU")
|
||||||
|
},
|
||||||
|
FRIText() {
|
||||||
|
return t("uni-calender.FRI")
|
||||||
|
},
|
||||||
|
SATText() {
|
||||||
|
return t("uni-calender.SAT")
|
||||||
|
},
|
||||||
|
SUNText() {
|
||||||
|
return t("uni-calender.SUN")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
date(newVal) {
|
||||||
|
// this.cale.setDate(newVal)
|
||||||
|
this.init(newVal)
|
||||||
|
},
|
||||||
|
startDate(val){
|
||||||
|
this.cale.resetSatrtDate(val)
|
||||||
|
this.cale.setDate(this.nowDate.fullDate)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
},
|
||||||
|
endDate(val){
|
||||||
|
this.cale.resetEndDate(val)
|
||||||
|
this.cale.setDate(this.nowDate.fullDate)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
},
|
||||||
|
selected(newVal) {
|
||||||
|
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.cale = new Calendar({
|
||||||
|
selected: this.selected,
|
||||||
|
startDate: this.startDate,
|
||||||
|
endDate: this.endDate,
|
||||||
|
range: this.range,
|
||||||
|
})
|
||||||
|
this.init(this.date)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 取消穿透
|
||||||
|
clean() {},
|
||||||
|
bindDateChange(e) {
|
||||||
|
const value = e.detail.value + '-1'
|
||||||
|
this.setDate(value)
|
||||||
|
|
||||||
|
const { year,month } = this.cale.getDate(value)
|
||||||
|
this.$emit('monthSwitch', {
|
||||||
|
year,
|
||||||
|
month
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化日期显示
|
||||||
|
* @param {Object} date
|
||||||
|
*/
|
||||||
|
init(date) {
|
||||||
|
this.cale.setDate(date)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 打开日历弹窗
|
||||||
|
*/
|
||||||
|
open() {
|
||||||
|
// 弹窗模式并且清理数据
|
||||||
|
if (this.clearDate && !this.insert) {
|
||||||
|
this.cale.cleanMultipleStatus()
|
||||||
|
// this.cale.setDate(this.date)
|
||||||
|
this.init(this.date)
|
||||||
|
}
|
||||||
|
this.show = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.aniMaskShow = true
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 关闭日历弹窗
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.aniMaskShow = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.show = false
|
||||||
|
this.$emit('close')
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认按钮
|
||||||
|
*/
|
||||||
|
confirm() {
|
||||||
|
this.setEmit('confirm')
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 变化触发
|
||||||
|
*/
|
||||||
|
change() {
|
||||||
|
if (!this.insert) return
|
||||||
|
this.setEmit('change')
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选择月份触发
|
||||||
|
*/
|
||||||
|
monthSwitch() {
|
||||||
|
let {
|
||||||
|
year,
|
||||||
|
month
|
||||||
|
} = this.nowDate
|
||||||
|
this.$emit('monthSwitch', {
|
||||||
|
year,
|
||||||
|
month: Number(month)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 派发事件
|
||||||
|
* @param {Object} name
|
||||||
|
*/
|
||||||
|
setEmit(name) {
|
||||||
|
let {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
date,
|
||||||
|
fullDate,
|
||||||
|
lunar,
|
||||||
|
extraInfo
|
||||||
|
} = this.calendar
|
||||||
|
this.$emit(name, {
|
||||||
|
range: this.cale.multipleStatus,
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
date,
|
||||||
|
fulldate: fullDate,
|
||||||
|
lunar,
|
||||||
|
extraInfo: extraInfo || {}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选择天触发
|
||||||
|
* @param {Object} weeks
|
||||||
|
*/
|
||||||
|
choiceDate(weeks) {
|
||||||
|
if (weeks.disable) return
|
||||||
|
this.calendar = weeks
|
||||||
|
// 设置多选
|
||||||
|
this.cale.setMultiple(this.calendar.fullDate)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 回到今天
|
||||||
|
*/
|
||||||
|
backToday() {
|
||||||
|
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
|
||||||
|
const date = this.cale.getDate(new Date())
|
||||||
|
const todayYearMonth = `${date.year}-${date.month}`
|
||||||
|
|
||||||
|
this.init(date.fullDate)
|
||||||
|
|
||||||
|
if(nowYearMonth !== todayYearMonth) {
|
||||||
|
this.monthSwitch()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.change()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 上个月
|
||||||
|
*/
|
||||||
|
pre() {
|
||||||
|
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||||
|
this.setDate(preDate)
|
||||||
|
this.monthSwitch()
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下个月
|
||||||
|
*/
|
||||||
|
next() {
|
||||||
|
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||||
|
this.setDate(nextDate)
|
||||||
|
this.monthSwitch()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置日期
|
||||||
|
* @param {Object} date
|
||||||
|
*/
|
||||||
|
setDate(date) {
|
||||||
|
this.cale.setDate(date)
|
||||||
|
this.weeks = this.cale.weeks
|
||||||
|
this.nowDate = this.cale.getInfo(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
|
||||||
|
$uni-border-color: #EDEDED;
|
||||||
|
$uni-text-color: #333;
|
||||||
|
$uni-bg-color-hover:#f1f1f1;
|
||||||
|
$uni-font-size-base:14px;
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-color-subtitle: #555555;
|
||||||
|
$uni-text-color-grey:#999;
|
||||||
|
.uni-calendar {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__mask {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: $uni-bg-color-mask;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
opacity: 0;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
z-index: 99;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--mask-show {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--fixed {
|
||||||
|
position: fixed;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
bottom: 0;
|
||||||
|
/* #endif */
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transform: translateY(460px);
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
bottom: calc(var(--window-bottom));
|
||||||
|
z-index: 99;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--ani-show {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__content {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__header {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 50px;
|
||||||
|
border-bottom-color: $uni-border-color;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--fixed-top {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-top-color: $uni-border-color;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--fixed-width {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__backtoday {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 25rpx;
|
||||||
|
padding: 0 5px;
|
||||||
|
padding-left: 10px;
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-top-left-radius: 25px;
|
||||||
|
border-bottom-left-radius: 25px;
|
||||||
|
color: $uni-text-color;
|
||||||
|
background-color: $uni-bg-color-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__header-text {
|
||||||
|
text-align: center;
|
||||||
|
width: 100px;
|
||||||
|
font-size: $uni-font-size-base;
|
||||||
|
color: $uni-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__header-btn-box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__header-btn {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-left-color: $uni-text-color-placeholder;
|
||||||
|
border-left-style: solid;
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-top-color: $uni-color-subtitle;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--left {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar--right {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.uni-calendar__weeks {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__weeks-item {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__weeks-day {
|
||||||
|
flex: 1;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 45px;
|
||||||
|
border-bottom-color: #F5F5F5;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__weeks-day-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__box-bg {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-calendar__box-bg-text {
|
||||||
|
font-size: 200px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $uni-text-color-grey;
|
||||||
|
opacity: 0.1;
|
||||||
|
text-align: center;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
line-height: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
360
src/uni_modules/uni-calendar/components/uni-calendar/util.js
Normal file
360
src/uni_modules/uni-calendar/components/uni-calendar/util.js
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
import CALENDAR from './calendar.js'
|
||||||
|
|
||||||
|
class Calendar {
|
||||||
|
constructor({
|
||||||
|
date,
|
||||||
|
selected,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
range
|
||||||
|
} = {}) {
|
||||||
|
// 当前日期
|
||||||
|
this.date = this.getDate(new Date()) // 当前初入日期
|
||||||
|
// 打点信息
|
||||||
|
this.selected = selected || [];
|
||||||
|
// 范围开始
|
||||||
|
this.startDate = startDate
|
||||||
|
// 范围结束
|
||||||
|
this.endDate = endDate
|
||||||
|
this.range = range
|
||||||
|
// 多选状态
|
||||||
|
this.cleanMultipleStatus()
|
||||||
|
// 每周日期
|
||||||
|
this.weeks = {}
|
||||||
|
// this._getWeek(this.date.fullDate)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置日期
|
||||||
|
* @param {Object} date
|
||||||
|
*/
|
||||||
|
setDate(date) {
|
||||||
|
this.selectDate = this.getDate(date)
|
||||||
|
this._getWeek(this.selectDate.fullDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理多选状态
|
||||||
|
*/
|
||||||
|
cleanMultipleStatus() {
|
||||||
|
this.multipleStatus = {
|
||||||
|
before: '',
|
||||||
|
after: '',
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置开始日期
|
||||||
|
*/
|
||||||
|
resetSatrtDate(startDate) {
|
||||||
|
// 范围开始
|
||||||
|
this.startDate = startDate
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置结束日期
|
||||||
|
*/
|
||||||
|
resetEndDate(endDate) {
|
||||||
|
// 范围结束
|
||||||
|
this.endDate = endDate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任意时间
|
||||||
|
*/
|
||||||
|
getDate(date, AddDayCount = 0, str = 'day') {
|
||||||
|
if (!date) {
|
||||||
|
date = new Date()
|
||||||
|
}
|
||||||
|
if (typeof date !== 'object') {
|
||||||
|
date = date.replace(/-/g, '/')
|
||||||
|
}
|
||||||
|
const dd = new Date(date)
|
||||||
|
switch (str) {
|
||||||
|
case 'day':
|
||||||
|
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||||
|
break
|
||||||
|
case 'month':
|
||||||
|
if (dd.getDate() === 31 && AddDayCount>0) {
|
||||||
|
dd.setDate(dd.getDate() + AddDayCount)
|
||||||
|
} else {
|
||||||
|
const preMonth = dd.getMonth()
|
||||||
|
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
|
||||||
|
const nextMonth = dd.getMonth()
|
||||||
|
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||||
|
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
|
||||||
|
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
|
||||||
|
}
|
||||||
|
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||||
|
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
|
||||||
|
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'year':
|
||||||
|
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const y = dd.getFullYear()
|
||||||
|
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||||
|
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||||
|
return {
|
||||||
|
fullDate: y + '-' + m + '-' + d,
|
||||||
|
year: y,
|
||||||
|
month: m,
|
||||||
|
date: d,
|
||||||
|
day: dd.getDay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取上月剩余天数
|
||||||
|
*/
|
||||||
|
_getLastMonthDays(firstDay, full) {
|
||||||
|
let dateArr = []
|
||||||
|
for (let i = firstDay; i > 0; i--) {
|
||||||
|
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||||
|
dateArr.push({
|
||||||
|
date: beforeDate,
|
||||||
|
month: full.month - 1,
|
||||||
|
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
|
||||||
|
disable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return dateArr
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取本月天数
|
||||||
|
*/
|
||||||
|
_currentMonthDys(dateData, full) {
|
||||||
|
let dateArr = []
|
||||||
|
let fullDate = this.date.fullDate
|
||||||
|
for (let i = 1; i <= dateData; i++) {
|
||||||
|
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||||
|
full.month : full.month) + '-' + (i < 10 ?
|
||||||
|
'0' + i : i)
|
||||||
|
// 是否今天
|
||||||
|
let isDay = fullDate === nowDate
|
||||||
|
// 获取打点信息
|
||||||
|
let info = this.selected && this.selected.find((item) => {
|
||||||
|
if (this.dateEqual(nowDate, item.date)) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 日期禁用
|
||||||
|
let disableBefore = true
|
||||||
|
let disableAfter = true
|
||||||
|
if (this.startDate) {
|
||||||
|
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||||
|
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||||
|
disableBefore = this.dateCompare(this.startDate, nowDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.endDate) {
|
||||||
|
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||||
|
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||||
|
disableAfter = this.dateCompare(nowDate, this.endDate)
|
||||||
|
}
|
||||||
|
let multiples = this.multipleStatus.data
|
||||||
|
let checked = false
|
||||||
|
let multiplesStatus = -1
|
||||||
|
if (this.range) {
|
||||||
|
if (multiples) {
|
||||||
|
multiplesStatus = multiples.findIndex((item) => {
|
||||||
|
return this.dateEqual(item, nowDate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (multiplesStatus !== -1) {
|
||||||
|
checked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
fullDate: nowDate,
|
||||||
|
year: full.year,
|
||||||
|
date: i,
|
||||||
|
multiple: this.range ? checked : false,
|
||||||
|
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
|
||||||
|
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
|
||||||
|
month: full.month,
|
||||||
|
lunar: this.getlunar(full.year, full.month, i),
|
||||||
|
disable: !(disableBefore && disableAfter),
|
||||||
|
isDay
|
||||||
|
}
|
||||||
|
if (info) {
|
||||||
|
data.extraInfo = info
|
||||||
|
}
|
||||||
|
|
||||||
|
dateArr.push(data)
|
||||||
|
}
|
||||||
|
return dateArr
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取下月天数
|
||||||
|
*/
|
||||||
|
_getNextMonthDays(surplus, full) {
|
||||||
|
let dateArr = []
|
||||||
|
for (let i = 1; i < surplus + 1; i++) {
|
||||||
|
dateArr.push({
|
||||||
|
date: i,
|
||||||
|
month: Number(full.month) + 1,
|
||||||
|
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
|
||||||
|
disable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return dateArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前日期详情
|
||||||
|
* @param {Object} date
|
||||||
|
*/
|
||||||
|
getInfo(date) {
|
||||||
|
if (!date) {
|
||||||
|
date = new Date()
|
||||||
|
}
|
||||||
|
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||||
|
return dateInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较时间大小
|
||||||
|
*/
|
||||||
|
dateCompare(startDate, endDate) {
|
||||||
|
// 计算截止时间
|
||||||
|
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||||
|
// 计算详细项的截止时间
|
||||||
|
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||||
|
if (startDate <= endDate) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较时间是否相等
|
||||||
|
*/
|
||||||
|
dateEqual(before, after) {
|
||||||
|
// 计算截止时间
|
||||||
|
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||||
|
// 计算详细项的截止时间
|
||||||
|
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||||
|
if (before.getTime() - after.getTime() === 0) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日期范围内所有日期
|
||||||
|
* @param {Object} begin
|
||||||
|
* @param {Object} end
|
||||||
|
*/
|
||||||
|
geDateAll(begin, end) {
|
||||||
|
var arr = []
|
||||||
|
var ab = begin.split('-')
|
||||||
|
var ae = end.split('-')
|
||||||
|
var db = new Date()
|
||||||
|
db.setFullYear(ab[0], ab[1] - 1, ab[2])
|
||||||
|
var de = new Date()
|
||||||
|
de.setFullYear(ae[0], ae[1] - 1, ae[2])
|
||||||
|
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
|
||||||
|
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||||
|
for (var k = unixDb; k <= unixDe;) {
|
||||||
|
k = k + 24 * 60 * 60 * 1000
|
||||||
|
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算阴历日期显示
|
||||||
|
*/
|
||||||
|
getlunar(year, month, date) {
|
||||||
|
return CALENDAR.solar2lunar(year, month, date)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置打点
|
||||||
|
*/
|
||||||
|
setSelectInfo(data, value) {
|
||||||
|
this.selected = value
|
||||||
|
this._getWeek(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多选状态
|
||||||
|
*/
|
||||||
|
setMultiple(fullDate) {
|
||||||
|
let {
|
||||||
|
before,
|
||||||
|
after
|
||||||
|
} = this.multipleStatus
|
||||||
|
|
||||||
|
if (!this.range) return
|
||||||
|
if (before && after) {
|
||||||
|
this.multipleStatus.before = ''
|
||||||
|
this.multipleStatus.after = ''
|
||||||
|
this.multipleStatus.data = []
|
||||||
|
} else {
|
||||||
|
if (!before) {
|
||||||
|
this.multipleStatus.before = fullDate
|
||||||
|
} else {
|
||||||
|
this.multipleStatus.after = fullDate
|
||||||
|
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||||
|
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||||
|
} else {
|
||||||
|
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._getWeek(fullDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取每周数据
|
||||||
|
* @param {Object} dateData
|
||||||
|
*/
|
||||||
|
_getWeek(dateData) {
|
||||||
|
const {
|
||||||
|
year,
|
||||||
|
month
|
||||||
|
} = this.getDate(dateData)
|
||||||
|
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||||
|
let currentDay = new Date(year, month, 0).getDate()
|
||||||
|
let dates = {
|
||||||
|
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||||
|
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||||
|
nextMonthDays: [], // 下个月开始几天
|
||||||
|
weeks: []
|
||||||
|
}
|
||||||
|
let canlender = []
|
||||||
|
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||||
|
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||||
|
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||||
|
let weeks = {}
|
||||||
|
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||||
|
for (let i = 0; i < canlender.length; i++) {
|
||||||
|
if (i % 7 === 0) {
|
||||||
|
weeks[parseInt(i / 7)] = new Array(7)
|
||||||
|
}
|
||||||
|
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||||
|
}
|
||||||
|
this.canlender = canlender
|
||||||
|
this.weeks = weeks
|
||||||
|
}
|
||||||
|
|
||||||
|
//静态方法
|
||||||
|
// static init(date) {
|
||||||
|
// if (!this.instance) {
|
||||||
|
// this.instance = new Calendar(date);
|
||||||
|
// }
|
||||||
|
// return this.instance;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Calendar
|
||||||
85
src/uni_modules/uni-calendar/package.json
Normal file
85
src/uni_modules/uni-calendar/package.json
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-calendar",
|
||||||
|
"displayName": "uni-calendar 日历",
|
||||||
|
"version": "1.4.11",
|
||||||
|
"description": "日历组件",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"日历",
|
||||||
|
"",
|
||||||
|
"打卡",
|
||||||
|
"日历选择"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/uni_modules/uni-calendar/readme.md
Normal file
103
src/uni_modules/uni-calendar/readme.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Calendar 日历
|
||||||
|
> **组件名:uni-calendar**
|
||||||
|
> 代码块: `uCalendar`
|
||||||
|
|
||||||
|
|
||||||
|
日历组件
|
||||||
|
|
||||||
|
> **注意事项**
|
||||||
|
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
|
||||||
|
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
|
||||||
|
> - 仅支持自定义组件模式
|
||||||
|
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
|
||||||
|
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
|
||||||
|
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
|
||||||
|
|
||||||
|
|
||||||
|
### 安装方式
|
||||||
|
|
||||||
|
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||||
|
|
||||||
|
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
在 ``template`` 中使用组件
|
||||||
|
|
||||||
|
```html
|
||||||
|
<view>
|
||||||
|
<uni-calendar
|
||||||
|
:insert="true"
|
||||||
|
:lunar="true"
|
||||||
|
:start-date="'2019-3-2'"
|
||||||
|
:end-date="'2019-5-20'"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过方法打开日历
|
||||||
|
|
||||||
|
需要设置 `insert` 为 `false`
|
||||||
|
|
||||||
|
```html
|
||||||
|
<view>
|
||||||
|
<uni-calendar
|
||||||
|
ref="calendar"
|
||||||
|
:insert="false"
|
||||||
|
@confirm="confirm"
|
||||||
|
/>
|
||||||
|
<button @click="open">打开日历</button>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(){
|
||||||
|
this.$refs.calendar.open();
|
||||||
|
},
|
||||||
|
confirm(e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Calendar Props
|
||||||
|
|
||||||
|
| 属性名 | 类型 | 默认值| 说明 |
|
||||||
|
| - | - | - | - |
|
||||||
|
| date | String |- | 自定义当前时间,默认为今天 |
|
||||||
|
| lunar | Boolean | false | 显示农历 |
|
||||||
|
| startDate | String |- | 日期选择范围-开始日期 |
|
||||||
|
| endDate | String |- | 日期选择范围-结束日期 |
|
||||||
|
| range | Boolean | false | 范围选择 |
|
||||||
|
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|
||||||
|
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
|
||||||
|
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|
||||||
|
|showMonth | Boolean | true | 是否显示月份为背景 |
|
||||||
|
|
||||||
|
### Calendar Events
|
||||||
|
|
||||||
|
| 事件名 | 说明 |返回值|
|
||||||
|
| - | - | - |
|
||||||
|
| open | 弹出日历组件,`insert :false` 时生效|- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 组件示例
|
||||||
|
|
||||||
|
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
|
||||||
26
src/uni_modules/uni-card/changelog.md
Normal file
26
src/uni_modules/uni-card/changelog.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
## 1.3.1(2021-12-20)
|
||||||
|
- 修复 在vue页面下略缩图显示不正常的bug
|
||||||
|
## 1.3.0(2021-11-19)
|
||||||
|
- 重构插槽的用法 ,header 替换为 title
|
||||||
|
- 新增 actions 插槽
|
||||||
|
- 新增 cover 封面图属性和插槽
|
||||||
|
- 新增 padding 内容默认内边距离
|
||||||
|
- 新增 margin 卡片默认外边距离
|
||||||
|
- 新增 spacing 卡片默认内边距
|
||||||
|
- 新增 shadow 卡片阴影属性
|
||||||
|
- 取消 mode 属性,可使用组合插槽代替
|
||||||
|
- 取消 note 属性 ,使用actions插槽代替
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||||
|
## 1.2.1(2021-07-30)
|
||||||
|
- 优化 vue3下事件警告的问题
|
||||||
|
## 1.2.0(2021-07-13)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.1.8(2021-07-01)
|
||||||
|
- 优化 图文卡片无图片加载时,提供占位图标
|
||||||
|
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
|
||||||
|
- 修复 thumbnail 不存在仍然占位的 bug
|
||||||
|
## 1.1.7(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.1.6(2021-02-04)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
270
src/uni_modules/uni-card/components/uni-card/uni-card.vue
Normal file
270
src/uni_modules/uni-card/components/uni-card/uni-card.vue
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
|
||||||
|
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
|
||||||
|
<!-- 封面 -->
|
||||||
|
<slot name="cover">
|
||||||
|
<view v-if="cover" class="uni-card__cover">
|
||||||
|
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
<slot name="title">
|
||||||
|
<view v-if="title || extra" class="uni-card__header">
|
||||||
|
<!-- 卡片标题 -->
|
||||||
|
<view class="uni-card__header-box" @click="onClick('title')">
|
||||||
|
<view v-if="thumbnail" class="uni-card__header-avatar">
|
||||||
|
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="uni-card__header-content">
|
||||||
|
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
|
||||||
|
<text v-if="title&&subTitle"
|
||||||
|
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uni-card__header-extra" @click="onClick('extra')">
|
||||||
|
<text class="uni-card__header-extra-text">{{ extra }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
<!-- 卡片内容 -->
|
||||||
|
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="uni-card__actions" @click="onClick('actions')">
|
||||||
|
<slot name="actions"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Card 卡片
|
||||||
|
* @description 卡片视图组件
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
|
||||||
|
* @property {String} title 标题文字
|
||||||
|
* @property {String} subTitle 副标题
|
||||||
|
* @property {Number} padding 内容内边距
|
||||||
|
* @property {Number} margin 卡片外边距
|
||||||
|
* @property {Number} spacing 卡片内边距
|
||||||
|
* @property {String} extra 标题额外信息
|
||||||
|
* @property {String} cover 封面图(本地路径需要引入)
|
||||||
|
* @property {String} thumbnail 标题左侧缩略图
|
||||||
|
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
|
||||||
|
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
|
||||||
|
* @property {String} shadow 卡片阴影
|
||||||
|
* @property {Boolean} border 卡片边框
|
||||||
|
* @event {Function} click 点击 Card 触发事件
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'UniCard',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
subTitle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '10px'
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '15px'
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
type: String,
|
||||||
|
default: '0 10px'
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isFull: {
|
||||||
|
// 内容区域是否通栏
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isShadow: {
|
||||||
|
// 是否开启阴影
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
shadow: {
|
||||||
|
type: String,
|
||||||
|
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick(type) {
|
||||||
|
this.$emit('click', type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$uni-border-3: #EBEEF5 !default;
|
||||||
|
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||||
|
$uni-main-color: #3a3a3a !default;
|
||||||
|
$uni-base-color: #6a6a6a !default;
|
||||||
|
$uni-secondary-color: #909399 !default;
|
||||||
|
$uni-spacing-sm: 8px !default;
|
||||||
|
$uni-border-color:$uni-border-3;
|
||||||
|
$uni-shadow: $uni-shadow-base;
|
||||||
|
$uni-card-title: 15px;
|
||||||
|
$uni-cart-title-color:$uni-main-color;
|
||||||
|
$uni-card-subtitle: 12px;
|
||||||
|
$uni-cart-subtitle-color:$uni-secondary-color;
|
||||||
|
$uni-card-spacing: 10px;
|
||||||
|
$uni-card-content-color: $uni-base-color;
|
||||||
|
|
||||||
|
.uni-card {
|
||||||
|
margin: $uni-card-spacing;
|
||||||
|
padding: 0 $uni-spacing-sm;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||||
|
background-color: #fff;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.uni-card__cover {
|
||||||
|
position: relative;
|
||||||
|
margin-top: $uni-card-spacing;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 4px;
|
||||||
|
.uni-card__cover-image {
|
||||||
|
flex: 1;
|
||||||
|
// width: 100%;
|
||||||
|
/* #ifndef APP-PLUS */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__header {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px $uni-border-color solid;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: $uni-card-spacing;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.uni-card__header-box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__header-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: $uni-card-spacing;
|
||||||
|
.uni-card__header-avatar-image {
|
||||||
|
flex: 1;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__header-content {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
// height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.uni-card__header-content-title {
|
||||||
|
font-size: $uni-card-title;
|
||||||
|
color: $uni-cart-title-color;
|
||||||
|
// line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__header-content-subtitle {
|
||||||
|
font-size: $uni-card-subtitle;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: $uni-cart-subtitle-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__header-extra {
|
||||||
|
line-height: 12px;
|
||||||
|
|
||||||
|
.uni-card__header-extra-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $uni-cart-subtitle-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__content {
|
||||||
|
padding: $uni-card-spacing;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $uni-card-content-color;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card__actions {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card--border {
|
||||||
|
border: 1px solid $uni-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card--shadow {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-shadow: $uni-shadow;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-card--full {
|
||||||
|
margin: 0;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
.uni-card--full:after {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
.uni-ellipsis {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
lines: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
90
src/uni_modules/uni-card/package.json
Normal file
90
src/uni_modules/uni-card/package.json
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-card",
|
||||||
|
"displayName": "uni-card 卡片",
|
||||||
|
"version": "1.3.1",
|
||||||
|
"description": "Card 组件,提供常见的卡片样式。",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"card",
|
||||||
|
"",
|
||||||
|
"卡片"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-icons",
|
||||||
|
"uni-scss"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/uni_modules/uni-card/readme.md
Normal file
12
src/uni_modules/uni-card/readme.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Card 卡片
|
||||||
|
> **组件名:uni-card**
|
||||||
|
> 代码块: `uCard`
|
||||||
|
|
||||||
|
卡片视图组件。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
|
|
||||||
|
|
||||||
36
src/uni_modules/uni-collapse/changelog.md
Normal file
36
src/uni_modules/uni-collapse/changelog.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
## 1.4.3(2022-01-25)
|
||||||
|
- 修复 初始化的时候 ,open 属性失效的bug
|
||||||
|
## 1.4.2(2022-01-21)
|
||||||
|
- 修复 微信小程序resize后组件收起的bug
|
||||||
|
## 1.4.1(2021-11-22)
|
||||||
|
- 修复 vue3中个别scss变量无法找到的问题
|
||||||
|
## 1.4.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
|
||||||
|
## 1.3.3(2021-08-17)
|
||||||
|
- 优化 show-arrow 属性默认为true
|
||||||
|
## 1.3.2(2021-08-17)
|
||||||
|
- 新增 show-arrow 属性,控制是否显示右侧箭头
|
||||||
|
## 1.3.1(2021-07-30)
|
||||||
|
- 优化 vue3下小程序事件警告的问题
|
||||||
|
## 1.3.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.2.2(2021-07-21)
|
||||||
|
- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
|
||||||
|
## 1.2.1(2021-07-21)
|
||||||
|
- 优化 组件示例
|
||||||
|
## 1.2.0(2021-07-21)
|
||||||
|
- 新增 组件折叠动画
|
||||||
|
- 新增 value\v-model 属性 ,动态修改面板折叠状态
|
||||||
|
- 新增 title 插槽 ,可定义面板标题
|
||||||
|
- 新增 border 属性 ,显示隐藏面板内容分隔线
|
||||||
|
- 新增 title-border 属性 ,显示隐藏面板标题分隔线
|
||||||
|
- 修复 resize 方法失效的Bug
|
||||||
|
- 修复 change 事件返回参数不正确的Bug
|
||||||
|
- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
|
||||||
|
## 1.1.7(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.1.6(2021-02-05)
|
||||||
|
- 优化 组件引用关系,通过uni_modules引用组件
|
||||||
|
## 1.1.5(2021-02-05)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
@ -0,0 +1,402 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-collapse-item">
|
||||||
|
<!-- onClick(!isOpen) -->
|
||||||
|
<view @click="onClick(!isOpen)" class="uni-collapse-item__title"
|
||||||
|
:class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
|
||||||
|
<view class="uni-collapse-item__title-wrap">
|
||||||
|
<slot name="title">
|
||||||
|
<view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
|
||||||
|
<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
|
||||||
|
<text class="uni-collapse-item__title-text">{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view v-if="showArrow"
|
||||||
|
:class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
|
||||||
|
class="uni-collapse-item__title-arrow">
|
||||||
|
<uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
|
||||||
|
:style="{height: (isOpen?height:0) +'px'}">
|
||||||
|
<view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
|
||||||
|
:class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
const dom = weex.requireModule('dom')
|
||||||
|
// #endif
|
||||||
|
/**
|
||||||
|
* CollapseItem 折叠面板子组件
|
||||||
|
* @description 折叠面板子组件
|
||||||
|
* @property {String} title 标题文字
|
||||||
|
* @property {String} thumb 标题左侧缩略图
|
||||||
|
* @property {String} name 唯一标志符
|
||||||
|
* @property {Boolean} open = [true|false] 是否展开组件
|
||||||
|
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
|
||||||
|
* @property {Boolean} border = [true|false] 是否显示分隔线
|
||||||
|
* @property {Boolean} disabled = [true|false] 是否展开面板
|
||||||
|
* @property {Boolean} showAnimation = [true|false] 开启动画
|
||||||
|
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uniCollapseItem',
|
||||||
|
props: {
|
||||||
|
// 列表标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
// 是否显示动画,app 端默认不开启动画,卡顿严重
|
||||||
|
showAnimation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
// 是否显示动画
|
||||||
|
showAnimation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// 是否展开
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 缩略图
|
||||||
|
thumb: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 标题分隔线显示类型
|
||||||
|
titleBorder: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showArrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
// TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
|
||||||
|
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||||
|
return {
|
||||||
|
isOpen: false,
|
||||||
|
isheight: null,
|
||||||
|
height: 0,
|
||||||
|
elId,
|
||||||
|
nameSync: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
open(val) {
|
||||||
|
this.isOpen = val
|
||||||
|
this.onClick(val, 'init')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated(e) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.init(true)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.collapse = this.getCollapse()
|
||||||
|
this.oldHeight = 0
|
||||||
|
this.onClick(this.open, 'init')
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
// TODO vue2
|
||||||
|
destroyed() {
|
||||||
|
if (this.__isUnmounted) return
|
||||||
|
this.uninstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
// TODO vue3
|
||||||
|
unmounted() {
|
||||||
|
this.__isUnmounted = true
|
||||||
|
this.uninstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
mounted() {
|
||||||
|
if (!this.collapse) return
|
||||||
|
if (this.name !== '') {
|
||||||
|
this.nameSync = this.name
|
||||||
|
} else {
|
||||||
|
this.nameSync = this.collapse.childrens.length + ''
|
||||||
|
}
|
||||||
|
if (this.collapse.names.indexOf(this.nameSync) === -1) {
|
||||||
|
this.collapse.names.push(this.nameSync)
|
||||||
|
} else {
|
||||||
|
console.warn(`name 值 ${this.nameSync} 重复`);
|
||||||
|
}
|
||||||
|
if (this.collapse.childrens.indexOf(this) === -1) {
|
||||||
|
this.collapse.childrens.push(this)
|
||||||
|
}
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(type) {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.getCollapseHeight(type)
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.getNvueHwight(type)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
uninstall() {
|
||||||
|
if (this.collapse) {
|
||||||
|
this.collapse.childrens.forEach((item, index) => {
|
||||||
|
if (item === this) {
|
||||||
|
this.collapse.childrens.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.collapse.names.forEach((item, index) => {
|
||||||
|
if (item === this.nameSync) {
|
||||||
|
this.collapse.names.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick(isOpen, type) {
|
||||||
|
if (this.disabled) return
|
||||||
|
this.isOpen = isOpen
|
||||||
|
if (this.isOpen && this.collapse) {
|
||||||
|
this.collapse.setAccordion(this)
|
||||||
|
}
|
||||||
|
if (type !== 'init') {
|
||||||
|
this.collapse.onChange(isOpen, this)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCollapseHeight(type, index = 0) {
|
||||||
|
const views = uni.createSelectorQuery().in(this)
|
||||||
|
views
|
||||||
|
.select(`#${this.elId}`)
|
||||||
|
.fields({
|
||||||
|
size: true
|
||||||
|
}, data => {
|
||||||
|
// TODO 百度中可能获取不到节点信息 ,需要循环获取
|
||||||
|
if (index >= 10) return
|
||||||
|
if (!data) {
|
||||||
|
index++
|
||||||
|
this.getCollapseHeight(false, index)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.height = data.height + 1
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.height = data.height
|
||||||
|
// #endif
|
||||||
|
this.isheight = true
|
||||||
|
if (type) return
|
||||||
|
this.onClick(this.isOpen, 'init')
|
||||||
|
})
|
||||||
|
.exec()
|
||||||
|
},
|
||||||
|
getNvueHwight(type) {
|
||||||
|
const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
|
||||||
|
if (option && option.result && option.size) {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
this.height = option.size.height + 1
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.height = option.size.height
|
||||||
|
// #endif
|
||||||
|
this.isheight = true
|
||||||
|
if (type) return
|
||||||
|
this.onClick(this.open, 'init')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取父元素实例
|
||||||
|
*/
|
||||||
|
getCollapse(name = 'uniCollapse') {
|
||||||
|
let parent = this.$parent;
|
||||||
|
let parentName = parent.$options.name;
|
||||||
|
while (parentName !== name) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
if (!parent) return false;
|
||||||
|
parentName = parent.$options.name;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.uni-collapse-item {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
&__title {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
transition: border-bottom-color .3s;
|
||||||
|
|
||||||
|
// transition-property: border-bottom-color;
|
||||||
|
// transition-duration: 5s;
|
||||||
|
&-wrap {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&-box {
|
||||||
|
padding: 0 15px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
&.is-disabled {
|
||||||
|
.uni-collapse-item__title-text {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.uni-collapse-item-border {
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
white-space: nowrap;
|
||||||
|
color: inherit;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
lines: 1;
|
||||||
|
/* #endif */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrap {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
will-change: height;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
&.is--transition {
|
||||||
|
// transition: all 0.3s;
|
||||||
|
transition-property: height, border-bottom-width;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
will-change: height;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
// transition: height 0.3s;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
|
||||||
|
&.uni-collapse-item--border {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-color: red;
|
||||||
|
border-bottom-color: #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--animation {
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-collapse">
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Collapse 折叠面板
|
||||||
|
* @description 展示可以折叠 / 展开的内容区域
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=23
|
||||||
|
* @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)
|
||||||
|
* @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
|
||||||
|
* @event {Function} change 切换面板时触发,如果是手风琴模式,返回类型为string,否则为array
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uniCollapse',
|
||||||
|
emits:['change','activeItem','input','update:modelValue'],
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
accordion: {
|
||||||
|
// 是否开启手风琴效果
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// TODO 兼容 vue2 和 vue3
|
||||||
|
dataValue() {
|
||||||
|
let value = (typeof this.value === 'string' && this.value === '') ||
|
||||||
|
(Array.isArray(this.value) && this.value.length === 0)
|
||||||
|
let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
|
||||||
|
(Array.isArray(this.modelValue) && this.modelValue.length === 0)
|
||||||
|
if (value) {
|
||||||
|
return this.modelValue
|
||||||
|
}
|
||||||
|
if (modelValue) {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
dataValue(val) {
|
||||||
|
this.setOpen(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.childrens = []
|
||||||
|
this.names = []
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
this.setOpen(this.dataValue)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setOpen(val) {
|
||||||
|
let str = typeof val === 'string'
|
||||||
|
let arr = Array.isArray(val)
|
||||||
|
this.childrens.forEach((vm, index) => {
|
||||||
|
if (str) {
|
||||||
|
if (val === vm.nameSync) {
|
||||||
|
if (!this.accordion) {
|
||||||
|
console.warn('accordion 属性为 false ,v-model 类型应该为 array')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vm.isOpen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arr) {
|
||||||
|
val.forEach(v => {
|
||||||
|
if (v === vm.nameSync) {
|
||||||
|
if (this.accordion) {
|
||||||
|
console.warn('accordion 属性为 true ,v-model 类型应该为 string')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vm.isOpen = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.emit(val)
|
||||||
|
},
|
||||||
|
setAccordion(self) {
|
||||||
|
if (!this.accordion) return
|
||||||
|
this.childrens.forEach((vm, index) => {
|
||||||
|
if (self !== vm) {
|
||||||
|
vm.isOpen = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
this.childrens.forEach((vm, index) => {
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
vm.getCollapseHeight()
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
vm.getNvueHwight()
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onChange(isOpen, self) {
|
||||||
|
let activeItem = []
|
||||||
|
|
||||||
|
if (this.accordion) {
|
||||||
|
activeItem = isOpen ? self.nameSync : ''
|
||||||
|
} else {
|
||||||
|
this.childrens.forEach((vm, index) => {
|
||||||
|
if (vm.isOpen) {
|
||||||
|
activeItem.push(vm.nameSync)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.$emit('change', activeItem)
|
||||||
|
this.emit(activeItem)
|
||||||
|
},
|
||||||
|
emit(val){
|
||||||
|
this.$emit('input', val)
|
||||||
|
this.$emit('update:modelValue', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" >
|
||||||
|
.uni-collapse {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
flex: 1;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
89
src/uni_modules/uni-collapse/package.json
Normal file
89
src/uni_modules/uni-collapse/package.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-collapse",
|
||||||
|
"displayName": "uni-collapse 折叠面板",
|
||||||
|
"version": "1.4.3",
|
||||||
|
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"折叠",
|
||||||
|
"折叠面板",
|
||||||
|
"手风琴"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-scss",
|
||||||
|
"uni-icons"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/uni_modules/uni-collapse/readme.md
Normal file
12
src/uni_modules/uni-collapse/readme.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Collapse 折叠面板
|
||||||
|
> **组件名:uni-collapse**
|
||||||
|
> 代码块: `uCollapse`
|
||||||
|
> 关联组件:`uni-collapse-item`、`uni-icons`。
|
||||||
|
|
||||||
|
|
||||||
|
折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
15
src/uni_modules/uni-combox/changelog.md
Normal file
15
src/uni_modules/uni-combox/changelog.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
## 1.0.1(2021-11-23)
|
||||||
|
- 优化 label、label-width 属性
|
||||||
|
## 1.0.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||||
|
## 0.1.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 0.0.6(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 0.0.5(2021-04-21)
|
||||||
|
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
|
||||||
|
## 0.0.4(2021-02-05)
|
||||||
|
- 优化 组件引用关系,通过uni_modules引用组件
|
||||||
|
## 0.0.3(2021-02-04)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
275
src/uni_modules/uni-combox/components/uni-combox/uni-combox.vue
Normal file
275
src/uni_modules/uni-combox/components/uni-combox/uni-combox.vue
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
|
||||||
|
<view v-if="label" class="uni-combox__label" :style="labelStyle">
|
||||||
|
<text>{{label}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-combox__input-box">
|
||||||
|
<input class="uni-combox__input" type="text" :placeholder="placeholder"
|
||||||
|
placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
|
||||||
|
@blur="onBlur" />
|
||||||
|
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
|
||||||
|
</uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="uni-combox__selector" v-if="showSelector">
|
||||||
|
<view class="uni-popper__arrow"></view>
|
||||||
|
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
|
||||||
|
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
|
||||||
|
<text>{{emptyTips}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
|
||||||
|
@click="onSelectorClick(index)">
|
||||||
|
<text>{{item}}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Combox 组合输入框
|
||||||
|
* @description 组合输入框一般用于既可以输入也可以选择的场景
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
|
||||||
|
* @property {String} label 左侧文字
|
||||||
|
* @property {String} labelWidth 左侧内容宽度
|
||||||
|
* @property {String} placeholder 输入框占位符
|
||||||
|
* @property {Array} candidates 候选项列表
|
||||||
|
* @property {String} emptyTips 筛选结果为空时显示的文字
|
||||||
|
* @property {String} value 组合框的值
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uniCombox',
|
||||||
|
emits: ['input', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
labelWidth: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
candidates: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emptyTips: {
|
||||||
|
type: String,
|
||||||
|
default: '无匹配项'
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showSelector: false,
|
||||||
|
inputVal: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
labelStyle() {
|
||||||
|
if (this.labelWidth === 'auto') {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return `width: ${this.labelWidth}`
|
||||||
|
},
|
||||||
|
filterCandidates() {
|
||||||
|
return this.candidates.filter((item) => {
|
||||||
|
return item.toString().indexOf(this.inputVal) > -1
|
||||||
|
})
|
||||||
|
},
|
||||||
|
filterCandidatesLength() {
|
||||||
|
return this.filterCandidates.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// #ifndef VUE3
|
||||||
|
value: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.inputVal = newVal
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.inputVal = newVal
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleSelector() {
|
||||||
|
this.showSelector = !this.showSelector
|
||||||
|
},
|
||||||
|
onFocus() {
|
||||||
|
this.showSelector = true
|
||||||
|
},
|
||||||
|
onBlur() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showSelector = false
|
||||||
|
}, 153)
|
||||||
|
},
|
||||||
|
onSelectorClick(index) {
|
||||||
|
this.inputVal = this.filterCandidates[index]
|
||||||
|
this.showSelector = false
|
||||||
|
this.$emit('input', this.inputVal)
|
||||||
|
this.$emit('update:modelValue', this.inputVal)
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit('input', this.inputVal)
|
||||||
|
this.$emit('update:modelValue', this.inputVal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.uni-combox {
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #DCDFE6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
// height: 40px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
// border-bottom: solid 1px #DDDDDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__label {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 22px;
|
||||||
|
padding-right: 10px;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__input-box {
|
||||||
|
position: relative;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__input-plac {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__selector {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 12px);
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 1px solid #EBEEF5;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 2;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__selector-scroll {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
max-height: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__selector-empty,
|
||||||
|
.uni-combox__selector-item {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
line-height: 36px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
// border-bottom: solid 1px #DDDDDD;
|
||||||
|
padding: 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__selector-item:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__selector-empty:last-child,
|
||||||
|
.uni-combox__selector-item:last-child {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
border-bottom: none;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
// picker 弹出层通用的指示小三角
|
||||||
|
.uni-popper__arrow,
|
||||||
|
.uni-popper__arrow::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popper__arrow {
|
||||||
|
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||||
|
top: -6px;
|
||||||
|
left: 10%;
|
||||||
|
margin-right: 3px;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-color: #EBEEF5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-popper__arrow::after {
|
||||||
|
content: " ";
|
||||||
|
top: 1px;
|
||||||
|
margin-left: -6px;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-combox__no-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
90
src/uni_modules/uni-combox/package.json
Normal file
90
src/uni_modules/uni-combox/package.json
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-combox",
|
||||||
|
"displayName": "uni-combox 组合框",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "可以选择也可以输入的表单项 ",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"combox",
|
||||||
|
"组合框",
|
||||||
|
"select"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uni-scss",
|
||||||
|
"uni-icons"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "n"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/uni_modules/uni-combox/readme.md
Normal file
11
src/uni_modules/uni-combox/readme.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## Combox 组合框
|
||||||
|
> **组件名:uni-combox**
|
||||||
|
> 代码块: `uCombox`
|
||||||
|
|
||||||
|
|
||||||
|
组合框组件。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
24
src/uni_modules/uni-countdown/changelog.md
Normal file
24
src/uni_modules/uni-countdown/changelog.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
## 1.2.2(2022-01-19)
|
||||||
|
- 修复 在微信小程序中样式不生效的bug
|
||||||
|
## 1.2.1(2022-01-18)
|
||||||
|
- 新增 update 方法 ,在动态更新时间后,刷新组件
|
||||||
|
## 1.2.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
|
||||||
|
## 1.1.3(2021-10-18)
|
||||||
|
- 重构
|
||||||
|
- 新增 font-size 支持自定义字体大小
|
||||||
|
## 1.1.2(2021-08-24)
|
||||||
|
- 新增 支持国际化
|
||||||
|
## 1.1.1(2021-07-30)
|
||||||
|
- 优化 vue3下小程序事件警告的问题
|
||||||
|
## 1.1.0(2021-07-30)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 1.0.5(2021-06-18)
|
||||||
|
- 修复 uni-countdown 重复赋值跳两秒的 bug
|
||||||
|
## 1.0.4(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 1.0.3(2021-05-08)
|
||||||
|
- 修复 uni-countdown 不能控制倒计时的 bug
|
||||||
|
## 1.0.2(2021-02-04)
|
||||||
|
- 调整为uni_modules目录规范
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"uni-countdown.day": "day",
|
||||||
|
"uni-countdown.h": "h",
|
||||||
|
"uni-countdown.m": "m",
|
||||||
|
"uni-countdown.s": "s"
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import en from './en.json'
|
||||||
|
import zhHans from './zh-Hans.json'
|
||||||
|
import zhHant from './zh-Hant.json'
|
||||||
|
export default {
|
||||||
|
en,
|
||||||
|
'zh-Hans': zhHans,
|
||||||
|
'zh-Hant': zhHant
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"uni-countdown.day": "天",
|
||||||
|
"uni-countdown.h": "时",
|
||||||
|
"uni-countdown.m": "分",
|
||||||
|
"uni-countdown.s": "秒"
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"uni-countdown.day": "天",
|
||||||
|
"uni-countdown.h": "時",
|
||||||
|
"uni-countdown.m": "分",
|
||||||
|
"uni-countdown.s": "秒"
|
||||||
|
}
|
||||||
@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-countdown">
|
||||||
|
<text v-if="showDay" :style="[timeStyle]" class="uni-countdown__number">{{ d }}</text>
|
||||||
|
<text v-if="showDay" :style="[splitorStyle]" class="uni-countdown__splitor">{{dayText}}</text>
|
||||||
|
<text :style="[timeStyle]" class="uni-countdown__number">{{ h }}</text>
|
||||||
|
<text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : hourText }}</text>
|
||||||
|
<text :style="[timeStyle]" class="uni-countdown__number">{{ i }}</text>
|
||||||
|
<text :style="[splitorStyle]" class="uni-countdown__splitor">{{ showColon ? ':' : minuteText }}</text>
|
||||||
|
<text :style="[timeStyle]" class="uni-countdown__number">{{ s }}</text>
|
||||||
|
<text v-if="!showColon" :style="[splitorStyle]" class="uni-countdown__splitor">{{secondText}}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
initVueI18n
|
||||||
|
} from '@dcloudio/uni-i18n'
|
||||||
|
import messages from './i18n/index.js'
|
||||||
|
const {
|
||||||
|
t
|
||||||
|
} = initVueI18n(messages)
|
||||||
|
/**
|
||||||
|
* Countdown 倒计时
|
||||||
|
* @description 倒计时组件
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=25
|
||||||
|
* @property {String} backgroundColor 背景色
|
||||||
|
* @property {String} color 文字颜色
|
||||||
|
* @property {Number} day 天数
|
||||||
|
* @property {Number} hour 小时
|
||||||
|
* @property {Number} minute 分钟
|
||||||
|
* @property {Number} second 秒
|
||||||
|
* @property {Number} timestamp 时间戳
|
||||||
|
* @property {Boolean} showDay = [true|false] 是否显示天数
|
||||||
|
* @property {Boolean} show-colon = [true|false] 是否以冒号为分隔符
|
||||||
|
* @property {String} splitorColor 分割符号颜色
|
||||||
|
* @event {Function} timeup 倒计时时间到触发事件
|
||||||
|
* @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'UniCountdown',
|
||||||
|
emits: ['timeup'],
|
||||||
|
props: {
|
||||||
|
showDay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showColon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 14
|
||||||
|
},
|
||||||
|
splitorColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
hour: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
minute: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
second: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timer: null,
|
||||||
|
syncFlag: false,
|
||||||
|
d: '00',
|
||||||
|
h: '00',
|
||||||
|
i: '00',
|
||||||
|
s: '00',
|
||||||
|
leftTime: 0,
|
||||||
|
seconds: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dayText() {
|
||||||
|
return t("uni-countdown.day")
|
||||||
|
},
|
||||||
|
hourText(val) {
|
||||||
|
return t("uni-countdown.h")
|
||||||
|
},
|
||||||
|
minuteText(val) {
|
||||||
|
return t("uni-countdown.m")
|
||||||
|
},
|
||||||
|
secondText(val) {
|
||||||
|
return t("uni-countdown.s")
|
||||||
|
},
|
||||||
|
timeStyle() {
|
||||||
|
const {
|
||||||
|
color,
|
||||||
|
backgroundColor,
|
||||||
|
fontSize
|
||||||
|
} = this
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
backgroundColor,
|
||||||
|
fontSize: `${fontSize}px`,
|
||||||
|
width: `${fontSize * 22 / 14}px`, // 按字体大小为 14px 时的比例缩放
|
||||||
|
lineHeight: `${fontSize * 20 / 14}px`,
|
||||||
|
borderRadius: `${fontSize * 3 / 14}px`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitorStyle() {
|
||||||
|
const { splitorColor, fontSize, backgroundColor } = this
|
||||||
|
return {
|
||||||
|
color: splitorColor,
|
||||||
|
fontSize: `${fontSize * 12 / 14}px`,
|
||||||
|
margin: backgroundColor ? `${fontSize * 4 / 14}px` : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
day(val) {
|
||||||
|
this.changeFlag()
|
||||||
|
},
|
||||||
|
hour(val) {
|
||||||
|
this.changeFlag()
|
||||||
|
},
|
||||||
|
minute(val) {
|
||||||
|
this.changeFlag()
|
||||||
|
},
|
||||||
|
second(val) {
|
||||||
|
this.changeFlag()
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal, oldVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.startData();
|
||||||
|
} else {
|
||||||
|
if (!oldVal) return
|
||||||
|
clearInterval(this.timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function(e) {
|
||||||
|
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||||
|
this.countDown()
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
destroyed() {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
unmounted() {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
toSeconds(timestamp, day, hours, minutes, seconds) {
|
||||||
|
if (timestamp) {
|
||||||
|
return timestamp - parseInt(new Date().getTime() / 1000, 10)
|
||||||
|
}
|
||||||
|
return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
|
||||||
|
},
|
||||||
|
timeUp() {
|
||||||
|
clearInterval(this.timer)
|
||||||
|
this.$emit('timeup')
|
||||||
|
},
|
||||||
|
countDown() {
|
||||||
|
let seconds = this.seconds
|
||||||
|
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||||||
|
if (seconds > 0) {
|
||||||
|
day = Math.floor(seconds / (60 * 60 * 24))
|
||||||
|
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||||||
|
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
|
||||||
|
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
|
||||||
|
} else {
|
||||||
|
this.timeUp()
|
||||||
|
}
|
||||||
|
if (day < 10) {
|
||||||
|
day = '0' + day
|
||||||
|
}
|
||||||
|
if (hour < 10) {
|
||||||
|
hour = '0' + hour
|
||||||
|
}
|
||||||
|
if (minute < 10) {
|
||||||
|
minute = '0' + minute
|
||||||
|
}
|
||||||
|
if (second < 10) {
|
||||||
|
second = '0' + second
|
||||||
|
}
|
||||||
|
this.d = day
|
||||||
|
this.h = hour
|
||||||
|
this.i = minute
|
||||||
|
this.s = second
|
||||||
|
},
|
||||||
|
startData() {
|
||||||
|
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||||
|
if (this.seconds <= 0) {
|
||||||
|
this.seconds = this.toSeconds(0, 0, 0, 0, 0)
|
||||||
|
this.countDown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clearInterval(this.timer)
|
||||||
|
this.countDown()
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.seconds--
|
||||||
|
if (this.seconds < 0) {
|
||||||
|
this.timeUp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.countDown()
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
update(){
|
||||||
|
this.startData();
|
||||||
|
},
|
||||||
|
changeFlag() {
|
||||||
|
if (!this.syncFlag) {
|
||||||
|
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||||
|
this.startData();
|
||||||
|
this.syncFlag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$font-size: 14px;
|
||||||
|
|
||||||
|
.uni-countdown {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__splitor {
|
||||||
|
margin: 0 2px;
|
||||||
|
font-size: $font-size;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__number {
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
86
src/uni_modules/uni-countdown/package.json
Normal file
86
src/uni_modules/uni-countdown/package.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-countdown",
|
||||||
|
"displayName": "uni-countdown 倒计时",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"description": "CountDown 倒计时组件",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"uniui",
|
||||||
|
"countdown",
|
||||||
|
"倒计时"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": ""
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": ["uni-scss"],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/uni_modules/uni-countdown/readme.md
Normal file
10
src/uni_modules/uni-countdown/readme.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## CountDown 倒计时
|
||||||
|
> **组件名:uni-countdown**
|
||||||
|
> 代码块: `uCountDown`
|
||||||
|
|
||||||
|
倒计时组件。
|
||||||
|
|
||||||
|
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown)
|
||||||
|
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||||
45
src/uni_modules/uni-data-checkbox/changelog.md
Normal file
45
src/uni_modules/uni-data-checkbox/changelog.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
## 1.0.3(2022-09-16)
|
||||||
|
- 可以使用 uni-scss 控制主题色
|
||||||
|
## 1.0.2(2022-06-30)
|
||||||
|
- 优化 在 uni-forms 中的依赖注入方式
|
||||||
|
## 1.0.1(2022-02-07)
|
||||||
|
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
|
||||||
|
## 1.0.0(2021-11-19)
|
||||||
|
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||||
|
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
|
||||||
|
## 0.2.5(2021-08-23)
|
||||||
|
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
|
||||||
|
## 0.2.4(2021-08-17)
|
||||||
|
- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
|
||||||
|
## 0.2.3(2021-08-11)
|
||||||
|
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
|
||||||
|
## 0.2.2(2021-07-30)
|
||||||
|
- 优化 在uni-forms组件,与label不对齐的问题
|
||||||
|
## 0.2.1(2021-07-27)
|
||||||
|
- 修复 单选默认值为0不能选中的Bug
|
||||||
|
## 0.2.0(2021-07-13)
|
||||||
|
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||||
|
## 0.1.11(2021-07-06)
|
||||||
|
- 优化 删除无用日志
|
||||||
|
## 0.1.10(2021-07-05)
|
||||||
|
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
|
||||||
|
## 0.1.9(2021-07-05)
|
||||||
|
- 修复 nvue 黑框样式问题
|
||||||
|
## 0.1.8(2021-06-28)
|
||||||
|
- 修复 selectedTextColor 属性不生效的Bug
|
||||||
|
## 0.1.7(2021-06-02)
|
||||||
|
- 新增 map 属性,可以方便映射text/value属性
|
||||||
|
## 0.1.6(2021-05-26)
|
||||||
|
- 修复 不关联服务空间的情况下组件报错的Bug
|
||||||
|
## 0.1.5(2021-05-12)
|
||||||
|
- 新增 组件示例地址
|
||||||
|
## 0.1.4(2021-04-09)
|
||||||
|
- 修复 nvue 下无法选中的问题
|
||||||
|
## 0.1.3(2021-03-22)
|
||||||
|
- 新增 disabled属性
|
||||||
|
## 0.1.2(2021-02-24)
|
||||||
|
- 优化 默认颜色显示
|
||||||
|
## 0.1.1(2021-02-24)
|
||||||
|
- 新增 支持nvue
|
||||||
|
## 0.1.0(2021-02-18)
|
||||||
|
- “暂无数据”显示居中
|
||||||
@ -0,0 +1,821 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
|
||||||
|
<template v-if="!isLocal">
|
||||||
|
<view class="uni-data-loading">
|
||||||
|
<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
|
||||||
|
<text v-else>{{mixinDatacomErrorMessage}}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}" @change="chagne">
|
||||||
|
<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||||
|
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||||
|
<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''" :checked="item.selected" />
|
||||||
|
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner" :style="item.styleIcon">
|
||||||
|
<view class="checkbox__inner-icon"></view>
|
||||||
|
</view>
|
||||||
|
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||||
|
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||||
|
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :style="item.styleBackgroud"></view>
|
||||||
|
</view>
|
||||||
|
</label>
|
||||||
|
</checkbox-group>
|
||||||
|
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
|
||||||
|
<!-- -->
|
||||||
|
<label class="checklist-box" :class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||||
|
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||||
|
<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''" :checked="item.selected" />
|
||||||
|
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
|
||||||
|
:style="item.styleBackgroud">
|
||||||
|
<view class="radio__inner-icon" :style="item.styleIcon"></view>
|
||||||
|
</view>
|
||||||
|
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||||
|
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||||
|
<view v-if="mode === 'list' && icon === 'right'" :style="item.styleRightIcon" class="checkobx__list"></view>
|
||||||
|
</view>
|
||||||
|
</label>
|
||||||
|
</radio-group>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* DataChecklist 数据选择器
|
||||||
|
* @description 通过数据渲染 checkbox 和 radio
|
||||||
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
|
||||||
|
* @property {String} mode = [default| list | button | tag] 显示模式
|
||||||
|
* @value default 默认横排模式
|
||||||
|
* @value list 列表模式
|
||||||
|
* @value button 按钮模式
|
||||||
|
* @value tag 标签模式
|
||||||
|
* @property {Boolean} multiple = [true|false] 是否多选
|
||||||
|
* @property {Array|String|Number} value 默认值
|
||||||
|
* @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
|
||||||
|
* @property {Number|String} min 最小选择个数 ,multiple为true时生效
|
||||||
|
* @property {Number|String} max 最大选择个数 ,multiple为true时生效
|
||||||
|
* @property {Boolean} wrap 是否换行显示
|
||||||
|
* @property {String} icon = [left|right] list 列表模式下icon显示位置
|
||||||
|
* @property {Boolean} selectedColor 选中颜色
|
||||||
|
* @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
|
||||||
|
* @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
|
||||||
|
* @property {Object} map 字段映射, 默认 map={text:'text',value:'value'}
|
||||||
|
* @value left 左侧显示
|
||||||
|
* @value right 右侧显示
|
||||||
|
* @event {Function} change 选中发生变化触发
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'uniDataChecklist',
|
||||||
|
mixins: [uniCloud.mixinDatacom || {}],
|
||||||
|
emits:['input','update:modelValue','change'],
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'default'
|
||||||
|
},
|
||||||
|
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Array, String, Number],
|
||||||
|
default () {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// TODO vue3
|
||||||
|
modelValue: {
|
||||||
|
type: [Array, String, Number],
|
||||||
|
default() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
localdata: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
wrap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'left'
|
||||||
|
},
|
||||||
|
selectedColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
selectedTextColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
emptyText:{
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据'
|
||||||
|
},
|
||||||
|
disabled:{
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
map:{
|
||||||
|
type: Object,
|
||||||
|
default(){
|
||||||
|
return {
|
||||||
|
text:'text',
|
||||||
|
value:'value'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
localdata: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.range = newVal
|
||||||
|
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
mixinDatacomResData(newVal) {
|
||||||
|
this.range = newVal
|
||||||
|
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||||
|
},
|
||||||
|
value(newVal) {
|
||||||
|
this.dataList = this.getDataList(newVal)
|
||||||
|
// fix by mehaotian is_reset 在 uni-forms 中定义
|
||||||
|
// if(!this.is_reset){
|
||||||
|
// this.is_reset = false
|
||||||
|
// this.formItem && this.formItem.setValue(newVal)
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
modelValue(newVal) {
|
||||||
|
this.dataList = this.getDataList(newVal);
|
||||||
|
// if(!this.is_reset){
|
||||||
|
// this.is_reset = false
|
||||||
|
// this.formItem && this.formItem.setValue(newVal)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dataList: [],
|
||||||
|
range: [],
|
||||||
|
contentText: {
|
||||||
|
contentdown: '查看更多',
|
||||||
|
contentrefresh: '加载中',
|
||||||
|
contentnomore: '没有更多'
|
||||||
|
},
|
||||||
|
isLocal:true,
|
||||||
|
styles: {
|
||||||
|
selectedColor: '#2979ff',
|
||||||
|
selectedTextColor: '#666',
|
||||||
|
},
|
||||||
|
isTop:0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
dataValue(){
|
||||||
|
if(this.value === '')return this.modelValue
|
||||||
|
if(this.modelValue === '') return this.value
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// this.form = this.getForm('uniForms')
|
||||||
|
// this.formItem = this.getForm('uniFormsItem')
|
||||||
|
// this.formItem && this.formItem.setValue(this.value)
|
||||||
|
|
||||||
|
// if (this.formItem) {
|
||||||
|
// this.isTop = 6
|
||||||
|
// if (this.formItem.name) {
|
||||||
|
// // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
|
||||||
|
// if(!this.is_reset){
|
||||||
|
// this.is_reset = false
|
||||||
|
// this.formItem.setValue(this.dataValue)
|
||||||
|
// }
|
||||||
|
// this.rename = this.formItem.name
|
||||||
|
// this.form.inputChildrens.push(this)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (this.localdata && this.localdata.length !== 0) {
|
||||||
|
this.isLocal = true
|
||||||
|
this.range = this.localdata
|
||||||
|
this.dataList = this.getDataList(this.getSelectedValue(this.range))
|
||||||
|
} else {
|
||||||
|
if (this.collection) {
|
||||||
|
this.isLocal = false
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadData() {
|
||||||
|
this.mixinDatacomGet().then(res=>{
|
||||||
|
this.mixinDatacomResData = res.result.data
|
||||||
|
if(this.mixinDatacomResData.length === 0){
|
||||||
|
this.isLocal = false
|
||||||
|
this.mixinDatacomErrorMessage = this.emptyText
|
||||||
|
}else{
|
||||||
|
this.isLocal = true
|
||||||
|
}
|
||||||
|
}).catch(err=>{
|
||||||
|
this.mixinDatacomErrorMessage = err.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取父元素实例
|
||||||
|
*/
|
||||||
|
getForm(name = 'uniForms') {
|
||||||
|
let parent = this.$parent;
|
||||||
|
let parentName = parent.$options.name;
|
||||||
|
while (parentName !== name) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
if (!parent) return false
|
||||||
|
parentName = parent.$options.name;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
},
|
||||||
|
chagne(e) {
|
||||||
|
const values = e.detail.value
|
||||||
|
|
||||||
|
let detail = {
|
||||||
|
value: [],
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.multiple) {
|
||||||
|
this.range.forEach(item => {
|
||||||
|
|
||||||
|
if (values.includes(item[this.map.value] + '')) {
|
||||||
|
detail.value.push(item[this.map.value])
|
||||||
|
detail.data.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const range = this.range.find(item => (item[this.map.value] + '') === values)
|
||||||
|
if (range) {
|
||||||
|
detail = {
|
||||||
|
value: range[this.map.value],
|
||||||
|
data: range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this.formItem && this.formItem.setValue(detail.value)
|
||||||
|
// TODO 兼容 vue2
|
||||||
|
this.$emit('input', detail.value);
|
||||||
|
// // TOTO 兼容 vue3
|
||||||
|
this.$emit('update:modelValue', detail.value);
|
||||||
|
this.$emit('change', {
|
||||||
|
detail
|
||||||
|
})
|
||||||
|
if (this.multiple) {
|
||||||
|
// 如果 v-model 没有绑定 ,则走内部逻辑
|
||||||
|
// if (this.value.length === 0) {
|
||||||
|
this.dataList = this.getDataList(detail.value, true)
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
this.dataList = this.getDataList(detail.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取渲染的新数组
|
||||||
|
* @param {Object} value 选中内容
|
||||||
|
*/
|
||||||
|
getDataList(value) {
|
||||||
|
// 解除引用关系,破坏原引用关系,避免污染源数据
|
||||||
|
let dataList = JSON.parse(JSON.stringify(this.range))
|
||||||
|
let list = []
|
||||||
|
if (this.multiple) {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataList.forEach((item, index) => {
|
||||||
|
item.disabled = item.disable || item.disabled || false
|
||||||
|
if (this.multiple) {
|
||||||
|
if (value.length > 0) {
|
||||||
|
let have = value.find(val => val === item[this.map.value])
|
||||||
|
item.selected = have !== undefined
|
||||||
|
} else {
|
||||||
|
item.selected = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.selected = value === item[this.map.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(item)
|
||||||
|
})
|
||||||
|
return this.setRange(list)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理最大最小值
|
||||||
|
* @param {Object} list
|
||||||
|
*/
|
||||||
|
setRange(list) {
|
||||||
|
let selectList = list.filter(item => item.selected)
|
||||||
|
let min = Number(this.min) || 0
|
||||||
|
let max = Number(this.max) || ''
|
||||||
|
list.forEach((item, index) => {
|
||||||
|
if (this.multiple) {
|
||||||
|
if (selectList.length <= min) {
|
||||||
|
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||||
|
if (have !== undefined) {
|
||||||
|
item.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectList.length >= max && max !== '') {
|
||||||
|
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||||
|
if (have === undefined) {
|
||||||
|
item.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setStyles(item, index)
|
||||||
|
list[index] = item
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置 class
|
||||||
|
* @param {Object} item
|
||||||
|
* @param {Object} index
|
||||||
|
*/
|
||||||
|
setStyles(item, index) {
|
||||||
|
// 设置自定义样式
|
||||||
|
item.styleBackgroud = this.setStyleBackgroud(item)
|
||||||
|
item.styleIcon = this.setStyleIcon(item)
|
||||||
|
item.styleIconText = this.setStyleIconText(item)
|
||||||
|
item.styleRightIcon = this.setStyleRightIcon(item)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取选中值
|
||||||
|
* @param {Object} range
|
||||||
|
*/
|
||||||
|
getSelectedValue(range) {
|
||||||
|
if (!this.multiple) return this.dataValue
|
||||||
|
let selectedArr = []
|
||||||
|
range.forEach((item) => {
|
||||||
|
if (item.selected) {
|
||||||
|
selectedArr.push(item[this.map.value])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this.dataValue.length > 0 ? this.dataValue : selectedArr
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置背景样式
|
||||||
|
*/
|
||||||
|
setStyleBackgroud(item) {
|
||||||
|
let styles = {}
|
||||||
|
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||||
|
if (this.selectedColor) {
|
||||||
|
if (this.mode !== 'list') {
|
||||||
|
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||||
|
}
|
||||||
|
if (this.mode === 'tag') {
|
||||||
|
styles['background-color'] = item.selected? selectedColor:'#f5f5f5'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let classles = ''
|
||||||
|
for (let i in styles) {
|
||||||
|
classles += `${i}:${styles[i]};`
|
||||||
|
}
|
||||||
|
return classles
|
||||||
|
},
|
||||||
|
setStyleIcon(item) {
|
||||||
|
let styles = {}
|
||||||
|
let classles = ''
|
||||||
|
if (this.selectedColor) {
|
||||||
|
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||||
|
styles['background-color'] = item.selected?selectedColor:'#fff'
|
||||||
|
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||||
|
|
||||||
|
if(!item.selected && item.disabled){
|
||||||
|
styles['background-color'] = '#F2F6FC'
|
||||||
|
styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i in styles) {
|
||||||
|
classles += `${i}:${styles[i]};`
|
||||||
|
}
|
||||||
|
return classles
|
||||||
|
},
|
||||||
|
setStyleIconText(item) {
|
||||||
|
let styles = {}
|
||||||
|
let classles = ''
|
||||||
|
if (this.selectedColor) {
|
||||||
|
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
|
||||||
|
if (this.mode === 'tag') {
|
||||||
|
styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666'
|
||||||
|
} else {
|
||||||
|
styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:selectedColor):'#666'
|
||||||
|
}
|
||||||
|
if(!item.selected && item.disabled){
|
||||||
|
styles.color = '#999'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i in styles) {
|
||||||
|
classles += `${i}:${styles[i]};`
|
||||||
|
}
|
||||||
|
return classles
|
||||||
|
},
|
||||||
|
setStyleRightIcon(item) {
|
||||||
|
let styles = {}
|
||||||
|
let classles = ''
|
||||||
|
if (this.mode === 'list') {
|
||||||
|
styles['border-color'] = item.selected?this.styles.selectedColor:'#DCDFE6'
|
||||||
|
}
|
||||||
|
for (let i in styles) {
|
||||||
|
classles += `${i}:${styles[i]};`
|
||||||
|
}
|
||||||
|
|
||||||
|
return classles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$uni-primary: #2979ff !default;
|
||||||
|
$border-color: #DCDFE6;
|
||||||
|
$disable:0.4;
|
||||||
|
|
||||||
|
@mixin flex {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-data-loading {
|
||||||
|
@include flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
padding-left: 10px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-data-checklist {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
flex: 1;
|
||||||
|
// 多选样式
|
||||||
|
.checklist-group {
|
||||||
|
@include flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&.is-list {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-box {
|
||||||
|
@include flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
margin: 5px 0;
|
||||||
|
margin-right: 25px;
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文字样式
|
||||||
|
.checklist-content {
|
||||||
|
@include flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.checklist-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-left: 5px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkobx__list {
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-right-color: #007aff;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-bottom-width:1px;
|
||||||
|
border-bottom-color: #007aff;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
height: 12px;
|
||||||
|
width: 6px;
|
||||||
|
left: -5px;
|
||||||
|
transform-origin: center;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多选样式
|
||||||
|
.checkbox__inner {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
position: absolute;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
top: 2px;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
top: 1px;
|
||||||
|
/* #endif */
|
||||||
|
left: 5px;
|
||||||
|
height: 8px;
|
||||||
|
width: 4px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-right-color: #fff;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-bottom-width:1px ;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
opacity: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
transform: rotate(40deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单选样式
|
||||||
|
.radio__inner {
|
||||||
|
@include flex;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.radio__inner-icon {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认样式
|
||||||
|
&.is--default {
|
||||||
|
|
||||||
|
// 禁用
|
||||||
|
&.is-disable {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
.checkbox__inner {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio__inner {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
}
|
||||||
|
.checklist-text {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
&.is-checked {
|
||||||
|
.checkbox__inner {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
background-color: $uni-primary;
|
||||||
|
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.radio__inner {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
.radio__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: $uni-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.checklist-text {
|
||||||
|
color: $uni-primary;
|
||||||
|
}
|
||||||
|
// 选中禁用
|
||||||
|
&.is-disable {
|
||||||
|
.checkbox__inner {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-text {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
.radio__inner {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮样式
|
||||||
|
&.is--button {
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px $border-color solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
// 禁用
|
||||||
|
&.is-disable {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
border: 1px #eee solid;
|
||||||
|
opacity: $disable;
|
||||||
|
.checkbox__inner {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
.radio__inner {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
.checklist-text {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
.checkbox__inner {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
background-color: $uni-primary;
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio__inner {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
|
||||||
|
.radio__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: $uni-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-text {
|
||||||
|
color: $uni-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中禁用
|
||||||
|
&.is-disable {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签样式
|
||||||
|
&.is--tag {
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px $border-color solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
.checklist-text {
|
||||||
|
margin: 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用
|
||||||
|
&.is-disable {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
background-color: $uni-primary;
|
||||||
|
border-color: $uni-primary;
|
||||||
|
|
||||||
|
.checklist-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 列表样式
|
||||||
|
&.is--list {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
padding: 10px 15px;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.is-list-border {
|
||||||
|
border-top: 1px #eee solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用
|
||||||
|
&.is-disable {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
.checkbox__inner {
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
.checklist-text {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
.checkbox__inner {
|
||||||
|
border-color: $uni-primary;
|
||||||
|
background-color: $uni-primary;
|
||||||
|
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.radio__inner {
|
||||||
|
.radio__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.checklist-text {
|
||||||
|
color: $uni-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-content {
|
||||||
|
.checkobx__list {
|
||||||
|
opacity: 1;
|
||||||
|
border-color: $uni-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中禁用
|
||||||
|
&.is-disable {
|
||||||
|
.checkbox__inner {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-text {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
84
src/uni_modules/uni-data-checkbox/package.json
Normal file
84
src/uni_modules/uni-data-checkbox/package.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"id": "uni-data-checkbox",
|
||||||
|
"displayName": "uni-data-checkbox 数据选择器",
|
||||||
|
"version": "1.0.3",
|
||||||
|
"description": "通过数据驱动的单选框和复选框",
|
||||||
|
"keywords": [
|
||||||
|
"uni-ui",
|
||||||
|
"checkbox",
|
||||||
|
"单选",
|
||||||
|
"多选",
|
||||||
|
"单选多选"
|
||||||
|
],
|
||||||
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.1"
|
||||||
|
},
|
||||||
|
"directories": {
|
||||||
|
"example": "../../temps/example_temps"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
|
"type": "component-vue"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": ["uni-load-more","uni-scss"],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
},
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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