feat:手动绑定孩子账号

This commit is contained in:
MJ 2025-08-06 23:44:33 +08:00
parent 549e25155c
commit 81c2f77434
21 changed files with 544 additions and 392 deletions

11
.vscode/settings.json vendored
View File

@ -48,5 +48,14 @@
"src/uni_modules/uni-search-bar/components/uni-search-bar/i18n",
"src/uni_modules/z-paging/components/z-paging/i18n"
],
"cSpell.words": ["iconfont", "persistedstate", "pinia", "VITE", "Wechat", "Weixin", "xuexiaole"]
"cSpell.words": [
"dcloudio",
"iconfont",
"persistedstate",
"pinia",
"VITE",
"Wechat",
"Weixin",
"xuexiaole"
]
}

9
pnpm-lock.yaml generated
View File

@ -1926,6 +1926,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
address@1.2.2:
resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
engines: {node: '>= 10.0.0'}
@ -7435,6 +7440,8 @@ snapshots:
acorn@8.11.3: {}
acorn@8.15.0: {}
address@1.2.2: {}
agent-base@6.0.2:
@ -9309,7 +9316,7 @@ snapshots:
jsdom@16.7.0:
dependencies:
abab: 2.0.6
acorn: 8.11.3
acorn: 8.15.0
acorn-globals: 6.0.0
cssom: 0.4.4
cssstyle: 2.3.0

View File

@ -1,50 +1,50 @@
import { http } from '../request/request';
import { http } from '../request/request'
/**
*
* @param params
*/
export const getCurInfo = async (params: any) =>
await http.get<any>('/parentBind/curInfo', params);
export const getCurInfo = async (params: any) => await http.get<any>('/parentBind/curInfo', params)
/**
*
* @param data
*/
export const parentBindAdmin = async (data: any) =>
await http.post<any>('/parentBind/admin', data);
export const parentBindAdmin = async (data: any) => await http.post<any>('/parentBind/admin', data)
/**
*
* @param data
*/
export const parentBindApply = async (data: any) =>
await http.post<any>('/parentBind/apply', data);
export const parentBindApply = async (data: any) => await http.post<any>('/parentBind/apply', data)
// 手动输入账号绑定孩子
export const manualBindApi = async (childAcc: any) =>
await http.postForm<any>('/parentBind/familyTeacherBindAcc', { childAcc })
/**
* -
* @param params
*/
export const adminInfo = async (params: any) =>
await http.get<any>('/parentBind/adminInfo', params);
export const adminInfo = async (params: any) => await http.get<any>('/parentBind/adminInfo', params)
/**
* -
* @param params
*/
export const applyInfo = async (params: any) =>
await http.get<any>('/parentBind/apply/info', params);
await http.get<any>('/parentBind/apply/info', params)
/**
*
* @param data
*/
export const adminApproval = async (data: any) =>
await http.post<any>('/parentBind/admin/approval', data);
await http.post<any>('/parentBind/admin/approval', data)
/**
*
* @param data
*/
export const deviceTimeControl = async (data: any) =>
await http.post<any>('/deviceTimeControl', data);
await http.post<any>('/deviceTimeControl', data)

View File

@ -50,8 +50,8 @@ function switchTab(item, index) {
onMounted(async () => {
currentIndex.value = props.selectedIndex
//
await getApplyBindInfoList()
notice.value = db.get('applyBindMsg')
notice.value = await getApplyBindInfoList()
list.value = [
{
pagePath: '/pages/home/index',

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
defineOptions({ name: 'mj-dialog' })
const props = defineProps<{
modelValue: boolean
title?: string
hideCancel?: boolean
hideOk?: boolean
okText?: string
cancelText?: string
maskClosable?: boolean
ok?: any
cancel?: any
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const show = computed({
get: () => props.modelValue,
set: val => {
emit('update:modelValue', val)
},
})
//
const btns = []
if (!props.hideCancel) {
btns.push({
text: props.cancelText || '取消',
color: '#999',
})
}
if (!props.hideOk) {
btns.push({
text: props.okText || '确定',
color: '#615dff',
})
}
const buttons = ref(btns)
function btnClick({ index }) {
if (index === 0) {
props.cancel?.()
emit('update:modelValue', false)
} else {
props.ok?.()
}
}
</script>
<template>
<tui-dialog
:show="show"
:title="title"
:buttons="buttons"
:maskClosable="maskClosable"
@click="btnClick"
>
<template v-slot:content>
<slot />
</template>
</tui-dialog>
</template>
<style lang="scss" scoped></style>

View File

@ -3,7 +3,7 @@ defineOptions({ name: 'mj-empty' })
</script>
<template>
<tui-no-data imgUrl="/static/nodata.png">暂无数据</tui-no-data>
<tui-no-data imgUrl="/static/nodata.png">暂无数据</tui-no-data>
</template>
<style lang="scss" scoped>

View File

@ -57,7 +57,7 @@ const dialog_default_buttons = [
},
{
text: '确定',
color: '#333',
color: '#615dff',
},
]
@ -90,6 +90,7 @@ function dialog(options: DialogOptions) {
if (options.cancelColor) {
options.buttons[0].color = options.cancelColor
}
//
}
//
@ -118,9 +119,12 @@ function dialog(options: DialogOptions) {
clickFn(hide, e)
}
//
if (cancelFn && e.index === 0) {
cancelFn()
hide()
if (e.index === 0) {
if (cancelFn) {
cancelFn(hide)
} else {
hide()
}
}
//
const okIdx = (options.buttons?.length || 1) - 1

View File

@ -392,12 +392,6 @@
"selectedIconPath": "static/tabBar/studyReport.png",
"text": "学情报告"
},
{
"pagePath": "pages/applicationManagement/index",
"iconPath": "static/tabBar/applicationNo.png",
"selectedIconPath": "static/tabBar/application.png",
"text": "应用管控"
},
{
"pagePath": "pages/mine/index",
"iconPath": "static/tabBar/mineno.png",

View File

@ -8,8 +8,9 @@
<Empty v-if="empty" content="暂无学情报告数据哦~" />
</mj-page>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import { onMounted, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import Empty from '@/components/Empty/index.vue'
import ChildrenBox from './components/childrenBox.vue'
@ -20,11 +21,11 @@ import './index.scss'
import { getParentBindChildApi, subjectApi } from '@/api'
import { userStore, bindApplyStore } from '@/store'
import { storeToRefs } from 'pinia'
import db from '@/utils/db'
import hud from '@/utils/hud'
import { badgeStore } from '@/store/badge'
const { userInfo } = storeToRefs(userStore())
const { getApplyBindInfoList } = bindApplyStore()
const { refreshBadge } = badgeStore()
const childrenList = ref<ChildrenType[]>() //
const empty = ref(false)
@ -71,25 +72,9 @@ async function getSubject() {
subjectList.value = data
chooseSubject.value = data[0]
}
//
async function handleTabBarBadge() {
await getApplyBindInfoList()
const hasApplyMsg = db.get('applyBindMsg')
if (hasApplyMsg) {
uni.setTabBarBadge({
index: 3, // ""tabBar
text: '●',
})
} else {
uni.removeTabBarBadge({
index: 3,
})
}
}
onShow(() => {
handleTabBarBadge()
refreshBadge()
})
onMounted(async () => {
@ -97,7 +82,6 @@ onMounted(async () => {
hud.loading()
await getParentBindChild()
await getSubject()
handleTabBarBadge()
} finally {
hud.hide()
}

View File

@ -72,7 +72,7 @@
v-for="(i, idx) in navList"
:key="idx"
:class="{ navigate_item: true, active: activeNav.type === i.type }"
@click="swicthNav(i)"
@click="switchNav(i)"
>{{ i.title }}</text
>
</view>
@ -132,7 +132,6 @@
</view>
</view>
</wd-popup>
</view>
</template>
@ -147,9 +146,10 @@ import Empty from '@/components/Empty/index.vue'
import { userStore, bindApplyStore } from '@/store'
import { storeToRefs } from 'pinia'
import db from '@/utils/db'
import { badgeStore } from '@/store/badge'
const { userInfo } = storeToRefs(userStore())
const { getApplyBindInfoList } = bindApplyStore()
const { refreshBadge } = badgeStore()
const OSS_URL = import.meta.env.VITE_OSS_HOST
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
const arrow = `${OSS_URL}/iconfont/down_arrow.png`
@ -158,7 +158,6 @@ const unchecked = `${OSS_URL}/iconfont/unchecked.png`
const showAllEquipment = ref(false)
const showCustomPopup = ref(false)
const methodPopup = ref(false)
const methodList = ref([
{
@ -218,7 +217,7 @@ function handleChecked(i) {
fetchAppRecord()
}
function swicthNav(i) {
function switchNav(i) {
activeNav.value = i
}
//
@ -288,31 +287,14 @@ async function fetchAppRecord() {
uni.hideLoading()
}
}
//
async function handleTabBarBadge() {
await getApplyBindInfoList()
const hasApplyMsg = db.get('applyBindMsg')
if (hasApplyMsg) {
uni.setTabBarBadge({
index: 3, // ""tabBar
text: '●'
})
} else {
uni.removeTabBarBadge({
index: 3
})
}
}
onShow(() => {
handleTabBarBadge()
refreshBadge()
})
onMounted(async () => {
await getParentBindDevice()
fetchAppRecord()
handleTabBarBadge()
})
</script>

View File

@ -1,23 +1,23 @@
<template>
<z-paging ref="paging" v-model="dataList" :auto="false" @query="queryList">
<view class="page-container">
<BackBar title="历史截屏" />
<template v-slot:empty>
<view class="empty_box">
<scroll-view scroll-y class="scroll-container">
<view v-if="dataList.length === 0" class="empty_box">
<Empty content="暂无历史截屏数据哦~" />
</view>
</template>
<view class="content">
<view
v-for="(item, index) in dataList"
:key="index"
class="item"
@click="previewImageFun(index)"
>
<image class="itemImage" :src="item.imageUrl" mode="aspectFill" />
<view class="time">{{ item.createTime.replace(/-/g, '.') }}</view>
<view v-else class="content">
<view
v-for="(item, index) in dataList"
:key="index"
class="item"
@click="previewImageFun(index)"
>
<image class="itemImage" :src="item.imageUrl" mode="aspectFill" />
<view class="time">{{ item.createTime.replace(/-/g, '.') }}</view>
</view>
</view>
</view>
</z-paging>
</scroll-view>
</view>
</template>
<script setup lang="ts">
@ -26,16 +26,18 @@ import { deviceScreenshotRecordList } from '@/api';
import { onLoad } from '@dcloudio/uni-app';
import Empty from '@/components/Empty/index.vue';
const dataList = ref([]);
const paging = ref();
const simSerialNumber = ref();
function queryList(pageNo: number, pageSize: number) {
function loadData() {
deviceScreenshotRecordList({
size: pageSize,
current: pageNo,
size: 1000,
current: 1,
simSerialNumber: simSerialNumber.value,
}).then(res => {
paging.value.complete(res.rows);
dataList.value = res.rows || [];
}).catch(() => {
dataList.value = [];
});
}
@ -52,12 +54,23 @@ function previewImageFun(idx: any) {
onLoad(option => {
simSerialNumber.value = option.simSerialNumber;
setTimeout(() => {
paging.value?.reload(); //
loadData();
}, 500);
});
</script>
<style lang="scss" scoped>
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
}
.scroll-container {
flex: 1;
height: 0;
}
.content {
width: 100%;
display: flex;

View File

@ -19,40 +19,40 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getDeviceScreenControlApi, lockDeviceScreenApi } from '@/api';
const OSS_URL = import.meta.env.VITE_OSS_HOST;
const simSerialNumber = ref();
const screenControlInfo = ref<any>({});
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getDeviceScreenControlApi, lockDeviceScreenApi } from '@/api'
const OSS_URL = import.meta.env.VITE_OSS_HOST
const simSerialNumber = ref()
const screenControlInfo = ref<any>({})
async function handleMachineScreen() {
try {
uni.showLoading({
title: `${screenControlInfo?.value.lockFlag === 1}` ? '解中...' : '锁屏中...',
});
const data = await lockDeviceScreenApi(simSerialNumber.value);
fetchDeviceScreen();
title: `${screenControlInfo?.value.lockFlag === 1}` ? '解中...' : '锁屏中...',
})
const data = await lockDeviceScreenApi(simSerialNumber.value)
fetchDeviceScreen()
} finally {
uni.hideLoading();
uni.hideLoading()
}
}
async function fetchDeviceScreen() {
try {
uni.showLoading({
title: '加载中...',
});
const data = await getDeviceScreenControlApi(simSerialNumber.value);
screenControlInfo.value = data ? data : {};
})
const data = await getDeviceScreenControlApi(simSerialNumber.value)
screenControlInfo.value = data ? data : {}
} finally {
uni.hideLoading();
uni.hideLoading()
}
}
onLoad(option => {
simSerialNumber.value = option.simSerialNumber;
fetchDeviceScreen();
});
simSerialNumber.value = option.simSerialNumber
fetchDeviceScreen()
})
</script>
<style lang="scss" scoped>

View File

@ -1,9 +1,9 @@
<template>
<mj-page class="teacher-content">
<view v-if="equipmentList.length === 0" class="binding" @click="bindDevice">
<!-- <view v-if="equipmentList.length === 0" class="binding">
<image class="scan" :src="scanQr" mode=""></image>扫码绑定设备
</view>
<view v-else class="my_equipment">
</view> -->
<view class="my_equipment">
<view class="header">
<text>我的设备</text>
<image
@ -57,11 +57,15 @@
</view>
<view class="equipment_item_left">
<image :src="i.id === showEquipment.id ? checked : unchecked" mode=""></image>
</view> </view
></template>
</view>
</view>
</template>
<view class="binding" @click="bindDevice">
<image class="scan" :src="scanQr" mode=""></image>扫码绑定设备
<view class="binding">
<view class="scan-bind" @click="scanBind">
<image class="scan" :src="scanQr" mode=""></image>扫码绑定
</view>
<view class="hand-bind" @click="handBind"> 手动绑定 </view>
</view>
</view>
<view class="home_function">
@ -72,25 +76,38 @@
</template>
</view>
<!-- 手动绑定弹框 -->
<mj-dialog
v-model="showHandBindPopup"
title="手动绑定孩子账号"
okText="绑定"
:ok="confirmHandBind"
>
<input
v-model="childAccount"
class="account-input"
placeholder="请输入孩子账号"
type="text"
/>
</mj-dialog>
</mj-page>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getParentBindDeviceApi, parentDeviceCurrentLoginApi } from '@/api'
import { bindApplyStore } from '@/store'
import db from '@/utils/db'
import { userStore } from '@/store'
import { storeToRefs } from 'pinia'
import hud from '@/utils/hud'
import router from '@/router/router'
import { badgeStore } from '@/store/badge'
import { getParentBindDeviceApi, manualBindApi } from '@/api'
const store = userStore()
const { getUserInfo } = store
const { userInfo } = storeToRefs(userStore())
const { getApplyBindInfoList } = bindApplyStore()
const { refreshBadge } = badgeStore()
const OSS_URL = import.meta.env.VITE_OSS_HOST
const scanQr = `${OSS_URL}/iconfont/scan_qr.png`
const arrow = `${OSS_URL}/iconfont/down_arrow.png`
@ -99,6 +116,10 @@ const unchecked = `${OSS_URL}/iconfont/unchecked.png`
const showAllDevice = ref(false)
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
//
const showHandBindPopup = ref(false)
const childAccount = ref('')
const functionList = reactive([
{
name: '一键锁屏',
@ -114,20 +135,20 @@ const functionList = reactive([
developing: false,
type: 1,
},
{
name: '时间管控',
bg: `${OSS_URL}/urm/home_time_control.png`,
developingBg: '',
developing: false,
type: 2,
},
{
name: '视频通话',
bg: `${OSS_URL}/urm/home_video_call.png`,
developingBg: `${OSS_URL}/urm/home_video_developing.png`,
developing: true,
type: 3,
},
// {
// name: '',
// bg: `${OSS_URL}/urm/home_time_control.png`,
// developingBg: '',
// developing: false,
// type: 2,
// },
// {
// name: '',
// bg: `${OSS_URL}/urm/home_video_call.png`,
// developingBg: `${OSS_URL}/urm/home_video_developing.png`,
// developing: true,
// type: 3,
// },
])
const equipmentList = ref([])
const showEquipment = ref()
@ -137,10 +158,40 @@ function chooseEquipment(i) {
showEquipment.value = i
}
function bindDevice() {
//
function scanBind() {
router.navigateTo('/pages/home/bindDevice/index')
}
//
function handBind() {
showHandBindPopup.value = true
childAccount.value = ''
}
//
function confirmHandBind() {
if (!childAccount.value.trim()) {
hud.error('请输入孩子账号')
return
}
hud.load({
task: async () => {
const res = Number(await manualBindApi(childAccount.value))
if (res === 1) {
hud.success('绑定成功')
} else if (res === 0) {
hud.error('绑定失败')
} else if (res === 2) {
hud.success('已向管理员发送绑定请求')
}
showHandBindPopup.value = false
},
option: '绑定',
})
}
//
function change(i: any) {
if (equipmentList.value.length === 0) {
@ -198,43 +249,17 @@ async function getParentBindDevice() {
equipmentList.value = data.rows
showEquipment.value = data.rows[0]
}
// for (let i in data.rows) {
// const _r = await parentDeviceCurrentLoginApi(data.rows[i].id)
// equipmentList.value.push({
// ...data.rows[i],
// curDeviceUserName: _r.curDeviceUserName,
// curDeviceUserAvatar: _r.curDeviceUserAvatar,
// })
// }
// showEquipment.value = data.rows?.length > 0 ? equipmentList.value[0] : {}
},
})
}
//
async function handleTabBarBadge() {
await getApplyBindInfoList()
const hasApplyMsg = db.get('applyBindMsg')
if (hasApplyMsg) {
uni.setTabBarBadge({
index: 3, // ""tabBar
text: '●'
})
} else {
uni.removeTabBarBadge({
index: 3
})
}
}
onShow(() => {
handleTabBarBadge()
refreshBadge()
})
onMounted(async () => {
await getUserInfo()
getParentBindDevice()
handleTabBarBadge()
})
</script>
@ -248,7 +273,7 @@ onMounted(async () => {
.binding {
display: flex;
align-items: center;
justify-content: center;
justify-content: space-between;
width: 100%;
height: 120rpx;
background-color: #fff;
@ -258,11 +283,19 @@ onMounted(async () => {
font-size: 34rpx;
font-weight: 500;
line-height: 52rpx;
.scan {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
.scan-bind {
.scan {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
}
.scan-bind,
.hand-bind {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
}
@ -378,4 +411,21 @@ onMounted(async () => {
}
}
}
//
.account-input {
width: 100%;
height: 80rpx;
border: 1px solid #e0e0e0;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
color: #333;
&::placeholder {
color: #ccc;
}
}
</style>

View File

@ -1,143 +1,140 @@
<template>
<view class="about_mine">
<z-paging
ref="paging"
v-model="dataList"
:paging-style="{ margin: '402rpx 30rpx 0 30rpx' }"
:style="{
height: `calc(100% - 532rpx - 120rpx - 52rpx)`,
}"
@query="queryList"
>
<template #top>
<view class="navigate">
<text
v-for="(i, idx) in navList"
:key="idx"
:class="{ navigate_item: true, active: activeNav.type === i.type }"
@click="swicthNav(i)"
>{{ i.title }}</text
>
</view>
</template>
<template v-slot:empty>
<view class="empty_box">
<view class="container">
<view class="navigate">
<text
v-for="(i, idx) in navList"
:key="idx"
:class="{ navigate_item: true, active: activeNav.type === i.type }"
@click="switchNav(i)"
>{{ i.title }}</text
>
</view>
<scroll-view scroll-y class="scroll-container">
<view v-if="noData" class="empty_box">
<image :src="empty" mode="" class="empty"></image>
<view class="tip">您还没有绑定任何数据快去扫码绑定吧</view>
<button class="btn" @click="handleBindDevice">扫码绑定</button>
</view>
</template>
<view class="content">
<!-- 我的孩子 -->
<template v-if="activeNav.type === 1">
<view
v-for="(i, idx) in dataList"
:key="idx"
class="children"
@click="handleDetialPage(activeNav.type, i)"
>
<image
:src="i?.childAvatar ? i?.childAvatar : defaultAvatar"
mode=""
class="children_avatar"
></image>
<view class="children-info">
<view class="name_vip">
<text class="name">{{ i.childName }}</text>
<image
v-if="i.currentLevel"
:src="`${OSS_URL}/iconfont/VIP/VIP_${i.currentLevel}.png`"
class="vip"
/>
</view>
<view class="time">
有效期至{{ i.vipEndTime ? dayjs(i.vipEndTime).format('YYYY.MM.DD') : '-' }}
<view v-else class="content">
<!-- 我的孩子 -->
<template v-if="activeNav.type === nav_child">
<view
v-for="(i, idx) in childList"
:key="idx"
class="children"
@click="handleDetailPage(activeNav.type, i)"
>
<image
:src="i?.childAvatar ? i?.childAvatar : defaultAvatar"
mode=""
class="children_avatar"
></image>
<view class="children-info">
<view class="name_vip">
<text class="name">{{ i.childName }}</text>
<image
v-if="i.currentLevel"
:src="`${OSS_URL}/iconfont/VIP/VIP_${i.currentLevel}.png`"
class="vip"
/>
</view>
<view class="time">
有效期至{{ i.vipEndTime ? dayjs(i.vipEndTime).format('YYYY.MM.DD') : '-' }}
</view>
</view>
<!-- <view v-if="i.havePurchasedInspector" class="service">
<wd-button class="btn" :round="false" @click.stop="toMySupervisionService(i)"
>督学服务</wd-button
>
</view> -->
</view>
<view v-if="i.havePurchasedInspector" class="service">
<wd-button class="btn" :round="false" @click.stop="toMySupervisionService(i)"
>督学服务</wd-button
>
</template>
<!-- 我的设备 -->
<template v-else>
<view
v-for="(i, idx) in deviceList"
:key="idx"
class="equipment"
@click="handleDetailPage(activeNav.type, i)"
>
序列号<text class="serial">{{ i.simSerialNumber }}</text>
</view>
</view>
</template>
<!-- 我的设备 -->
<template v-else>
<view
v-for="(i, idx) in dataList"
:key="idx"
class="equipment"
@click="handleDetialPage(activeNav.type, i)"
>
序列号<text class="serial">{{ i.simSerialNumber }}</text>
</view>
</template>
</view>
</z-paging>
</template>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, nextTick } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getParentBindChildApi, getParentBindDeviceApi } from '@/api'
import { userStore } from '@/store'
import { storeToRefs } from 'pinia'
import { getCache } from '@/utils'
import router from '@/router/router'
import type { ChildrenType, DeviceType } from './interface'
import dayjs from 'dayjs'
import db from '@/utils/db'
const { userInfo } = storeToRefs(userStore())
const OSS_URL = import.meta.env.VITE_OSS_HOST
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
const empty = `${OSS_URL}/empty.png`
const nav_child = 1
const nav_device = 2
//
const activeNav = ref({
title: '我的孩子',
type: 1,
type: nav_child,
})
const navList = reactive([
{
title: '我的孩子',
type: 1,
type: nav_child,
},
{
title: '我的设备',
type: 2,
type: nav_device,
},
])
const dataList = ref<any[]>([])
const paging = ref(null)
const childList = ref<any[]>([])
const deviceList = ref<any[]>([])
function swicthNav(i) {
paging.value.reload(true)
const noData = computed(() => {
return (
(activeNav.value.type === nav_child && childList.value.length === 0) ||
(activeNav.value.type === nav_device && deviceList.value.length === 0)
)
})
function switchNav(i: any) {
activeNav.value = i
loadData()
}
const queryList = (pageNo: number, pageSize: number) => {
if (activeNav.value.type === 1) {
getParentBindChildApi({ parentId: userInfo.value.id, current: pageNo, size: pageSize })
const loadData = () => {
if (activeNav.value.type === nav_child) {
getParentBindChildApi({ parentId: userInfo.value.id, current: 1, size: 1000 })
.then(res => {
paging.value.complete(res.rows)
childList.value = res.rows || []
})
.catch(res => {
paging.value.complete(false)
childList.value = []
})
} else {
getParentBindDeviceApi({ parentId: userInfo.value.id, current: pageNo, size: pageSize })
getParentBindDeviceApi({ parentId: userInfo.value.id, current: 1, size: 1000 })
.then(res => {
paging.value.complete(res.rows)
deviceList.value = res.rows || []
})
.catch(res => {
paging.value.complete(false)
deviceList.value = []
})
}
}
//
function handleDetialPage(type: number, obj: any) {
function handleDetailPage(type: number, obj: any) {
let details = encodeURIComponent(JSON.stringify(obj))
switch (type) {
case 1:
@ -167,20 +164,21 @@ const toMySupervisionService = child => {
}
onShow(() => {
if (paging.value) {
paging.value.refresh()
}
loadData()
})
onMounted(() => {
loadData()
})
onMounted(() => {})
</script>
<style lang="scss" scoped>
.about_mine {
// .paging_box {
// height: calc(100vh - 532rpx - 120rpx - 52rpx);
// }
:deep(.zp-empty-view-center) {
display: block;
.container {
margin-top: 20px;
}
.scroll-container {
margin-top: 30rpx;
}
.empty_box {
display: flex;

View File

@ -1,5 +1,5 @@
<template>
<view class="page">
<mj-page class="page">
<BackBar title="绑定申请" />
<Empty v-if="bindApplyList?.length === 0" content="暂无申请数据哦~" />
<template v-else>
@ -85,67 +85,71 @@
</swiper-item>
</swiper>
</template>
</view>
</mj-page>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import type { BindApplyItemType } from '../interface';
import { adminApproval } from '@/api';
import { ref, onMounted, nextTick } from 'vue'
import type { BindApplyItemType } from '../interface'
import { adminApproval } from '@/api'
import { maskPhone, useRelation, debounce } from '@/hooks';
import Empty from '@/components/Empty/index.vue';
import { bindApplyStore } from '@/store';
import { storeToRefs } from 'pinia';
import { useRelation, debounce } from '@/hooks'
import Empty from '@/components/Empty/index.vue'
import { bindApplyStore } from '@/store'
import { storeToRefs } from 'pinia'
import hud from '@/utils/hud'
const { bindApplyList } = storeToRefs(bindApplyStore());
const { getApplyBindInfoList } = bindApplyStore();
const OSS_URL = import.meta.env.VITE_OSS_HOST;
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`;
const current = ref(0);
const { bindApplyList } = storeToRefs(bindApplyStore())
const { getApplyBindInfoList } = bindApplyStore()
const OSS_URL = import.meta.env.VITE_OSS_HOST
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
const current = ref(0)
const showData = ref<BindApplyItemType>();
const showData = ref<BindApplyItemType>()
async function handleBtnFn(e: boolean, id: string) {
adminApproval({
approvalFlag: e,
id: id,
}).then(async res => {
uni.showToast({
title: '提交成功!',
icon: 'none',
mask: true,
});
await getApplyBindInfoList();
if (current.value >= bindApplyList.value.length) {
current.value = bindApplyList.value.length > 0 ? bindApplyList.value.length - 1 : 0;
}
showData.value = bindApplyList.value.length > 0 ? bindApplyList.value[current.value - 1] : {};
});
const option = e ? '同意' : '拒绝'
hud.confirmLoad(`确定【${option}】绑定?`, {
task: async () => {
await adminApproval({
approvalFlag: e,
id: id,
})
await getApplyBindInfoList()
await nextTick()
if (current.value >= bindApplyList.value.length) {
current.value = bindApplyList.value.length > 0 ? bindApplyList.value.length - 1 : 0
}
showData.value = bindApplyList.value.length > 0 ? bindApplyList.value[current.value - 1] : {}
},
option,
hideSuccess: false,
})
}
const subFun = debounce(handleBtnFn, 500);
const subFun = debounce(handleBtnFn, 500)
function switchPage(type) {
switch (type) {
case 'pre':
if (current.value > 0) {
current.value -= 1;
showData.value = bindApplyList.value[current.value];
current.value -= 1
showData.value = bindApplyList.value[current.value]
}
break;
break
case 'next':
if (current.value < bindApplyList.value.length - 1) {
current.value += 1;
showData.value = bindApplyList.value[current.value];
current.value += 1
showData.value = bindApplyList.value[current.value]
}
break;
break
}
}
onMounted(async () => {
await getApplyBindInfoList();
await getApplyBindInfoList()
await nextTick()
showData.value = bindApplyList.value.length > 0 ? bindApplyList.value[0] : {};
});
showData.value = bindApplyList.value.length > 0 ? bindApplyList.value[0] : {}
})
</script>
<style lang="scss" scoped>

View File

@ -1,40 +1,35 @@
<template>
<view class="patriarch_list">
<z-paging
ref="paging"
v-model="dataList"
:paging-style="{ margin: `${top}rpx 30rpx 0 30rpx` }"
@query="queryList"
>
<template #top>
<view class="title">绑定家长</view>
</template>
<template v-slot:empty>
<view class="empty_box">
<view class="container" :style="{ margin: `${top}rpx 30rpx 0 30rpx` }">
<view class="title">绑定家长</view>
<scroll-view scroll-y class="scroll-container">
<view v-if="dataList.length === 0" class="empty_box">
<Empty content="暂无绑定家长数据哦~" />
</view>
</template>
<view v-for="(i, idx) in dataList" :key="idx" class="patriarch">
<view class="left">
<image
:src="i.parentAvatar ? i.parentAvatar : defaultAvatar"
mode=""
class="patriarch_avatar"
></image>
<view>
<view class="header">
<view class="name">{{ i.parentName }}</view>
<view class="tag">
<text v-if="i.adminFlag === 1" class="tag_item manager">管理员</text>
<text class="tag_item relation">{{ useRelation1[i.identityType] }}</text>
<view v-else class="content">
<view v-for="(i, idx) in dataList" :key="idx" class="patriarch">
<view class="left">
<image
:src="i.parentAvatar ? i.parentAvatar : defaultAvatar"
mode=""
class="patriarch_avatar"
></image>
<view>
<view class="header">
<view class="name">{{ i.parentName }}</view>
<view class="tag">
<text v-if="i.adminFlag === 1" class="tag_item manager">管理员</text>
<text class="tag_item relation">{{ useRelation1[i.identityType as keyof typeof useRelation1] }}</text>
</view>
</view>
<view class="phone"> {{ maskPhone(i.parentPhone) }} </view>
</view>
</view>
<view class="phone"> {{ maskPhone(i.parentPhone) }} </view>
<view class="right"><button class="unbind_btn" @click="handleUnbind(i as any)">解绑</button></view>
</view>
</view>
<view class="right"><button class="unbind_btn" @click="handleUnbind(i)">解绑</button></view>
</view>
</z-paging>
</scroll-view>
</view>
<CustomPopup
v-model="showCustomPopup"
titleText="温馨提示"
@ -76,31 +71,30 @@ const OSS_URL = import.meta.env.VITE_OSS_HOST
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
const showCustomPopup = ref(false)
const unbind = ref<ChildrenType | DeviceType>()
const dataList = ref<ChildrenType[] | DeviceType[]>()
const paging = ref(null)
const dataList = ref<ChildrenType[] | DeviceType[]>([])
const detailInfo = ref<ChildrenType & DeviceType>()
const queryList = (pageNo: number, pageSize: number) => {
const loadData = () => {
if (props.type === 1) {
getParentBindChildApi({ childId: detailInfo.value.childId, current: pageNo, size: pageSize })
getParentBindChildApi({ childId: detailInfo.value.childId, current: 1, size: 1000 })
.then(res => {
paging.value.complete(res.rows)
dataList.value = res.rows || []
})
.catch(res => {
paging.value.complete(false)
dataList.value = []
})
} else {
getParentBindDeviceApi({
simSerialNumber: detailInfo.value.simSerialNumber,
current: pageNo,
size: pageSize,
current: 1,
size: 1000,
})
.then(res => {
paging.value.complete(res.rows)
dataList.value = res.rows || []
})
.catch(res => {
paging.value.complete(false)
dataList.value = []
})
}
}
@ -116,25 +110,40 @@ async function handleConfirmPopup() {
})
if (props.type === 1) {
await childUnBoundParentApi({
parentId: unbind.value.parentId,
parentId: (unbind.value as any).parentId,
childId: detailInfo.value.childId,
})
} else {
await parentUnBoundDeviceApi({
parentId: unbind.value.parentId,
parentId: (unbind.value as any).parentId,
simSerialNumber: detailInfo.value.simSerialNumber,
})
}
uni.hideLoading()
paging.value.reload()
loadData()
showCustomPopup.value = false
}
onLoad(option => {
detailInfo.value = JSON.parse(decodeURIComponent(option.details))
loadData()
})
</script>
<style lang="scss" scoped>
.patriarch_list {
height: 100vh;
.container {
height: calc(100vh - 445rpx);
display: flex;
flex-direction: column;
}
.scroll-container {
flex: 1;
height: 0;
margin-top: 20rpx;
}
.empty_box {
.empty {
margin-top: 0 !important;

View File

@ -1,36 +1,21 @@
<template>
<view class="content">
<PatriarchCard />
<AbountMine />
<AboutMine />
</view>
</template>
<script setup lang="ts">
import { onShow } from '@dcloudio/uni-app'
import { bindApplyStore } from '@/store'
import db from '@/utils/db'
import PatriarchCard from './patriarchCard.vue';
import AbountMine from './aboutMine.vue';
import PatriarchCard from './patriarchCard.vue'
import AboutMine from './aboutMine.vue'
import { badgeStore } from '@/store/badge'
const { getApplyBindInfoList } = bindApplyStore()
const { refreshBadge } = badgeStore()
onShow(async () => {
//
await getApplyBindInfoList()
const hasApplyMsg = db.get('applyBindMsg')
if (hasApplyMsg) {
uni.setTabBarBadge({
index: 3, // ""tabBar
text: '●'
})
} else {
uni.removeTabBarBadge({
index: 3
})
}
refreshBadge()
})
</script>

View File

@ -14,7 +14,9 @@
<view v-if="i.show" class="function_item" @click="handleJump(i)">
<view class="photo_box">
<image :src="i.photo" mode="" class="photo"></image>
<text v-if="i.title === '绑定申请' && getCache('applyBindMsg')" class="unread"></text>
<text v-if="i.id === id_apply && bindApplyList?.length" class="unread">
{{ bindApplyList?.length }}
</text>
</view>
<text class="title">{{ i.title }}</text>
</view>
@ -25,24 +27,25 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { userStore } from '@/store'
import { bindApplyStore, userStore } from '@/store'
import { storeToRefs } from 'pinia'
import { getCache } from '@/utils'
import router from '@/router/router'
import db from '@/utils/db'
const { userInfo } = storeToRefs(userStore())
const { bindApplyList } = storeToRefs(bindApplyStore())
const id_apply = 2
const OSS_URL = import.meta.env.VITE_OSS_HOST
const defaultAvatar = `${OSS_URL}/urm/default_avatar.png`
const list = ref([
// {
// title: '',
// photo: `${OSS_URL}/urm/my_scan_binding.png`,
// show: true,
// url: '/pages/home/bindDevice/index',
// },
{
title: '扫码绑定',
photo: `${OSS_URL}/urm/my_scan_binding.png`,
show: true,
url: '/pages/home/bindDevice/index',
},
{
id: id_apply,
title: '绑定申请',
photo: `${OSS_URL}/urm/my_binding_apply.png`,
show: true,
@ -153,14 +156,19 @@ onMounted(() => {})
}
.unread {
$unread-wh: 24rpx;
display: block;
position: absolute;
text-align: center;
line-height: $unread-wh;
right: 0;
top: 0;
width: 16rpx;
height: 16rpx;
width: $unread-wh;
height: $unread-wh;
font-size: $unread-wh - 6rpx;
background-color: rgba(255, 40, 40, 1);
border-radius: 50%;
color: white;
}
}

26
src/store/badge.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineStore } from 'pinia'
import { bindApplyStore } from './bindApply'
export const badgeStore = defineStore('badge', () => {
const { getApplyBindInfoList } = bindApplyStore()
async function refreshBadge() {
// 我的
const mineIndex = 2
const size = (await getApplyBindInfoList())?.length
if (size) {
uni.setTabBarBadge({
index: mineIndex,
text: `${size}`,
})
} else {
uni.removeTabBarBadge({
index: mineIndex,
})
}
}
return {
refreshBadge,
}
})

View File

@ -1,26 +1,24 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { setCache } from '@/utils'
import { applyBindInfoListApi } from '@/api'
import type { BindApplyItemType } from '@/pages/mine/interface'
import db from '@/utils/db'
export const bindApplyStore = defineStore('bindApplyStore', () => {
const bindApplyList = ref<BindApplyItemType[]>()
const applyBindMsg = ref(false)
// 未处理的绑定申请列表
const getApplyBindInfoList = async () => {
try {
const data = await applyBindInfoListApi()
bindApplyList.value = data
if (bindApplyList.value.length === 0) {
db.set('applyBindMsg', false)
} else {
db.set('applyBindMsg', true)
}
applyBindMsg.value = data?.length
return data
} catch (err) {
console.log(err)
}
}
return {
applyBindMsg,
bindApplyList,
getApplyBindInfoList,
}

View File

@ -41,23 +41,23 @@ function _toast(type: string, msg: string, duration = default_duration) {
})
}
export async function info(text: string, duration?: number) {
async function info(text: string, duration?: number) {
return _toast('info', text, duration)
}
export async function error(text: string, duration?: number) {
async function error(text: string, duration?: number) {
return _toast('error', text, duration)
}
export async function warn(text: string, duration?: number) {
async function warn(text: string, duration?: number) {
return _toast('warn', text, duration)
}
export async function success(text: string, duration?: number) {
async function success(text: string, duration?: number) {
return _toast('success', text, duration)
}
export async function primary(text: string, duration?: number) {
async function primary(text: string, duration?: number) {
return _toast('primary', text, duration)
}
@ -69,6 +69,17 @@ export function dialog(options: DialogOptions) {
dict.callFn(dialog_dict_key, options)
}
export function confirmLoad(content: any, options: LoadOptions) {
dialog({
title: '确认',
content,
ok: async (hide: any) => {
hide()
load(options)
},
})
}
export function hide() {
dict.callFn(hide_dict_key)
}
@ -136,4 +147,5 @@ export default {
primary,
hide,
dialog,
confirmLoad,
}