main
king 1 year ago
parent 2b9c256381
commit e33657c661
  1. 32
      src/api/meeting.ts
  2. 9
      src/api/request.ts
  3. 23
      src/components/button/MyButton.tsx
  4. 11
      src/components/button/myButton.module.scss
  5. 2
      src/components/popPut/popPut.tsx
  6. 4
      src/pages/manage/meetings/meetings.config.ts
  7. 43
      src/pages/manage/meetings/meetings.module.scss
  8. 74
      src/pages/manage/meetings/meetings.tsx
  9. 66
      src/pages/manage/spotMeeting/spotMeeting.module.scss
  10. 281
      src/pages/manage/spotMeeting/spotMeeting.tsx
  11. 4
      src/static/css/module.scss
  12. BIN
      src/static/img/code.png

@ -1,7 +1,33 @@
import {request} from "@/api/request";
export const meetingAPi={
qrcodeKey() {
return request('/api/v1/auth/login/qrcode/key', "GET")
export interface Meeting {
id: number
/** 部门ID */
department_id: number;
/** 现场会描述 */
description: string;
/** 预计结束时间 */
estimate_end_time: number;
/** 预计开始时间 */
estimate_start_time: number;
/** 现场会标题 */
name: string;
}
export const meetingAPi = {
setMeetings(data: Meeting) {
return request<{ id: number }>('/api/v1/meetings/meeting', "POST", data)
},
setMeeting(id: string) {
return request<Meeting>(`/api/v1/meetings/meeting/${id}`, "GET")
},
setList(page: number, page_size: number) {
return request<{data:Meeting[],total:number}>(`/api/v1/meetings/meeting?page=${page}&page_size=${page_size}`, "GET")
},
exists() {
return request<Meeting>('/api/v1/meetings/exists', "GET")
},
del(id:number){
return request(`/api/v1/meetings/meeting/${id}`,"DELETE")
}
}

@ -77,9 +77,9 @@ export function request<T = unknown>(
...option,
success(res) {
try {
const data = res.data as any
if (data?.code === 0 && res.statusCode === 200) {
resolve(data.data || [])
const data = res?.data as any
if (data?.code === 0 && res?.statusCode === 200) {
resolve(data?.data || [])
} else if (res.statusCode === 401) {
Taro.showModal({
title: "登录过期,需重新登陆",
@ -91,7 +91,7 @@ export function request<T = unknown>(
} else {
reject(null)
Taro.showToast({
title: data.msg || data.message || ERROR_STATUS[res.statusCode] || '请求错误~',
title: data?.msg || data?.message || ERROR_STATUS[res.statusCode] || '请求错误~',
icon: 'error'
})
}
@ -101,7 +101,6 @@ export function request<T = unknown>(
},
fail(err) {
const errMsg = err?.errMsg
console.log(err)
Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'})
reject(null)
}

@ -2,6 +2,7 @@ import {CSSProperties, FC} from "react";
import {Button, View} from "@tarojs/components";
import styles from './myButton.module.scss'
import {ButtonProps} from "@tarojs/components/types/Button";
import Loading from "@/components/loading";
interface Props extends ButtonProps {
fillet?: boolean
@ -11,8 +12,8 @@ interface Props extends ButtonProps {
const MyButton: FC<Props> = (props) => {
const buttonStyle = (): CSSProperties => {
const style: CSSProperties = {}
const buttonStyle = (): CSSProperties | string => {
let style: CSSProperties | string = {}
switch (props.type) {
case 'default':
style.background = '#f5f8f7'
@ -29,18 +30,30 @@ const MyButton: FC<Props> = (props) => {
style.width = props.width + "px"
}
if (props.style && typeof props.style === 'object') {
style = {
...style,
...props.style
}
}
return style
}
return (
<View className={styles.MybuttonBox}>
<Button
{...props}
loading={false}
style={buttonStyle()}
className={`${styles.Mybutton} ${props.className}`}>
{props.children}
</Button>
{
props.loading && <View className='mr-1'>
<Loading/>
</View>
}
<View>{props.children}</View>
</Button>
)
}
export default MyButton

@ -5,15 +5,10 @@
font-size: 32rpx;
border: none !important;
outline: none !important;
position: absolute;
margin: 0;
padding: 0;
box-sizing: border-box;
}
.MybuttonBox {
position: relative;
height: 76rpx;
line-height: 76rpx;
flex: 1;
display: flex;
justify-content:center;
align-items: center;
}

@ -7,7 +7,7 @@ import CustomPageContainer from "@/components/custom-page-container/custom-page-
interface Props {
height?: number | string
title: string
content?: string
content?: string | ReactNode
chevron?: boolean
image?: string
isProp?: boolean

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '见面会记录',
onReachBottomDistance: 30
})

@ -0,0 +1,43 @@
.meeting {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.title {
position: relative;
flex: 1;
width: 70%;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
overflow: hidden;
box-sizing: border-box;
}
.overdue {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
text-align: center;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
background: rgba(#000, .8);
}
.del {
border-radius: 10rpx;
background: #FF3A2F;
display: flex;
align-items: center;
color: #fff;
width: 100rpx;
justify-content: center;
margin-left: 20rpx;
}

@ -0,0 +1,74 @@
import {FC, useCallback, useEffect, useState} from "react";
import {View} from "@tarojs/components";
import {Meeting, meetingAPi} from "@/api";
import styles from './meetings.module.scss'
import Taro, {useReachBottom} from "@tarojs/taro";
import Empty from "@/components/empty/empty";
import {formatDate} from "@/utils/time";
const MeetingsConfig: FC = () => {
const [page, setPage] = useState(1)
const [meeting, setMeeting] = useState<Meeting[]>([])
const [total, setTotal] = useState(0)
const getData = useCallback(async () => {
try {
const res = await meetingAPi.setList(1, 10)
setTotal(res.total)
setMeeting([
...(meeting || []),
...res.data
])
} catch (e) {
}
}, [page])
function del(id: number, index: number) {
try {
Taro.showModal({
title: '确定删除',
async success({confirm}) {
if (confirm) {
await meetingAPi.del(id)
const oldMeeting: Meeting[] = JSON.parse(JSON.stringify(meeting))
oldMeeting.splice(index, 1)
setMeeting(oldMeeting)
}
}
})
} catch (e) {
}
}
useReachBottom(useCallback(() => {
if (meeting?.length < total) {
setPage(page + 1)
}
}, [total, meeting]))
useEffect(() => {
getData()
}, [page])
return (
<View className='p-2'>
{
meeting.length ?
meeting.map((d, index) => <View className={styles.meeting}>
<View className={styles.title}>
<View className='font-weight mb-1'>{d.name}</View>
<View>{formatDate(new Date(d.estimate_start_time), "MM-dd")} {formatDate(new Date(d.estimate_start_time), "MM-dd")}</View>
{Date.now() > d.estimate_end_time && <View className={styles.overdue}></View>}
</View>
<View className={styles.del} onClick={() => del(d.id, index)}></View>
</View>)
: <Empty name='无历史记录'/>
}
</View>
)
}
export default MeetingsConfig

@ -1,5 +1,67 @@
.page {
background: #fff;
background: #00D6AC;
min-height: 100vh;
padding: 20px;
padding: 40px;
position: absolute;
top: 0;
left: 0;
width: 100%;
box-sizing: border-box;
}
.box {
width: 100%;
padding: 60rpx 20rpx 20rpx 20rpx;
background: #fff;
box-sizing: border-box;
border-radius: 15rpx;
text-align: center;
}
.code {
width: 300rpx;
margin: 50rpx auto 70rpx;
}
.choice {
border-top: 1px dashed #ddd;
padding: 40rpx 0 0;
position: relative;
&:after {
content: '';
display: block;
background: #00D6AC;
width: 40rpx;
height: 40rpx;
border-radius: 100%;
position: absolute;
left: -40rpx;
top: -20rpx;
}
&:before {
content: '';
display: block;
background: #00D6AC;
width: 40rpx;
height: 40rpx;
border-radius: 100%;
position: absolute;
right: -40rpx;
top: -20rpx;
}
}
.button {
margin: 30rpx auto;
box-shadow: 0 0 10rpx rgba(#00d6ac, .5);
}
.buttonDel {
margin: 30rpx auto;
box-shadow: 0 0 10rpx rgba(#c94f4f, .5);
}

@ -1,82 +1,92 @@
import {FC, useCallback, useEffect, useState} from "react";
import {Image, Picker, View} from "@tarojs/components";
import styles from './spotMeeting.module.scss'
import Taro from "@tarojs/taro";
import {curriculum, meetingAPi} from "@/api";
import {Form, Image, Input, Picker, Textarea, View} from "@tarojs/components";
import Taro, {getSetting, authorize} from "@tarojs/taro";
import {curriculum, Meeting, meetingAPi} from "@/api";
import {formatDate} from "@/utils/time";
import {getSetting, authorize} from "@tarojs/taro";
import PopPut from "@/components/popPut/popPut";
import MyButton from "@/components/button/MyButton";
import styles from './spotMeeting.module.scss'
import code from '@/static/img/code.png'
import Icon from "@/components/icon";
const SpotMeeting: FC = () => {
const path = encodeURIComponent("/pages/meeting/meeting")
const params = Taro.getCurrentInstance().router?.params as { id: string | undefined }
const [manages, setManages] = useState<Manage[]>([])
const [start, setStart] = useState<string>(formatDate(new Date(), "YY-MM-dd hh:00:00"))
const [end, setEnd] = useState<string>(formatDate(new Date(), "YY-MM-dd 18:00:00"))
const [tempFilePath, setTempFilePath] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null)
const [depid, setDepid] = useState<number | null>(null)
const [isDownloading, setDownloading] = useState(false)
const change = useCallback((e) => {
setDepid(() => manages[Number(e.detail.value)]?.id)
}, [manages])
const [imgUrl, setImgUrl] = useState('')
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [loading, setLoading] = useState(false)
useEffect(() => {
curriculum.department().then(res => {
setManages(res.data)
})
meetingAPi.qrcodeKey().then((res: any) => {
if (res.dep_id && res.end_time && res.start_time) {
setDepid(res.dep_id)
setEnd(formatDate(new Date(res.end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.start_time), "YY-MM-dd 08:00:00"))
setLoading(true)
if (params.id != undefined) {
meetingAPi.setMeeting(params.id).then(res => {
if (!Array.isArray(res)) {
setDepid(res.department_id)
setName(res.name)
setDescription(res.description)
setEnd(formatDate(new Date(res.estimate_end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.estimate_start_time), "YY-MM-dd 08:00:00"))
downUrl(res.id, false)
} else {
setLoading(false)
}
})
}, [])
useEffect(() => {
if (!depid || isDownloading) {
return
} else {
meetingAPi.exists().then(res => {
if (!Array.isArray(res)) {
setDepid(res.department_id)
setName(res.name)
setDescription(res.description)
setEnd(formatDate(new Date(res.estimate_end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.estimate_start_time), "YY-MM-dd 08:00:00"))
downUrl(res.id, false)
} else {
setLoading(false)
}
})
}
}, [])
Taro.showLoading()
setDownloading(true)
const startTime = new Date(start).getTime()
const endTime = new Date(end).getTime()
const path = encodeURIComponent("/pages/meeting/meeting")
const qrcodeUrl = `${process.env.TARO_APP_API}/official/qrcode?depid=${depid}&start_time=${startTime}&end_time=${endTime}&path=${path}&t=${Date.now()}`
const change = useCallback((e) => {
setDepid(() => manages[Number(e.detail.value)]?.id)
}, [manages])
if (process.env.TARO_ENV === 'h5') {
setTempFilePath(qrcodeUrl)
setDownloading(false)
const change_start = useCallback((time: string) => {
const mil = new Date(time).getTime()
const endMil = new Date(end).getTime()
if (mil > endMil) {
Taro.showToast({title: '大于结束时间', icon: 'error'})
} else {
Taro.downloadFile({
url: qrcodeUrl,
success(res) {
setTempFilePath(res.tempFilePath)
},
fail() {
setError("请求失败");
},
complete() {
setDownloading(false)
setStart(time)
}
})
}
Taro.hideLoading()
}, [depid, end, start])
}, [end])
const change_end = useCallback((time: string) => {
const mil = new Date(time).getTime()
const startMil = new Date(start).getTime()
if (mil < startMil) {
Taro.showToast({title: '小于开始时间', icon: 'error'})
} else {
setEnd(time)
}
}, [start])
const handleWriteFile = useCallback(() => {
if (tempFilePath == null) {
if (imgUrl == null) {
Taro.showToast({title: '下载失败', icon: 'error'})
return
}
Taro.saveImageToPhotosAlbum({
filePath: tempFilePath,
filePath: imgUrl,
success() {
Taro.showModal({title: '下载成功'})
},
@ -84,12 +94,11 @@ const SpotMeeting: FC = () => {
Taro.showToast({title: '下载失败', icon: 'error'})
}
})
}, [tempFilePath])
}, [imgUrl])
const handleSaveCode = useCallback(() => {
if (process.env.TARO_ENV === 'h5') {
Taro.showToast({title: '请截屏', icon: 'error'})
Taro.showToast({title: '长按保存二维码', icon: 'error'})
} else {
getSetting({
success: function ({authSetting}) {
@ -108,41 +117,165 @@ const SpotMeeting: FC = () => {
},
});
}
}, [tempFilePath]);
}, [imgUrl]);
const downUrl = (id: number, down = true) => {
Taro.showLoading({title: '加载二维码'})
const url = process.env.TARO_APP_API + '/official/qrcode?' + 'meeting_id=' + id + '&path=' + path
if (process.env.TARO_ENV !== 'h5') {
Taro.downloadFile({
url,
success(res) {
setImgUrl(res.tempFilePath)
if (down) {
handleSaveCode()
}
},
fail() {
Taro.showModal({title: '二维码生成失败'})
},
complete() {
setLoading(false)
Taro.hideLoading()
}
})
} else {
setTimeout(() => {setImgUrl(url)})
if (down) {handleSaveCode()}
Taro.hideLoading()
setLoading(false)
}
}
async function submit(e) {
if (imgUrl) { // 已有
handleSaveCode()
return
}
const values: Meeting = e.detail.value
for (const [key, value] of Object.entries(values)) {
if (!value && key !== 'description') {
Taro.showToast({title: '请认真填写', icon: 'error'})
return
}
}
if (!depid) {
Taro.showToast({title: '请选择部门', icon: 'error'})
return
}
setLoading(true)
try {
const res = await meetingAPi.setMeetings({
...values,
estimate_end_time: new Date(end).getTime(),
estimate_start_time: new Date(start).getTime(),
department_id: depid
})
downUrl(res.id)
} catch (e) {
}
setLoading(false)
}
function jumpMeeings() {
Taro.navigateTo({url: '/pages/manage/meetings/meetings'})
}
function imageOnLoad() {
Taro.hideLoading()
}
useEffect(() => {
if (imgUrl) {
setImgUrl('')
}
}, [start, end, name, depid, description])
return (
<View className={styles.page}>
<Picker mode="date" value={start} onChange={(e) => setStart(e.detail.value + ' 8:00:00')} name='start'>
<PopPut title='开始时间' content={start}/>
</Picker>
<View className={styles.box}>
<View></View>
<View className={styles.code}>
{
imgUrl
? <Image src={imgUrl} mode='aspectFit' onLoad={imageOnLoad}/>
: <Image src={code} mode='aspectFit'/>
}
</View>
<Picker mode="date" value={end} onChange={(e) => setEnd(e.detail.value + ' 18:00:00')} name='end'>
<View className={styles.choice}>
<Form onSubmit={submit}>
<PopPut
title='会议标题'
content={<Input
className='input'
name='name'
value={name}
disabled={!!params.id}
placeholder='请输入会议标题'
onInput={(e) => setName(e.detail.value)}
/>}
chevron
/>
<Picker
mode="date"
value={start}
disabled={!!params.id}
onChange={(e) => change_start(e.detail.value + ' 8:00:00')}
name='estimate_start_time'>
<PopPut title='开始时间' content={start}/>
</Picker>
<Picker
mode="date"
value={end}
disabled={!!params.id}
onChange={(e) => change_end(e.detail.value + ' 18:00:00')}
name='estimate_end_time'>
<PopPut title='结束时间' content={end}/>
</Picker>
<View>
<Picker mode='selector' range={manages} onChange={change} rangeKey='name'>
<PopPut title='部门' content={manages?.find(x => x.id == depid)?.name}/>
<Picker
mode='selector'
range={manages}
onChange={change}
disabled={!!params.id}
rangeKey='name'
name='department_id'>
<PopPut title='选择部门' content={manages?.find(x => x.id == depid)?.name}/>
</Picker>
</View>
{tempFilePath && <View className='text-center'>
<Image
src={tempFilePath}
mode='widthFix'
onLoad={() => setError(null)}
onError={(e) => setError(e.detail.errMsg)}
style={{width: '80%'}}
/>
{error && <View>{error}</View>}
<Textarea
value={description}
className='Textarea'
disabled={!!params.id}
placeholder='请输入描述'
maxlength={255}
name='description'
onInput={(e) => setDescription(e.detail.value)}
style={{height: '60px'}}>
</Textarea>
<MyButton
formType='submit'
fillet
width={200}
className={styles.button}
loading={loading}>
</MyButton>
{process.env.TARO_ENV !== 'h5' && <MyButton onClick={handleSaveCode}></MyButton>}
{process.env.TARO_ENV === 'h5' && <View></View>}
</Form>
</View>
<View className='text-center text-muted my-3' onClick={jumpMeeings}> <Icon name='chevron-right'/></View>
</View>
</View>}
</View>
)
}

@ -11,6 +11,10 @@ page,
taro-button-core::after {
border: none !important;
}
body {
font-size: 32rpx;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Loading…
Cancel
Save