main
king 1 year ago
parent 2b9c256381
commit e33657c661
  1. 32
      src/api/meeting.ts
  2. 9
      src/api/request.ts
  3. 33
      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. 289
      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"; import {request} from "@/api/request";
export const meetingAPi={ export interface Meeting {
qrcodeKey() { id: number
return request('/api/v1/auth/login/qrcode/key', "GET") /** 部门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, ...option,
success(res) { success(res) {
try { try {
const data = res.data as any const data = res?.data as any
if (data?.code === 0 && res.statusCode === 200) { if (data?.code === 0 && res?.statusCode === 200) {
resolve(data.data || []) resolve(data?.data || [])
} else if (res.statusCode === 401) { } else if (res.statusCode === 401) {
Taro.showModal({ Taro.showModal({
title: "登录过期,需重新登陆", title: "登录过期,需重新登陆",
@ -91,7 +91,7 @@ export function request<T = unknown>(
} else { } else {
reject(null) reject(null)
Taro.showToast({ Taro.showToast({
title: data.msg || data.message || ERROR_STATUS[res.statusCode] || '请求错误~', title: data?.msg || data?.message || ERROR_STATUS[res.statusCode] || '请求错误~',
icon: 'error' icon: 'error'
}) })
} }
@ -101,7 +101,6 @@ export function request<T = unknown>(
}, },
fail(err) { fail(err) {
const errMsg = err?.errMsg const errMsg = err?.errMsg
console.log(err)
Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'}) Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'})
reject(null) reject(null)
} }

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

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

@ -7,7 +7,7 @@ import CustomPageContainer from "@/components/custom-page-container/custom-page-
interface Props { interface Props {
height?: number | string height?: number | string
title: string title: string
content?: string content?: string | ReactNode
chevron?: boolean chevron?: boolean
image?: string image?: string
isProp?: boolean 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 { .page {
background: #fff; background: #00D6AC;
min-height: 100vh; 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 {FC, useCallback, useEffect, useState} from "react";
import {Image, Picker, View} from "@tarojs/components"; import {Form, Image, Input, Picker, Textarea, View} from "@tarojs/components";
import styles from './spotMeeting.module.scss' import Taro, {getSetting, authorize} from "@tarojs/taro";
import Taro from "@tarojs/taro"; import {curriculum, Meeting, meetingAPi} from "@/api";
import {curriculum, meetingAPi} from "@/api";
import {formatDate} from "@/utils/time"; import {formatDate} from "@/utils/time";
import {getSetting, authorize} from "@tarojs/taro";
import PopPut from "@/components/popPut/popPut"; import PopPut from "@/components/popPut/popPut";
import MyButton from "@/components/button/MyButton"; 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 SpotMeeting: FC = () => {
const path = encodeURIComponent("/pages/meeting/meeting")
const params = Taro.getCurrentInstance().router?.params as { id: string | undefined }
const [manages, setManages] = useState<Manage[]>([]) const [manages, setManages] = useState<Manage[]>([])
const [start, setStart] = useState<string>(formatDate(new Date(), "YY-MM-dd hh:00:00")) 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 [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 [depid, setDepid] = useState<number | null>(null)
const [isDownloading, setDownloading] = useState(false) const [imgUrl, setImgUrl] = useState('')
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const change = useCallback((e) => { const [loading, setLoading] = useState(false)
setDepid(() => manages[Number(e.detail.value)]?.id)
}, [manages])
useEffect(() => { useEffect(() => {
curriculum.department().then(res => { curriculum.department().then(res => {
setManages(res.data) setManages(res.data)
}) })
meetingAPi.qrcodeKey().then((res: any) => { setLoading(true)
if (res.dep_id && res.end_time && res.start_time) { if (params.id != undefined) {
setDepid(res.dep_id) meetingAPi.setMeeting(params.id).then(res => {
setEnd(formatDate(new Date(res.end_time), "YY-MM-dd 18:00:00")) if (!Array.isArray(res)) {
setStart(formatDate(new Date(res.start_time), "YY-MM-dd 08:00:00")) 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"))
useEffect(() => { downUrl(res.id, false)
if (!depid || isDownloading) { } else {
return setLoading(false)
}
})
} 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() const change = useCallback((e) => {
setDownloading(true) setDepid(() => manages[Number(e.detail.value)]?.id)
const startTime = new Date(start).getTime() }, [manages])
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()}`
if (process.env.TARO_ENV === 'h5') { const change_start = useCallback((time: string) => {
setTempFilePath(qrcodeUrl) const mil = new Date(time).getTime()
setDownloading(false) const endMil = new Date(end).getTime()
if (mil > endMil) {
Taro.showToast({title: '大于结束时间', icon: 'error'})
} else { } else {
Taro.downloadFile({ setStart(time)
url: qrcodeUrl,
success(res) {
setTempFilePath(res.tempFilePath)
},
fail() {
setError("请求失败");
},
complete() {
setDownloading(false)
}
})
} }
Taro.hideLoading() }, [end])
}, [depid, end, start])
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(() => { const handleWriteFile = useCallback(() => {
if (tempFilePath == null) { if (imgUrl == null) {
Taro.showToast({title: '下载失败', icon: 'error'}) Taro.showToast({title: '下载失败', icon: 'error'})
return return
} }
Taro.saveImageToPhotosAlbum({ Taro.saveImageToPhotosAlbum({
filePath: tempFilePath, filePath: imgUrl,
success() { success() {
Taro.showModal({title: '下载成功'}) Taro.showModal({title: '下载成功'})
}, },
@ -84,12 +94,11 @@ const SpotMeeting: FC = () => {
Taro.showToast({title: '下载失败', icon: 'error'}) Taro.showToast({title: '下载失败', icon: 'error'})
} }
}) })
}, [imgUrl])
}, [tempFilePath])
const handleSaveCode = useCallback(() => { const handleSaveCode = useCallback(() => {
if (process.env.TARO_ENV === 'h5') { if (process.env.TARO_ENV === 'h5') {
Taro.showToast({title: '请截屏', icon: 'error'}) Taro.showToast({title: '长按保存二维码', icon: 'error'})
} else { } else {
getSetting({ getSetting({
success: function ({authSetting}) { 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 ( return (
<View className={styles.page}> <View className={styles.page}>
<Picker mode="date" value={start} onChange={(e) => setStart(e.detail.value + ' 8:00:00')} name='start'> <View className={styles.box}>
<PopPut title='开始时间' content={start}/> <View></View>
</Picker>
<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}>
<PopPut title='结束时间' content={end}/> <Form onSubmit={submit}>
</Picker> <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>
<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> <Textarea
<Picker mode='selector' range={manages} onChange={change} rangeKey='name'> value={description}
<PopPut title='部门' content={manages?.find(x => x.id == depid)?.name}/> className='Textarea'
</Picker> disabled={!!params.id}
</View> placeholder='请输入描述'
maxlength={255}
name='description'
onInput={(e) => setDescription(e.detail.value)}
style={{height: '60px'}}>
</Textarea>
{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>}
{process.env.TARO_ENV !== 'h5' && <MyButton onClick={handleSaveCode}></MyButton>} <MyButton
{process.env.TARO_ENV === 'h5' && <View></View>} formType='submit'
fillet
width={200}
className={styles.button}
loading={loading}>
</MyButton>
</Form>
</View>
<View className='text-center text-muted my-3' onClick={jumpMeeings}> <Icon name='chevron-right'/></View>
</View>
</View>}
</View> </View>
) )
} }

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Loading…
Cancel
Save