学员详情(删除,修改,权限,学习记录)

main
king 1 year ago
parent 7947e62663
commit 586a1ba696
  1. 1
      .env
  2. 1
      .env.playedu
  3. 2
      project.config.json
  4. 2
      project.tt.json
  5. 4
      src/api/manage.ts
  6. 13
      src/api/user.ts
  7. 1
      src/app.config.ts
  8. 2
      src/app.scss
  9. 39
      src/components/lineChart/lineChart.module.scss
  10. 43
      src/components/lineChart/lineChart.tsx
  11. 19
      src/pages/business/videoInfo/components/catalogue.tsx
  12. 4
      src/pages/business/videoInfo/videoInfo.scss
  13. 3
      src/pages/login/login.tsx
  14. 74
      src/pages/manage/depAdmin/depAdmin.tsx
  15. 42
      src/pages/manage/userInfo/components/info.tsx
  16. 3
      src/pages/manage/userInfo/userInfo.config.ts
  17. 51
      src/pages/manage/userInfo/userInfo.module.scss
  18. 140
      src/pages/manage/userInfo/userInfo.tsx
  19. 58
      src/utils/time.ts

@ -2,3 +2,4 @@
TARO_APP_API=https://playedu.yaojiankang.top
TARO_APP_LGOIN=true

@ -1 +1,2 @@
TARO_APP_API=https://playedu.yaojiankang.top
TARO_APP_LGOIN=false

@ -2,7 +2,7 @@
"miniprogramRoot": "./dist",
"projectname": "video",
"description": "",
"appid": "wxd8ae2141559dcf5b",
"appid": "wx703940a70f0f1be7",
"setting": {
"urlCheck": true,
"es6": false,

@ -2,7 +2,7 @@
"miniprogramRoot": "Progress/",
"projectname": "video",
"description": "",
"appid": "wxd8ae2141559dcf5b",
"appid": "wx703940a70f0f1be7",
"setting": {
"urlCheck": true,
"es6": false,

@ -80,11 +80,11 @@ export const ManageApi = {
putUser(id: number, data: Student) {
return request(`/api/v1/user/front/${id}`, "PUT", data)
},
del(id: number) {
del(id: string) {
return request(`/api/v1/user/${id}`, "DELETE")
},
/** 修改学员类型 */
setRoleType(id: number, data: setRoleTypeData) {
setRoleType(id: string, data: setRoleTypeData) {
return request(`/api/v1/user/${id}`, "PUT", data)
},
/** 部门 */

@ -46,6 +46,11 @@ interface HourCourse {
duration: Record<number, number>
}
export interface StatisticsParam {
start_time: number
end_time: string
}
export const userApi = {
login(code: string) {
return request<LoginData>('/api/v1/auth/login/wechat', 'POST', {code})
@ -81,6 +86,12 @@ export const userApi = {
},
meetingSave(data: any) {
return request<LoginData>(`/api/v1/user/meeting/save`, "POST", data)
},
info(user_id: string) {
return request<User>(`/api/v1/statistics/${user_id}`, "GET")
},
/**获取指定学员指定时间学习记录 */
statistics(user_id: string, data: StatisticsParam) {
return request<{data:Record<number, number>}>(`/api/v1/statistics/statistics/${user_id}?start_time=${data.start_time}&end_time=${data.end_time}`, "GET")
}
}

@ -64,6 +64,7 @@ export default defineAppConfig({
'spotMeeting/spotMeeting',
'selectDep/selectDep',
'meetings/meetings',
'userInfo/userInfo',
]
}
],

@ -68,7 +68,7 @@
.mt-1 {margin-top: 10rpx}
.mt-2 {margin-top: 20rpx}
.mt-3 {margin-top: 30rpx}
.mt-3 {margin-top: 30rpx !important;}
.mt-4 {margin-top: 40rpx}
.mt-5 {margin-top: 50rpx}
.mt-6 {margin-top: 60rpx}

@ -0,0 +1,39 @@
.lineChart {
display: flex;
align-items: flex-end;
justify-content: left;
flex-wrap: nowrap;
height: 400px;
}
.columnBox {
display: flex;
flex-direction: column;
align-items: center;
}
.column {
width: 30rpx;
background: linear-gradient(180deg, #03D9B3 0%, #05BF88 100%);
border-radius: 30rpx;
margin-bottom: 20rpx;
overflow: hidden;
animation: rise 300ms ease-in-out forwards;
max-height: 0;
}
.line {
width: 1rpx;
background: #ddd;
height: 100%;
margin-bottom: 10px;
}
@keyframes rise {
from {
max-height: 0;
}
to {
max-height: 300rpx;
}
}

@ -0,0 +1,43 @@
import {ScrollView, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import style from './lineChart.module.scss'
export interface lineData {
time: string
value: number
}
interface Props {
data: lineData[]
}
const height = 180
const LineChart: FC<Props> = ({data}) => {
const [maxHeight, setMaxHeight] = useState(0)
const [lineChartList, setLineChartList] = useState(data)
useEffect(() => {
setLineChartList(data)
setMaxHeight(data.reduce((pre, cur) => {
return Math.max(pre, cur.value)
}, 0))
}, [data])
return (
<ScrollView scrollX>
<View className={style.lineChart}>
{
lineChartList.map(d => <View key={d.time}>
<View className={style.columnBox} style={{width: "100px"}}>
<View className={style.line} style={{height: height - 10 - (d.value / maxHeight * height) + "px"}}></View>
<View className={style.column} style={{height: d.value / maxHeight * height + "px"}}> </View>
<View>{d.time}</View>
</View>
</View>)
}
</View>
</ScrollView>
)
}
export default LineChart

@ -29,7 +29,9 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
const [show, setShow] = useState(false)
function jumCurHistory() {
Taro.navigateTo({url: `/pages/business/hourHistory/hourHistory?courseId=${id}&hourId=${playId}`})
if (playId) {
Taro.navigateTo({url: `/pages/business/hourHistory/hourHistory?courseId=${id}&hourId=${playId}`})
}
}
function tabChange({tab}: OnChangOpt) {
@ -94,12 +96,14 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
}
}
return (
<>
<View className='catalogue'>
<Tabs tabList={tabList} onChange={tabChange} current={current}/>
<View className='py-2 hours'>
{current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>}
{current === 0 && <View className='short_desc'>{data?.course.short_desc || data?.course.title}</View>}
{current === 1 && <View>
<View className='font-weight'></View>
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View>
@ -134,12 +138,11 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
<Image src={curRecord} className='image'/>
</View>
{
playId != null && <View onClick={jumCurHistory}>
<Image src={hourRecord} className='image'/>
</View>
}
<View onClick={jumCurHistory} className={playId ? undefined : 'filter-saturate'}>
<Image src={hourRecord} className='image'/>
</View>
</View>

@ -100,3 +100,7 @@
}
}
.filter-saturate {
filter: saturate(0);
}

@ -180,7 +180,6 @@ const Login: FC = () => {
Taro.reLaunch({url: '/pages/index/index'})
}
return (
<View className={styles.container}>
<CustomPageContainer show={!!validateCode} position='bottom' onBeforeLeave={() => setCode(null)}>
@ -208,7 +207,7 @@ const Login: FC = () => {
</View>
</MyButton>
{process.env.TARO_APP_LGOIN && <MyButton onClick={TESTLOGIN}>线</MyButton>}
{process.env.TARO_APP_LGOIN === 'true' && <MyButton onClick={TESTLOGIN}>线</MyButton>}
</View>
)
}

@ -87,81 +87,11 @@ const DepAdmin: FC = () => {
})
}
function delUser(id: number) {
Taro.showModal({
title: '是否确认删除',
async success({confirm}) {
if (confirm) {
await ManageApi.del(id)
Taro.showToast({title: '删除成功'})
await getData()
}
}
})
}
function userManagesSheet(user: User) {
const itemList = [
'修改学员',
'删除学员',
"学习记录",
]
if (user.role_type === 1) {
itemList.push('取消管理员')
} else if (user.role_type === 0) {
itemList.push('设置为管理员')
}
Taro.showActionSheet({
itemList,
success({tapIndex}) {
switch (tapIndex) {
case 0:
Taro.navigateTo({url: "/pages/manage/addStudent/addStudent" + (user.id ? `?id=${user.id}` : '')})
break
case 1:
delUser(user.id)
break
case 2:
Taro.navigateTo({url: `/pages/manage/college/college?id=${user.id}&name=${user.name}`})
break
case 3:
setRoleType(user)
break
}
},
fail() {
}
})
}
function jumpAddStudent() {
Taro.navigateTo({url: '/pages/manage/addStudent/addStudent'})
}
function setRoleType(user: User) {
if (user.role_type === 2) {
Taro.showModal({title: "禁止修改超级管理员"})
return
}
const type = user.role_type === 0 ? 1 : 0
Taro.showModal({
title: "设置为" + ['学员', '管理员'][type],
async success({confirm}) {
if (confirm) {
try {
Taro.showLoading()
await ManageApi.setRoleType(user.id, {auth_id: user?.id!, role_type: type})
Taro.hideLoading()
Taro.showToast({title: "设置成功"})
await getData()
} catch (e) {
}
}
}
})
}
async function addDep() {
if (!depName) {
Taro.showToast({title: "请认真填写", icon: "error"})
@ -192,7 +122,7 @@ const DepAdmin: FC = () => {
useEffect(() => {
getData()
Taro.setNavigationBarTitle({
title: decodeURI(params.name) || '部门管理'
title: decodeURI(params.name) ?? '部门管理'
})
}, [])
@ -212,7 +142,7 @@ const DepAdmin: FC = () => {
key={d.id}
leftImage={d.avatar}
title={d.name}
onClick={() => userManagesSheet(d)}
onClick={() => Taro.navigateTo({url: '/pages/manage/userInfo/userInfo?userId=' + d.id})}
content={['学员', '管理员', '超级管理员'][d.role_type]}
/>)}

@ -0,0 +1,42 @@
import {FC} from "react";
import {Image, Text, View} from "@tarojs/components";
import styles from '../userInfo.module.scss'
interface Props {
data: User | null
}
const Info: FC<Props> = ({data}) => {
return (
<>
<View className={styles.box}>
<Image src={data?.avatar || ''} className={styles.image}/>
<View className='ml-2'>
<View>
<Text className='font-weight'>{data?.name}</Text>
<Text
className={data?.is_lock ? styles.tag_muted : styles.tag}>
{data?.is_lock ? '禁用' : '正常'}
</Text>
</View>
<View className='text-muted font-24 mt-2'>{['学员', '管理员', '超级管理员'][data?.role_type || 0]}</View>
</View>
</View>
<View className={`${styles.box} mt-3`} style={{display: 'block'}}>
<View className={styles.information} style={{borderBottom: "1px solid #F5F8F7"}}>
<Text style={{width: '100px', display: 'inline-block'}}></Text>
<Text>{data?.id}</Text>
</View>
<View className={styles.information}>
<Text style={{width: '100px', display: 'inline-block'}}></Text>
<Text>{data?.phone_number}</Text>
</View>
</View>
</>
)
}
export default Info

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '学员详情',
})

@ -0,0 +1,51 @@
.page {
padding: 15rpx;
}
.box {
background: #fff;
border-radius: 20rpx;
padding: 20rpx;
display: flex;
align-items: center;
box-sizing: border-box;
line-height: 1.75;
}
.image {
width: 120rpx;
height: 120rpx;
background: #ddd;
border-radius: 10rpx;
overflow: hidden;
}
.tag {
background: #00D6AC;
font-size: 24rpx;
color: #fff;
margin-left: 20rpx;
padding: 3rpx 10rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
.tag_muted {
background: #909795;
font-size: 24rpx;
color: #fff;
margin-left: 20rpx;
padding: 3rpx 10rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
.information {
width: 100%;
color: #606563;
padding: 20rpx 0;
}
.total{
text-align: center;
}

@ -0,0 +1,140 @@
import {Text, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react";
import styles from './userInfo.module.scss'
import Taro from "@tarojs/taro";
import Info from "@/pages/manage/userInfo/components/info";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import MyButton from "@/components/button/MyButton";
import {ManageApi, StatisticsParam, userApi} from "@/api";
import {Profile} from "@/store";
import {everyDay, getMonday, getSunday, monthEnd, monthFirst} from "@/utils/time";
import LineChart from "@/components/lineChart/lineChart";
const tabList: TabList<any>[] = [
{
title: '日',
value: {
start_time: new Date().setHours(0, 0, 0, 0),
end_time: new Date().setHours(24, 0, 0, 0)
}
},
{
title: '周', value: {
start_time: getMonday(),
end_time: getSunday()
}
},
{
title: '月', value: {
start_time: monthFirst(),
end_time: monthEnd()
}
},
]
const UserInfo: FC = () => {
const {userId} = Taro.getCurrentInstance().router?.params as { userId: string }
const [data, setData] = useState<User | null>(null)
const [lineData, setLineData] = useState<any[]>([])
const {user} = Profile.useContainer()
function setRoleType() {
if (!data) return;
if (data.role_type === 2) {
Taro.showModal({title: "禁止修改超级管理员"})
return
}
const type = data.role_type === 0 ? 1 : 0
Taro.showModal({
title: "设置为" + ['学员', '管理员'][type],
async success({confirm}) {
if (confirm) {
try {
await ManageApi.setRoleType(userId, {auth_id: user?.id!, role_type: type})
Taro.showToast({title: "设置成功"})
setData({
...data,
role_type: type
})
} catch (e) {
}
}
}
})
}
function delUser() {
Taro.showModal({
title: '是否确认删除',
async success({confirm}) {
if (confirm) {
await ManageApi.del(userId)
Taro.showToast({title: '删除成功'})
Taro.navigateBack()
}
}
})
}
async function getStatistics(data: StatisticsParam) {
try {
const res = await userApi.statistics(userId, data)
const everyDayValue = everyDay(data.start_time, Number(data.end_time), res.data)
setLineData(everyDayValue)
} catch (e) {
}
}
function tabChange({tab}: OnChangOpt<StatisticsParam>) {
getStatistics(tab?.value! as StatisticsParam)
}
useEffect(() => {
getStatistics(tabList[0].value)
userApi.info(userId).then(res => {
setData(res)
})
}, [])
return (
<View className={styles.page}>
<Info data={data}/>
<View className={`${styles.box} mt-3`} style={{display: 'block'}}>
<Tabs tabList={tabList} onChange={tabChange}/>
<View className='font-weight font-36 mt-5 mb-3'>
<Text style={{margin: '0 10px', color: '#00D6AC'}}>121212</Text>
</View>
<LineChart data={lineData}/>
</View>
<View className='mt-5'>
{
data?.role_type !== 2
&& <MyButton onClick={setRoleType}>{['设置为管理员', '设置为学员'][data?.role_type || 0]}</MyButton>
}
<MyButton
type='default'
style={{background: '#fff'}}
onClick={() => Taro.navigateTo({url: "/pages/manage/addStudent/addStudent?id=" + userId})}
className={'mt-3'}>
</MyButton>
<MyButton
type='default'
style={{background: '#fff', color: 'red'}}
className={'mt-3'}
onClick={delUser}>
</MyButton>
</View>
</View>
)
}
export default UserInfo

@ -4,6 +4,8 @@
* 20:20:20
* 10:10
* */
import {lineData} from "@/components/lineChart/lineChart";
export function formatMinute(s: number | string): string {
const time = Number(s)
if (isNaN(time)) {
@ -62,3 +64,59 @@ export function formatDate(date, format) {
.replace(/mm/g, preArr[min] || min)
.replace(/ss/g, preArr[sec] || sec);
}
export function getMonday(): number {
const now = new Date();
const nowTime = now.setHours(0, 0, 0, 0);
const day = now.getDay() || 7; //为周日的时候 day 修改为7 否则当天周天会有问题
const oneDayTime = 24 * 60 * 60 * 1000;
return nowTime - (day - 1) * oneDayTime;//显示周一
}
export function getSunday(): number {
const now = new Date();
const nowTime = now.setHours(0, 0, 0, 0);
const day = now.getDay() || 7; //为周日的时候 day 修改为7 否则当天周天会有问题
const oneDayTime = 24 * 60 * 60 * 1000;
return nowTime + (7 - day) * oneDayTime
}
export function monthFirst(): number {
const data = new Date();
data.setDate(1);
return data.setHours(0, 0, 0, 0)
}
export function monthEnd(): number {
const data = new Date();
if (data.getMonth() === 11) {
data.setMonth(0);
} else {
data.setMonth(data.getMonth() + 1);
}
data.setDate(1);
data.setHours(0);
data.setSeconds(0);
data.setMinutes(0);
return (parseInt(String(data.getTime() / 1000)) - 1) * 1000;
}
export function everyDay(start_time: number, end_time: number, data: Record<number, number>): lineData[] {
const time = 86400000
try {
const days = Math.floor((end_time - start_time) / time)
if (isNaN(days)) return [];
return new Array(days === 1 ? 1 : days + 1).fill(0).map((_, index) => {
const day = start_time + index * time
return {
time: formatDateTime(new Date(day), 'MM-dd'),
value: data?.[String(day)] || 0
}
})
} catch (e) {
return []
}
}

Loading…
Cancel
Save