feat:学情报告数据显示
This commit is contained in:
parent
e0fc132885
commit
defc2ab5f1
@ -8,21 +8,23 @@ import { storeToRefs } from 'pinia'
|
||||
import { useBadgeStore } from '@/store/badge'
|
||||
import { useChildStore } from '@/store/child'
|
||||
import hud from '@/utils/hud'
|
||||
import { getStudentStudyDataApi } from '@/api'
|
||||
import { useStudyDataStore } from '@/store/study-data'
|
||||
import { secondsToHours } from '@/utils'
|
||||
|
||||
const childStore = useChildStore()
|
||||
const { getChildren } = childStore
|
||||
const { children } = storeToRefs(childStore)
|
||||
const { refreshBadge } = useBadgeStore()
|
||||
const { getStudentStudyData } = useStudyDataStore()
|
||||
|
||||
const showChild = ref<ChildrenType>() // 展示的孩子
|
||||
|
||||
const showChildData = ref({} as any)
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST
|
||||
const arrow = `${OSS_URL}/iconfont/down_arrow.png`
|
||||
|
||||
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
|
||||
const showAllChildren = ref(false)
|
||||
const checkedId = ref<string>()
|
||||
const checkedId = ref()
|
||||
function handleShowChild(i: ChildrenType) {
|
||||
checkedId.value = i.child?.id
|
||||
showChild.value = i
|
||||
@ -90,7 +92,13 @@ const subjectOptions = [
|
||||
const subject = ref(subjectOptions[0].value)
|
||||
|
||||
watch(
|
||||
[() => dataType.value, () => timeType.value, () => timeDay.value, () => subject.value],
|
||||
[
|
||||
() => dataType.value,
|
||||
() => timeType.value,
|
||||
() => timeDay.value,
|
||||
() => subject.value,
|
||||
() => checkedId.value,
|
||||
],
|
||||
() => {
|
||||
const params = {
|
||||
queryType: dataType.value,
|
||||
@ -104,7 +112,9 @@ watch(
|
||||
if (dataType.value === dataTypeSubject) {
|
||||
params.subjectId = subject.value
|
||||
}
|
||||
getStudentStudyDataApi(params)
|
||||
getStudentStudyData(params).then(map => {
|
||||
showChildData.value = map[checkedId.value] || {}
|
||||
})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
@ -214,94 +224,94 @@ onMounted(() => {
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="study-data">
|
||||
<mj-segment v-model="dataType" :options="dataTypeOptions" />
|
||||
<view v-if="checkedId" class="study-data">
|
||||
<view class="time-type-box">
|
||||
<mj-segment class="time-type" v-model="timeType" :options="timeTypeOptions" />
|
||||
<view class="time-type-text" v-if="timeType === timeTypeDay" @click="timeDayRef.show()">{{
|
||||
timeDay
|
||||
}}</view>
|
||||
</view>
|
||||
<mj-segment v-model="dataType" :options="dataTypeOptions" />
|
||||
<mj-segment v-if="dataType === dataTypeSubject" v-model="subject" :options="subjectOptions" />
|
||||
<tui-grid v-if="dataType === dataTypeSum" class="data-grid">
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ secondsToHours(showChildData.learnTime) }}</view>
|
||||
<text class="label">有效学习时长</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.knowledgeCount || 0 }}</view>
|
||||
<text class="label">知识点</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.errorCount || 0 }}</view>
|
||||
<text class="label">错题</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.correctCount || 0 }}</view>
|
||||
<text class="label">订正</text>
|
||||
</tui-grid-item>
|
||||
</tui-grid>
|
||||
<tui-grid v-else-if="dataType === dataTypeWord" class="data-grid">
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.wordStrangerCount || 0 }}</view>
|
||||
<text class="label">单词陌生</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.wordKnowCount || 0 }}</view>
|
||||
<text class="label">单词认识</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.wordKnowWellCount || 0 }}</view>
|
||||
<text class="label">单词熟悉</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.wordGraspCount || 0 }}</view>
|
||||
<text class="label">单词掌握</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.wordCount || 0 }}</view>
|
||||
<text class="label">单词总数</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.starCount || 0 }}</view>
|
||||
<text class="label">单词闯关星星</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.sentenceCount || 0 }}</view>
|
||||
<text class="label">语感训练句子</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.learnTime || 0 }}</view>
|
||||
<text class="label">有效学习时长</text>
|
||||
</tui-grid-item>
|
||||
</tui-grid>
|
||||
<tui-grid v-else-if="dataType === dataTypeSubject" class="data-grid">
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.successTestCount || 0 }}</view>
|
||||
<text class="label">答题正确</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.errorTestCount || 0 }}</view>
|
||||
<text class="label">答题错误</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.allKnowledgeCount || 0 }}</view>
|
||||
<text class="label">知识点总数</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.allKnowledgeGraspCount || 0 }}</view>
|
||||
<text class="label">知识点掌握</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.allReviewCount || 0 }}</view>
|
||||
<text class="label">订正总数</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.successReviewCount || 0 }}</view>
|
||||
<text class="label">订正正确</text>
|
||||
</tui-grid-item>
|
||||
<tui-grid-item :cell="2">
|
||||
<view class="value">{{ 0 }}</view>
|
||||
<view class="value">{{ showChildData.learnTime || 0 }}</view>
|
||||
<text class="label">有效学习时长</text>
|
||||
</tui-grid-item>
|
||||
</tui-grid>
|
||||
@ -366,251 +376,81 @@ onMounted(() => {
|
||||
background-color: #fff;
|
||||
padding: 0 30rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 30rpx;
|
||||
font-size: 34rpx;
|
||||
line-height: 48rpx;
|
||||
color: $font-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.title2 {
|
||||
margin: 30rpx 0 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trend_icon {
|
||||
margin-left: 10rpx;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
|
||||
.study_situation {
|
||||
display: flex;
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
padding: 42rpx 30rpx;
|
||||
.children {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 119rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7faff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
box-sizing: border-box;
|
||||
padding: 30rpx 0 30rpx 0;
|
||||
border-bottom: 1rpx solid $border-color;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: #3fd15f;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.study_situation2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.item {
|
||||
width: calc(50% - 15rpx);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
|
||||
&_label {
|
||||
display: block;
|
||||
height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
background-color: #ebf2ff;
|
||||
border-bottom: 1rpx solid #d9e1f2;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&_date {
|
||||
display: block;
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 79rpx;
|
||||
line-height: 79rpx;
|
||||
background-color: #f7faff;
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
|
||||
font-size: 28rpx;
|
||||
// rgba(255, 40, 40, 1)jiang rgba(255, 141, 95, 1)ping
|
||||
.children_avatar {
|
||||
margin-right: 20rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name_vip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6rpx;
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
max-width: 210rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
line-height: 48rpx;
|
||||
color: $font-color;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vip {
|
||||
margin-left: 10rpx;
|
||||
width: 85rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
color: $font-aux-color;
|
||||
}
|
||||
}
|
||||
|
||||
.keep {
|
||||
color: #ff8d5f;
|
||||
.icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.down {
|
||||
color: #ff2828;
|
||||
.checked_icon {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.up {
|
||||
color: #21d17a;
|
||||
.rotate {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date_filter_box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.children {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 0 30rpx 0;
|
||||
border-bottom: 1rpx solid $border-color;
|
||||
|
||||
.left {
|
||||
.children_info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.children_avatar {
|
||||
margin-right: 20rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name_vip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6rpx;
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
max-width: 210rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
line-height: 48rpx;
|
||||
color: $font-color;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vip {
|
||||
margin-left: 10rpx;
|
||||
width: 85rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
color: $font-aux-color;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.checked_icon {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.children_info {
|
||||
display: flex;
|
||||
padding: 30rpx 0 0 0;
|
||||
|
||||
.item {
|
||||
width: 50%;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
color: $font-aux-color;
|
||||
|
||||
.level {
|
||||
color: $font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter_box {
|
||||
padding: 30rpx 30rpx 13rpx 30rpx;
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.learning_time {
|
||||
.date_filter_box {
|
||||
margin: 50rpx 0 30rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.video_learning {
|
||||
.date_filter_box {
|
||||
margin: 30rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.exercise_statistics {
|
||||
.study_situation2 {
|
||||
margin: 30rpx 0 0 0;
|
||||
padding: 30rpx 0 0 0;
|
||||
|
||||
.item {
|
||||
width: 210rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
width: 50%;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
color: $font-aux-color;
|
||||
|
||||
.language_statistics {
|
||||
.study_situation2 {
|
||||
margin: 30rpx 0 50rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.knowledge_mapping {
|
||||
.chart_label {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
color: rgba(102, 102, 102, 1);
|
||||
|
||||
.iden_color {
|
||||
display: inline-block;
|
||||
width: 30rpx;
|
||||
height: 8rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.increase {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
|
||||
.iden_color {
|
||||
background-color: rgba(69, 203, 70, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.decrease {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iden_color {
|
||||
background-color: rgba(248, 104, 96, 1);
|
||||
.level {
|
||||
color: $font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
||||
</view>
|
||||
<view class="code_btn">
|
||||
<template v-if="codeTime > 0">
|
||||
<text class="time_tip">{{ codeTimeContet }} 秒后重新发送</text>
|
||||
<text class="time_tip">{{ codeTimeContent }} 秒后重新发送</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="time_tip" @click="getCode">获取验证码</text>
|
||||
@ -67,7 +67,7 @@ const { getUserInfo, setToken } = useUserStore()
|
||||
const formatPhone = ref('')
|
||||
const verifyCode = ref('')
|
||||
const codeTime = ref(0)
|
||||
const codeTimeContet = computed(() => {
|
||||
const codeTimeContent = computed(() => {
|
||||
return codeTime.value
|
||||
})
|
||||
|
||||
@ -119,7 +119,6 @@ async function handleLogin() {
|
||||
await smsLogin()
|
||||
}
|
||||
|
||||
let wxUser: any
|
||||
async function smsLogin() {
|
||||
hud.load({
|
||||
task: async () => {
|
||||
@ -128,7 +127,7 @@ async function smsLogin() {
|
||||
clientType: 'PARENT',
|
||||
phone: formatPhone.value.replace(/\s+/g, ''),
|
||||
verifyCode: verifyCode.value,
|
||||
wxUser,
|
||||
wxUser: db.get('wxUser') || undefined,
|
||||
})
|
||||
await loginSuccess(token)
|
||||
},
|
||||
@ -150,16 +149,13 @@ async function wxLogin() {
|
||||
const data = await wxLoginApi({
|
||||
code,
|
||||
})
|
||||
// 存储openid
|
||||
if (data.wxUser.openid) {
|
||||
db.set('openid', data.wxUser.openid)
|
||||
// 存储wxUser
|
||||
if (data.wxUser) {
|
||||
db.set('wxUser', data.wxUser)
|
||||
}
|
||||
if (data.accessToken) {
|
||||
// 说明已经有绑定过账号
|
||||
await loginSuccess(data.accessToken)
|
||||
} else {
|
||||
// 说明没有绑定过账号
|
||||
wxUser = data.wxUser
|
||||
}
|
||||
},
|
||||
option: '登录',
|
||||
|
@ -93,6 +93,7 @@ onMounted(() => {})
|
||||
<text class="identity">家长</text>
|
||||
</view>
|
||||
<view class="right-bottom">
|
||||
<text class="account">{{ userInfo.account }}</text>
|
||||
<text
|
||||
>乐贝:<text class="currency">{{ userInfo.currency }}</text></text
|
||||
>
|
||||
@ -168,6 +169,11 @@ onMounted(() => {})
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
color: $font-aux-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
.account {
|
||||
}
|
||||
|
||||
.currency {
|
||||
color: #333;
|
||||
|
12
src/store/study-data.ts
Normal file
12
src/store/study-data.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { getStudentStudyDataApi } from '@/api'
|
||||
import obj from '@/utils/obj'
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useStudyDataStore = defineStore('study-data', () => {
|
||||
async function getStudentStudyData(params: any) {
|
||||
const res = await getStudentStudyDataApi(params)
|
||||
return obj.list2map(res, 'userId')
|
||||
}
|
||||
return { getStudentStudyData }
|
||||
})
|
@ -25,7 +25,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
const getUserInfo = async () => {
|
||||
const mode = import.meta.env.MODE
|
||||
// 如果是正式环境 && 且缺少openid,直接跳回登录页面
|
||||
if (mode !== 'dev' && mode !== 'mp' && !db.get('openid')) {
|
||||
if (mode !== 'dev' && mode !== 'mp' && !db.get('wxUser')) {
|
||||
router.toLogin()
|
||||
return
|
||||
}
|
||||
|
230
src/utils/obj.ts
230
src/utils/obj.ts
@ -1,25 +1,229 @@
|
||||
// 对象相关处理
|
||||
function clearNullProps(obj: any) {
|
||||
for (const key in obj) {
|
||||
if (obj[key] === null || obj[key] === undefined) {
|
||||
delete obj[key]
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (obj[key] === null || obj[key] === undefined) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// 计算父节点和子节点的总数
|
||||
function getNodesSize(nodes: any[], childrenKey = 'children') {
|
||||
let size = 0
|
||||
for (const node of nodes) {
|
||||
size += 1
|
||||
if (node[childrenKey]?.length > 0) {
|
||||
size += getNodesSize(node.children, childrenKey)
|
||||
}
|
||||
let size = 0
|
||||
for (const node of nodes) {
|
||||
size += 1
|
||||
if (node[childrenKey]?.length > 0) {
|
||||
size += getNodesSize(node.children, childrenKey)
|
||||
}
|
||||
return size
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// 只赋值dst中存在的属性
|
||||
function assignExits(dst: any, src: any) {
|
||||
for (const key in dst) {
|
||||
dst[key] = src[key]
|
||||
}
|
||||
}
|
||||
|
||||
// 只提取指定的属性
|
||||
function filterKeys(obj: any, keys: string[]) {
|
||||
const newObj: any = {}
|
||||
for (const key of keys) {
|
||||
newObj[key] = obj[key]
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
// 是否没有值
|
||||
function isNullOrUndefined(obj: any) {
|
||||
return obj === null || obj === undefined
|
||||
}
|
||||
|
||||
function isEqualIgnoreNumberString(obj1: any, obj2: any) {
|
||||
return isEqual(obj1, obj2, { ignoreNumberString: true })
|
||||
}
|
||||
|
||||
function isEqual(
|
||||
obj1: any,
|
||||
obj2: any,
|
||||
options?: {
|
||||
ignoreNumberString?: boolean
|
||||
},
|
||||
) {
|
||||
if (obj1 === obj2) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 忽略数字、字符串的比较
|
||||
if (
|
||||
options?.ignoreNumberString &&
|
||||
(typeof obj1 === 'number' || typeof obj1 === 'string') &&
|
||||
(typeof obj2 === 'number' || typeof obj2 === 'string')
|
||||
) {
|
||||
return String(obj1) === String(obj2)
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
|
||||
if (keys1.length !== keys2.length) return false
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) return false
|
||||
if (!isEqual(obj1[key], obj2[key], options)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function isKeyValueObject(obj: any) {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
!Array.isArray(obj) &&
|
||||
!(obj instanceof Date) &&
|
||||
!(obj instanceof RegExp) &&
|
||||
!(obj instanceof Map) &&
|
||||
!(obj instanceof Set)
|
||||
)
|
||||
}
|
||||
|
||||
function coverNullProps(dst: any, src: any) {
|
||||
return cover(dst, src, {
|
||||
onlyNullProps: true,
|
||||
})
|
||||
}
|
||||
|
||||
function moveElement<T>(arr: T[], fromIndex: number, toIndex: number): T[] {
|
||||
// 处理边界情况
|
||||
if (fromIndex < 0 || fromIndex >= arr.length || toIndex < 0 || toIndex >= arr.length) {
|
||||
return arr
|
||||
}
|
||||
|
||||
// 移除fromIndex位置的元素
|
||||
const [element] = arr.splice(fromIndex, 1)
|
||||
// 插入到toIndex位置
|
||||
arr.splice(toIndex, 0, element)
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
// 覆盖属性
|
||||
function cover(
|
||||
dst: any,
|
||||
src: any,
|
||||
options?: {
|
||||
onlyNullProps?: boolean
|
||||
},
|
||||
) {
|
||||
if (!isKeyValueObject(dst) || !isKeyValueObject(src)) {
|
||||
return dst
|
||||
}
|
||||
const onlyNull = options?.onlyNullProps
|
||||
// 合并所有的key
|
||||
const keys = Array.from(new Set([...Object.keys(dst), ...Object.keys(src)]))
|
||||
for (const key of keys) {
|
||||
// 跳过
|
||||
if (!(key in src)) continue
|
||||
// 来源值
|
||||
const srcVal = src[key]
|
||||
// 目标值
|
||||
const dstVal = dst[key]
|
||||
if (onlyNull && dstVal !== undefined && dstVal !== null) {
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(srcVal)) {
|
||||
// 如果是数组
|
||||
if (Array.isArray(dstVal)) {
|
||||
dst[key] = [...dstVal, ...srcVal]
|
||||
} else {
|
||||
dst[key] = [...srcVal]
|
||||
}
|
||||
} else if (isKeyValueObject(srcVal) && isKeyValueObject(dstVal)) {
|
||||
// 如果是对象类型
|
||||
dst[key] = cover(dstVal, srcVal)
|
||||
} else {
|
||||
// 如果是非对象类型
|
||||
dst[key] = srcVal
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
function getAllMethods(obj: any): Record<string, any> {
|
||||
const methods: Record<string, any> = {}
|
||||
let current = obj
|
||||
|
||||
while (current && current !== Object.prototype) {
|
||||
const proto = Object.getPrototypeOf(current)
|
||||
Object.getOwnPropertyNames(proto)
|
||||
.filter(name => name !== 'constructor' && typeof proto[name] === 'function')
|
||||
.forEach(name => {
|
||||
// 这个判断很重要,优先返回自己的属性,不希望被父类的属性覆盖
|
||||
if (methods[name]) return
|
||||
methods[name] = proto[name]
|
||||
})
|
||||
current = proto
|
||||
}
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
function snowflakeId(workerId = 1): string {
|
||||
// 自定义起始时间
|
||||
const timestamp = BigInt(Date.now())
|
||||
const workerBits = BigInt(workerId) << 12n
|
||||
const sequenceBits = BigInt(Math.floor(Math.random() * 4096))
|
||||
return String((timestamp << 22n) | workerBits | sequenceBits)
|
||||
}
|
||||
|
||||
function list2map(list: any[], key: string, value?: string): any {
|
||||
const map: Record<string, any> = {}
|
||||
if (!list?.length) return map
|
||||
for (const item of list) {
|
||||
map[item[key]] = value ? item[value] : item
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
function options2map(list: any[]): any {
|
||||
return list2map(list, 'value', 'label')
|
||||
}
|
||||
|
||||
function map2options(map: Record<number, string>) {
|
||||
// 将map转换成options
|
||||
const arr = []
|
||||
for (const value in map) {
|
||||
arr.push({
|
||||
value,
|
||||
label: map[value],
|
||||
})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
export default {
|
||||
clearNullProps,
|
||||
getNodesSize,
|
||||
clearNullProps,
|
||||
getNodesSize,
|
||||
assignExits,
|
||||
filterKeys,
|
||||
isEqual,
|
||||
isEqualIgnoreNumberString,
|
||||
cover,
|
||||
map2options,
|
||||
list2map,
|
||||
coverNullProps,
|
||||
isKeyValueObject,
|
||||
isNullOrUndefined,
|
||||
getAllMethods,
|
||||
snowflakeId,
|
||||
options2map,
|
||||
moveElement,
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import dayjs from 'dayjs'
|
||||
import isToday from 'dayjs/plugin/isToday'
|
||||
import isYesterday from 'dayjs/plugin/isYesterday'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
import db from './db'
|
||||
|
||||
const OSS_URL = import.meta.env.VITE_OSS_HOST
|
||||
@ -8,6 +9,7 @@ const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
|
||||
|
||||
dayjs.extend(isToday)
|
||||
dayjs.extend(isYesterday)
|
||||
dayjs.extend(duration)
|
||||
// store统一设置
|
||||
export const setCache = (key: string, value: any) => {
|
||||
if (!key) throw new Error('key is required')
|
||||
@ -36,6 +38,18 @@ export const ArrToObj = (arr: any[], key = 'code', value = 'value'): { [key: str
|
||||
return obj
|
||||
}
|
||||
|
||||
export function secondsToHours(s: any) {
|
||||
let t = Number(s)
|
||||
if (isNaN(t) || t <= 0) {
|
||||
return '0'
|
||||
}
|
||||
const day = dayjs.duration(t, 's')
|
||||
return `${String(Math.floor(day.asHours())).padStart(2, '0')}:${String(day.minutes()).padStart(
|
||||
2,
|
||||
'0',
|
||||
)}:${String(day.seconds()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 延迟运行
|
||||
export const $sleep = (time: number) => {
|
||||
return new Promise(res => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user