一师一策调整
This commit is contained in:
parent
de30a7c235
commit
8306bb151f
57
src/api/base/kccyApi.ts
Normal file
57
src/api/base/kccyApi.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询课程成员
|
||||||
|
*/
|
||||||
|
export function kccyFindPageApi(params: any) {
|
||||||
|
return get('/api/kccy/findPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询课程成员详情
|
||||||
|
*/
|
||||||
|
export function kccyFindByIdApi(params: any) {
|
||||||
|
return get('/api/kccy/findById', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增/修改课程成员
|
||||||
|
*/
|
||||||
|
export function kccySaveApi(params: any) {
|
||||||
|
return post('/api/kccy/save', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除课程成员
|
||||||
|
*/
|
||||||
|
export function kccyLogicDeleteApi(params: any) {
|
||||||
|
return post('/api/kccy/logicDelete', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据课程建班ID查询成员列表
|
||||||
|
*/
|
||||||
|
export function kccyFindByKcjbIdApi(params: any) {
|
||||||
|
return get('/api/kccy/findByKcjbId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段选择
|
||||||
|
*/
|
||||||
|
export function kccyExportFieldChooseApi(params: any) {
|
||||||
|
return get('/api/kccy/exportFieldChoose', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出课程成员数据
|
||||||
|
*/
|
||||||
|
export function kccyExportFileApi(params: any) {
|
||||||
|
return post('/api/kccy/export', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入课程成员数据
|
||||||
|
*/
|
||||||
|
export function kccyImportDataApi(params: any) {
|
||||||
|
return post('/api/kccy/importData', params);
|
||||||
|
}
|
||||||
29
src/api/base/kcjbApi.ts
Normal file
29
src/api/base/kcjbApi.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程建班列表
|
||||||
|
*/
|
||||||
|
export function kcjbFindPageApi(params: any) {
|
||||||
|
return get('/api/kcjb/findPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程建班详情
|
||||||
|
*/
|
||||||
|
export function kcjbFindByIdApi(params: any) {
|
||||||
|
return get('/api/kcjb/findById', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报名课程
|
||||||
|
*/
|
||||||
|
export function kcjbRegisterApi(params: any) {
|
||||||
|
return post('/api/kcjb/register', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取轮播图数据
|
||||||
|
*/
|
||||||
|
export function kcjbBannerApi() {
|
||||||
|
return get('/api/kcjb/banner');
|
||||||
|
}
|
||||||
79
src/api/base/rwApi.ts
Normal file
79
src/api/base/rwApi.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
export function rwFindPageApi(params: any) {
|
||||||
|
return get('/api/rw/findPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rwFindPageSummaryApi(params: any) {
|
||||||
|
return get('/api/rw/findPageSummary', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rwCompletionSummaryApi() {
|
||||||
|
return get('/api/rw/completionSummary');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增/修改
|
||||||
|
*/
|
||||||
|
export function rwSaveApi(params: any) {
|
||||||
|
return post('/api/rw/save', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
export function rwLogicDeleteApi(params: any) {
|
||||||
|
return post('/api/rw/logicDelete', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询
|
||||||
|
*/
|
||||||
|
export function rwFindByIdApi(params: any) {
|
||||||
|
return get('/api/rw/findById', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部
|
||||||
|
*/
|
||||||
|
export function rwFindAllApi() {
|
||||||
|
return get('/api/rw/findAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段选择
|
||||||
|
*/
|
||||||
|
export function rwExportFieldChooseApi(params: any) {
|
||||||
|
return get('/api/rw/exportFieldChoose', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出
|
||||||
|
*/
|
||||||
|
export function rwExportFileApi(params: any) {
|
||||||
|
return post('/api/rw/export', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入
|
||||||
|
*/
|
||||||
|
export function rwImportDataApi(params: any) {
|
||||||
|
return post('/api/rw/importData', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强删除活动
|
||||||
|
*/
|
||||||
|
export function rwDelApi(params: any) {
|
||||||
|
return post('/api/rw/delete', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送教师
|
||||||
|
*/
|
||||||
|
export function rwPushJsApi(params: any) {
|
||||||
|
return post('/api/rw/pushJs', params);
|
||||||
|
}
|
||||||
113
src/api/base/rwzxApi.ts
Normal file
113
src/api/base/rwzxApi.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { get, post } from "@/utils/request";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询任务执行
|
||||||
|
*/
|
||||||
|
export function rwzxFindPageApi(params: any) {
|
||||||
|
return get('/api/rwzx/findPage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务ID查询执行情况
|
||||||
|
*/
|
||||||
|
export function rwzxFindByRwIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/findByRwId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务ID查询已执行信息
|
||||||
|
*/
|
||||||
|
export function executedInfoByRwIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/executedInfoByRwId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务ID查询未执行教师信息
|
||||||
|
*/
|
||||||
|
export function noxecuteJsbyRwIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/noxecuteJsbyRwId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增/修改任务执行
|
||||||
|
*/
|
||||||
|
export function rwzxSaveApi(params: any) {
|
||||||
|
return post('/api/rwzx/save', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除任务执行
|
||||||
|
*/
|
||||||
|
export function rwzxLogicDeleteApi(params: any) {
|
||||||
|
return post('/api/rwzx/logicDelete', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询任务执行
|
||||||
|
*/
|
||||||
|
export function rwzxFindByIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/findById', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部任务执行
|
||||||
|
*/
|
||||||
|
export function rwzxFindAllApi() {
|
||||||
|
return get('/api/rwzx/findAll');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务执行统计
|
||||||
|
*/
|
||||||
|
export function rwzxCompletionSummaryApi(params: any) {
|
||||||
|
return get('/api/rwzx/completionSummary', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段选择
|
||||||
|
*/
|
||||||
|
export function rwzxExportFieldChooseApi(params: any) {
|
||||||
|
return get('/api/rwzx/exportFieldChoose', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出任务执行数据
|
||||||
|
*/
|
||||||
|
export function rwzxExportFileApi(params: any) {
|
||||||
|
return post('/api/rwzx/export', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入任务执行数据
|
||||||
|
*/
|
||||||
|
export function rwzxImportDataApi(params: any) {
|
||||||
|
return post('/api/rwzx/importData', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强删除任务执行记录
|
||||||
|
*/
|
||||||
|
export function rwzxDelApi(params: any) {
|
||||||
|
return post('/api/rwzx/delete', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询任务未执行教师
|
||||||
|
*/
|
||||||
|
export function rwzxNoxecuteJsbyRwIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/noxecuteJsbyRwId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询任务已执行信息
|
||||||
|
*/
|
||||||
|
export function rwzxExecutedInfoByRwIdApi(params: any) {
|
||||||
|
return get('/api/rwzx/executedInfoByRwId', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量创建任务执行记录
|
||||||
|
*/
|
||||||
|
export function rwzxBatchCreateApi(params: any) {
|
||||||
|
return post('/api/rwzx/batchCreate', params);
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<u-popup :show="showPopup" @close="showPopup = false">
|
<u-popup :show="showPopup" @close="showPopup = false" mode="bottom" width="100%">
|
||||||
<view class="js-picker-popup">
|
<view class="js-picker-popup">
|
||||||
<view class="js-picker-header">
|
<view class="js-picker-header">
|
||||||
<view class="js-cancel-btn" @click="handleCancel">取消</view>
|
<view class="js-cancel-btn" @click="handleCancel">取消</view>
|
||||||
|
|||||||
@ -87,11 +87,56 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 文件上传区域 -->
|
||||||
|
<view class="upload-section" v-if="enableFile">
|
||||||
|
<view class="section-title">
|
||||||
|
<text class="title-text">文件</text>
|
||||||
|
<text class="count-text">({{ fileList.length }}/{{ maxFileCount }})</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="file-list">
|
||||||
|
<view
|
||||||
|
class="file-item"
|
||||||
|
v-for="(file, index) in fileList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<view class="file-preview" @click="previewFile(index)">
|
||||||
|
<view class="file-icon">
|
||||||
|
<uni-icons :type="getFileIcon(file.extension || '')" size="32" color="#666"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="file-info">
|
||||||
|
<text class="file-name">{{ file.name }}</text>
|
||||||
|
<text class="file-size" v-if="file.size">{{ formatFileSize(file.size) }}</text>
|
||||||
|
<text class="file-type">{{ file.extension?.toUpperCase() || 'FILE' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="file-actions">
|
||||||
|
<view class="delete-btn" @click="removeFile(index)">
|
||||||
|
<uni-icons type="close" size="16" color="#fff"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="add-btn"
|
||||||
|
v-if="fileList.length < maxFileCount"
|
||||||
|
@click="chooseFile"
|
||||||
|
>
|
||||||
|
<uni-icons type="paperclip" size="24" color="#999"></uni-icons>
|
||||||
|
<text class="add-text">添加文件</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { imagUrl } from '@/utils'
|
||||||
|
import {
|
||||||
|
previewFile as previewFileUtil
|
||||||
|
} from '@/utils/filePreview'
|
||||||
|
|
||||||
// 接口定义
|
// 接口定义
|
||||||
interface ImageItem {
|
interface ImageItem {
|
||||||
@ -111,6 +156,16 @@ interface VideoItem {
|
|||||||
thumbnail?: string
|
thumbnail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FileItem {
|
||||||
|
tempPath?: string
|
||||||
|
url?: string
|
||||||
|
name?: string
|
||||||
|
type?: string
|
||||||
|
size?: number
|
||||||
|
extension?: string
|
||||||
|
mimeType?: string
|
||||||
|
}
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
// 图片相关
|
// 图片相关
|
||||||
@ -123,6 +178,12 @@ interface Props {
|
|||||||
maxVideoCount?: number
|
maxVideoCount?: number
|
||||||
videoList?: VideoItem[]
|
videoList?: VideoItem[]
|
||||||
|
|
||||||
|
// 文件相关
|
||||||
|
enableFile?: boolean
|
||||||
|
maxFileCount?: number
|
||||||
|
fileList?: FileItem[]
|
||||||
|
allowedFileTypes?: string[]
|
||||||
|
|
||||||
// 压缩配置
|
// 压缩配置
|
||||||
compressConfig?: {
|
compressConfig?: {
|
||||||
image: {
|
image: {
|
||||||
@ -153,6 +214,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
maxVideoCount: 3,
|
maxVideoCount: 3,
|
||||||
videoList: () => [],
|
videoList: () => [],
|
||||||
|
|
||||||
|
enableFile: false,
|
||||||
|
maxFileCount: 5,
|
||||||
|
fileList: () => [],
|
||||||
|
allowedFileTypes: () => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'wav', 'zip', 'rar'],
|
||||||
|
|
||||||
compressConfig: () => ({
|
compressConfig: () => ({
|
||||||
image: {
|
image: {
|
||||||
quality: 60,
|
quality: 60,
|
||||||
@ -176,16 +242,20 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:imageList': [images: ImageItem[]]
|
'update:imageList': [images: ImageItem[]]
|
||||||
'update:videoList': [videos: VideoItem[]]
|
'update:videoList': [videos: VideoItem[]]
|
||||||
|
'update:fileList': [files: FileItem[]]
|
||||||
'image-upload-success': [image: ImageItem, index: number]
|
'image-upload-success': [image: ImageItem, index: number]
|
||||||
'image-upload-error': [error: any, index: number]
|
'image-upload-error': [error: any, index: number]
|
||||||
'video-upload-success': [video: VideoItem, index: number]
|
'video-upload-success': [video: VideoItem, index: number]
|
||||||
'video-upload-error': [error: any, index: number]
|
'video-upload-error': [error: any, index: number]
|
||||||
'upload-progress': [type: 'image' | 'video', current: number, total: number]
|
'file-upload-success': [file: FileItem, index: number]
|
||||||
|
'file-upload-error': [error: any, index: number]
|
||||||
|
'upload-progress': [type: 'image' | 'video' | 'file', current: number, total: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const imageList = ref<ImageItem[]>([...props.imageList])
|
const imageList = ref<ImageItem[]>([...props.imageList])
|
||||||
const videoList = ref<VideoItem[]>([...props.videoList])
|
const videoList = ref<VideoItem[]>([...props.videoList])
|
||||||
|
const fileList = ref<FileItem[]>([...props.fileList])
|
||||||
|
|
||||||
// 监听props变化
|
// 监听props变化
|
||||||
watch(() => props.imageList, (newList) => {
|
watch(() => props.imageList, (newList) => {
|
||||||
@ -196,6 +266,10 @@ watch(() => props.videoList, (newList) => {
|
|||||||
videoList.value = [...newList]
|
videoList.value = [...newList]
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
watch(() => props.fileList, (newList) => {
|
||||||
|
fileList.value = [...newList]
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
// 压缩配置
|
// 压缩配置
|
||||||
const COMPRESS_CONFIG = computed(() => props.compressConfig)
|
const COMPRESS_CONFIG = computed(() => props.compressConfig)
|
||||||
|
|
||||||
@ -483,6 +557,180 @@ const chooseVideo = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 选择文件
|
||||||
|
const chooseFile = () => {
|
||||||
|
uni.chooseFile({
|
||||||
|
count: props.maxFileCount - fileList.value.length,
|
||||||
|
type: 'all',
|
||||||
|
success: async (res) => {
|
||||||
|
const tempFiles = res.tempFiles
|
||||||
|
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
|
||||||
|
try {
|
||||||
|
for (const file of tempFiles) {
|
||||||
|
const fileInfo = file as any
|
||||||
|
const fileName = fileInfo.name || ''
|
||||||
|
const fileExtension = fileName.split('.').pop()?.toLowerCase() || ''
|
||||||
|
|
||||||
|
// 检查文件类型是否被允许
|
||||||
|
if (props.allowedFileTypes && props.allowedFileTypes.length > 0) {
|
||||||
|
if (!props.allowedFileTypes.includes(fileExtension)) {
|
||||||
|
showToast({
|
||||||
|
title: `不支持的文件类型: ${fileExtension}`,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定文件类型
|
||||||
|
let fileType = 'document'
|
||||||
|
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'].includes(fileExtension)) {
|
||||||
|
fileType = 'image'
|
||||||
|
} else if (['mp4', 'mov', 'avi', 'wmv', 'flv'].includes(fileExtension)) {
|
||||||
|
fileType = 'video'
|
||||||
|
} else if (['mp3', 'wav', 'aac', 'ogg'].includes(fileExtension)) {
|
||||||
|
fileType = 'audio'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建文件项
|
||||||
|
const fileItem: FileItem = {
|
||||||
|
tempPath: fileInfo.path,
|
||||||
|
name: fileName,
|
||||||
|
type: fileType,
|
||||||
|
size: fileInfo.size,
|
||||||
|
extension: fileExtension,
|
||||||
|
mimeType: fileInfo.type || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到文件列表
|
||||||
|
fileList.value.push(fileItem)
|
||||||
|
|
||||||
|
// 如果启用自动上传
|
||||||
|
if (props.autoUpload && props.uploadApi) {
|
||||||
|
await uploadFile(fileItem, fileList.value.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新父组件
|
||||||
|
emit('update:fileList', fileList.value)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('文件处理失败:', error)
|
||||||
|
showToast({ title: '文件处理失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
console.error('选择文件失败:', error)
|
||||||
|
if (error.errMsg && !error.errMsg.includes('cancel')) {
|
||||||
|
showToast({ title: '选择文件失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
|
const uploadFile = async (fileItem: FileItem, index: number) => {
|
||||||
|
if (!props.uploadApi || !fileItem.tempPath) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await props.uploadApi(fileItem.tempPath)
|
||||||
|
|
||||||
|
if (result && result.resultCode === 1 && result.result && result.result.length > 0) {
|
||||||
|
const uploadedFile = result.result[0]
|
||||||
|
|
||||||
|
// 更新文件项
|
||||||
|
fileItem.url = uploadedFile.filePath
|
||||||
|
fileItem.tempPath = undefined // 清除临时路径
|
||||||
|
|
||||||
|
// 触发成功事件
|
||||||
|
emit('file-upload-success', fileItem, index)
|
||||||
|
|
||||||
|
showToast({ title: '文件上传成功', icon: 'success' })
|
||||||
|
} else {
|
||||||
|
throw new Error('上传失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('文件上传失败:', error)
|
||||||
|
emit('file-upload-error', error, index)
|
||||||
|
showToast({ title: '文件上传失败', icon: 'none' })
|
||||||
|
|
||||||
|
// 移除上传失败的文件
|
||||||
|
fileList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览文件
|
||||||
|
const previewFile = (index: number) => {
|
||||||
|
const file = fileList.value[index]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
if (file.type === 'image' && file.url) {
|
||||||
|
// 图片预览
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [file.url],
|
||||||
|
current: file.url
|
||||||
|
})
|
||||||
|
} else if (file.url) {
|
||||||
|
// 使用公文预览方式预览其他文件
|
||||||
|
const fileUrl = imagUrl(file.url)
|
||||||
|
const fileName = file.name || '未知文件'
|
||||||
|
const fileExtension = file.extension || file.url.split('.').pop() || ''
|
||||||
|
|
||||||
|
// 统一使用 kkview 预览
|
||||||
|
const fullFileName = fileExtension ? `${fileName}.${fileExtension}` : fileName
|
||||||
|
previewFileUtil(fileUrl, fullFileName, fileExtension)
|
||||||
|
.catch((error: any) => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '预览失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 其他文件类型显示提示
|
||||||
|
showToast({
|
||||||
|
title: `预览 ${file.name} 功能待实现`,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除文件
|
||||||
|
const removeFile = (index: number) => {
|
||||||
|
fileList.value.splice(index, 1)
|
||||||
|
emit('update:fileList', fileList.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件图标
|
||||||
|
const getFileIcon = (extension: string): string => {
|
||||||
|
const iconMap: { [key: string]: string } = {
|
||||||
|
// 文档
|
||||||
|
'pdf': 'book',
|
||||||
|
'doc': 'book',
|
||||||
|
'docx': 'book',
|
||||||
|
'txt': 'book',
|
||||||
|
// 表格
|
||||||
|
'xls': 'table',
|
||||||
|
'xlsx': 'table',
|
||||||
|
// 演示文稿
|
||||||
|
'ppt': 'slideshow',
|
||||||
|
'pptx': 'slideshow',
|
||||||
|
// 音频
|
||||||
|
'mp3': 'mic',
|
||||||
|
'wav': 'mic',
|
||||||
|
'aac': 'mic',
|
||||||
|
'ogg': 'mic',
|
||||||
|
// 压缩文件
|
||||||
|
'zip': 'folder',
|
||||||
|
'rar': 'folder',
|
||||||
|
'7z': 'folder',
|
||||||
|
// 其他
|
||||||
|
'default': 'paperclip'
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconMap[extension.toLowerCase()] || iconMap['default']
|
||||||
|
}
|
||||||
|
|
||||||
// 上传图片
|
// 上传图片
|
||||||
const uploadImages = async (images: ImageItem[]) => {
|
const uploadImages = async (images: ImageItem[]) => {
|
||||||
if (!props.uploadApi) return
|
if (!props.uploadApi) return
|
||||||
@ -683,10 +931,13 @@ const showToast = (options: { title: string; icon?: string; duration?: number })
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
chooseImage,
|
chooseImage,
|
||||||
chooseVideo,
|
chooseVideo,
|
||||||
|
chooseFile,
|
||||||
uploadImages,
|
uploadImages,
|
||||||
uploadVideos,
|
uploadVideos,
|
||||||
|
uploadFile,
|
||||||
removeImage,
|
removeImage,
|
||||||
removeVideo
|
removeVideo,
|
||||||
|
removeFile
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -842,4 +1093,84 @@ defineExpose({
|
|||||||
color: #999;
|
color: #999;
|
||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件相关样式 */
|
||||||
|
.file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx;
|
||||||
|
border: 1rpx solid #e9ecef;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
border: 1rpx solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-type {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #666;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-actions .delete-btn {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background: rgba(255, 59, 48, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -17,6 +17,17 @@ export interface VideoItem {
|
|||||||
thumbnail?: string // 缩略图路径
|
thumbnail?: string // 缩略图路径
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文件项接口
|
||||||
|
export interface FileItem {
|
||||||
|
tempPath?: string // 临时路径(用于预览)
|
||||||
|
url?: string // 服务器路径(上传成功后)
|
||||||
|
name?: string // 文件名
|
||||||
|
type?: string // 文件类型(image/video/audio/document等)
|
||||||
|
size?: number // 文件大小(字节)
|
||||||
|
extension?: string // 文件扩展名
|
||||||
|
mimeType?: string // MIME类型
|
||||||
|
}
|
||||||
|
|
||||||
// 压缩配置接口
|
// 压缩配置接口
|
||||||
export interface CompressConfig {
|
export interface CompressConfig {
|
||||||
image: {
|
image: {
|
||||||
@ -45,6 +56,12 @@ export interface ImageVideoUploadProps {
|
|||||||
maxVideoCount?: number
|
maxVideoCount?: number
|
||||||
videoList?: VideoItem[]
|
videoList?: VideoItem[]
|
||||||
|
|
||||||
|
// 文件相关
|
||||||
|
enableFile?: boolean
|
||||||
|
maxFileCount?: number
|
||||||
|
fileList?: FileItem[]
|
||||||
|
allowedFileTypes?: string[] // 允许的文件类型,如 ['pdf', 'doc', 'docx', 'mp3', 'wav']
|
||||||
|
|
||||||
// 压缩配置
|
// 压缩配置
|
||||||
compressConfig?: CompressConfig
|
compressConfig?: CompressConfig
|
||||||
|
|
||||||
@ -57,11 +74,14 @@ export interface ImageVideoUploadProps {
|
|||||||
export interface ImageVideoUploadEmits {
|
export interface ImageVideoUploadEmits {
|
||||||
'update:imageList': [images: ImageItem[]]
|
'update:imageList': [images: ImageItem[]]
|
||||||
'update:videoList': [videos: VideoItem[]]
|
'update:videoList': [videos: VideoItem[]]
|
||||||
|
'update:fileList': [files: FileItem[]]
|
||||||
'image-upload-success': [image: ImageItem, index: number]
|
'image-upload-success': [image: ImageItem, index: number]
|
||||||
'image-upload-error': [error: any, index: number]
|
'image-upload-error': [error: any, index: number]
|
||||||
'video-upload-success': [video: VideoItem, index: number]
|
'video-upload-success': [video: VideoItem, index: number]
|
||||||
'video-upload-error': [error: any, index: number]
|
'video-upload-error': [error: any, index: number]
|
||||||
'upload-progress': [type: 'image' | 'video', current: number, total: number]
|
'file-upload-success': [file: FileItem, index: number]
|
||||||
|
'file-upload-error': [error: any, index: number]
|
||||||
|
'upload-progress': [type: 'image' | 'video' | 'file', current: number, total: number]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认压缩配置
|
// 默认压缩配置
|
||||||
|
|||||||
@ -136,6 +136,48 @@
|
|||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/addkcrw",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "新增任务",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/editkcrw",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改任务",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务列表",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/push",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务推送",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/kcrwzx",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务执行",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/routine/yishiyice/kcrwzxtj",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "提交任务",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/view/routine/yishiyice/success",
|
"path": "pages/view/routine/yishiyice/success",
|
||||||
"style": {
|
"style": {
|
||||||
@ -143,6 +185,20 @@
|
|||||||
"enablePullDownRefresh": false
|
"enablePullDownRefresh": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/rw/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务执行",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/view/rw/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "任务详情",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/view/routine/JiaoXueZiYuan/index",
|
"path": "pages/view/routine/JiaoXueZiYuan/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -95,7 +95,7 @@ onLoad(async (options) => {
|
|||||||
rwflx.value = result.rwlxes;
|
rwflx.value = result.rwlxes;
|
||||||
rw.value = result;
|
rw.value = result;
|
||||||
for (let i = 0; i < rwflx.value.length; i++) {
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
|
if (rwflx.value[i].rwfl == "sczy") {
|
||||||
schema.push({
|
schema.push({
|
||||||
field: `${rwflx.value[i].id}`,
|
field: `${rwflx.value[i].id}`,
|
||||||
label: `${rwflx.value[i].rwbt}`,
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
|||||||
@ -263,7 +263,23 @@ const sections = reactive<Section[]>([
|
|||||||
permissionKey: "routine-yrcg", // 一日常规权限编码
|
permissionKey: "routine-yrcg", // 一日常规权限编码
|
||||||
path: "/pages/view/quantitativeAssessment/index/index",
|
path: "/pages/view/quantitativeAssessment/index/index",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "r14",
|
||||||
|
icon: "ysyc",
|
||||||
|
text: "一师一策",
|
||||||
|
show: true,
|
||||||
|
permissionKey: "routine-ysyc", // 一日一策权限编码
|
||||||
|
path: "/pages/view/routine/yishiyice/index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "r15",
|
||||||
|
icon: "rwzx",
|
||||||
|
text: "任务执行",
|
||||||
|
show: true,
|
||||||
|
permissionKey: "routine-ysyc", // 一日一策权限编码
|
||||||
|
path: "/pages/view/rw/index",
|
||||||
|
},
|
||||||
|
//path: "/pages/view/routine/yishiyice/index"rw/index,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
1190
src/pages/view/routine/yishiyice/addkcrw.vue
Normal file
1190
src/pages/view/routine/yishiyice/addkcrw.vue
Normal file
File diff suppressed because it is too large
Load Diff
1193
src/pages/view/routine/yishiyice/detail.vue
Normal file
1193
src/pages/view/routine/yishiyice/detail.vue
Normal file
File diff suppressed because it is too large
Load Diff
1423
src/pages/view/routine/yishiyice/editkcrw.vue
Normal file
1423
src/pages/view/routine/yishiyice/editkcrw.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,312 +1,500 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasicLayout :show-nav-bar="true" :nav-bar-props="{ title: '一师一策' }">
|
|
||||||
<view class="yishiyice-page">
|
<view class="yishiyice-page">
|
||||||
<!-- Search Bar -->
|
<!-- 查询组件 -->
|
||||||
<view class="search-bar-container">
|
<view class="query-component">
|
||||||
|
<view class="search-card">
|
||||||
|
<view class="search-item">
|
||||||
<uni-search-bar
|
<uni-search-bar
|
||||||
placeholder="请输入搜索内容..."
|
v-model="searchForm.kcmc"
|
||||||
|
placeholder="请输入课程名称..."
|
||||||
bgColor="#f4f5f7"
|
bgColor="#f4f5f7"
|
||||||
radius="100"
|
radius="100"
|
||||||
cancelButton="none"
|
cancelButton="none"
|
||||||
@confirm="handleSearch"
|
@confirm="handleSearch"
|
||||||
|
@input="handleSearchInput"
|
||||||
></uni-search-bar>
|
></uni-search-bar>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="search-actions">
|
||||||
|
<u-button
|
||||||
|
text="查询"
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="handleSearch"
|
||||||
|
class="search-btn"
|
||||||
|
/>
|
||||||
|
<u-button
|
||||||
|
text="重置"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
@click="handleReset"
|
||||||
|
class="reset-btn"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- Carousel/Banner -->
|
<!-- 列表组件 -->
|
||||||
<view class="banner-section">
|
<view class="list-component">
|
||||||
<swiper
|
<scroll-view
|
||||||
v-if="bannerList.length > 0"
|
scroll-y
|
||||||
class="banner-swiper"
|
class="list-scroll-view"
|
||||||
circular
|
@scrolltolower="loadMore"
|
||||||
indicator-dots
|
lower-threshold="100"
|
||||||
autoplay
|
|
||||||
:interval="3000"
|
|
||||||
:duration="500"
|
|
||||||
indicator-color="rgba(255, 255, 255, 0.5)"
|
|
||||||
indicator-active-color="#ffffff"
|
|
||||||
>
|
>
|
||||||
<swiper-item v-for="(item, index) in bannerList" :key="index" class="swiper-item">
|
<view v-if="isLoading && courseList.length === 0" class="loading-indicator">加载中...</view>
|
||||||
<view class="banner-content" :style="{ background: item.bgColor }">
|
<template v-else-if="courseList.length > 0">
|
||||||
<view class="text-content">
|
<view v-for="course in courseList" :key="course.id" class="course-card" @click="viewCourseDetail(course.id)">
|
||||||
<text class="banner-title">{{ item.title }}</text>
|
<view class="card-header">
|
||||||
<text class="banner-subtitle">{{ item.subtitle }}</text>
|
<text class="course-title">{{ course.kcmc }}</text>
|
||||||
</view>
|
<view class="arrow-icon">
|
||||||
<image :src="item.imageUrl" mode="aspectFit" class="banner-image"></image>
|
<text class="arrow-text">></text>
|
||||||
</view>
|
|
||||||
</swiper-item>
|
|
||||||
</swiper>
|
|
||||||
<!-- Placeholder if no banner items -->
|
|
||||||
<view v-else class="banner-placeholder">
|
|
||||||
<text>暂无内容</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="card-body">
|
||||||
<!-- Course List -->
|
|
||||||
<view class="course-list">
|
|
||||||
<view v-for="course in courseList" :key="course.id" class="course-card">
|
|
||||||
<image :src="course.imageUrl || '/static/default-cover.png'" mode="aspectFill" class="course-image"></image>
|
|
||||||
<view class="course-info">
|
<view class="course-info">
|
||||||
<text class="course-title">{{ course.title }}</text>
|
<view class="info-item">
|
||||||
<text class="course-desc">{{ course.description }}</text>
|
<text class="info-label">课程描述:</text>
|
||||||
<text class="course-date">{{ course.date }}</text>
|
<text class="info-value">{{ course.kcms || '暂无描述' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<button class="register-btn" size="mini" @click="registerCourse(course.id)">立即报名</button>
|
<view class="info-item" v-if="course.jsxm">
|
||||||
|
<text class="info-label">主讲教师:</text>
|
||||||
|
<text class="info-value">{{ course.jsxm }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info-item" v-if="course.bzrxm">
|
||||||
|
<text class="info-label">班主任:</text>
|
||||||
|
<text class="info-value">{{ course.bzrxm }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item" v-if="course.ljlxxm">
|
||||||
|
<text class="info-label">联系人:</text>
|
||||||
|
<text class="info-value">{{ course.ljlxxm }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<view v-else class="empty-state">暂无课程数据</view>
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view v-if="isLoading && courseList.length > 0" class="loading-more">
|
||||||
|
<text>加载中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 没有更多数据 -->
|
||||||
|
<view v-if="!hasMore && courseList.length > 0" class="no-more">
|
||||||
|
<text>没有更多数据了</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 新增按钮 - 固定在底部 -->
|
||||||
|
<view class="add-button-fixed">
|
||||||
|
<u-button
|
||||||
|
text="新增课程"
|
||||||
|
type="primary"
|
||||||
|
@click="goToAddCourse"
|
||||||
|
class="add-btn"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</BasicLayout>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
|
import { kcjbFindPageApi, kcjbBannerApi, kcjbRegisterApi } from "@/api/base/kcjbApi";
|
||||||
|
|
||||||
// Interface for Banner items
|
|
||||||
interface BannerItem {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
imageUrl: string;
|
|
||||||
bgColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface for Course items
|
|
||||||
interface CourseItem {
|
interface CourseItem {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
kcmc: string; // 课程名称
|
||||||
description: string;
|
kcms: string; // 课程描述
|
||||||
date: string;
|
cjsj: string; // 创建时间
|
||||||
imageUrl: string;
|
kctp: string; // 课程图片
|
||||||
|
kczt: string; // 课程状态:A正常,B暂停,C结束
|
||||||
|
kclx?: string; // 课程类型
|
||||||
|
jsxm?: string; // 教师姓名
|
||||||
|
bzrxm?: string; // 班主任姓名
|
||||||
|
ljlxxm?: string; // 联系人姓名
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock Banner Data
|
// 搜索表单
|
||||||
const bannerList = ref<BannerItem[]>([
|
const searchForm = reactive({
|
||||||
{
|
kcmc: '' // 课程名称
|
||||||
id: 'b1',
|
});
|
||||||
title: '一师一策',
|
|
||||||
subtitle: '一师一优课、一课一名师',
|
|
||||||
imageUrl: '/static/mock/banner-yishiyice.png', // Replace with actual image path
|
|
||||||
bgColor: 'linear-gradient(to right, #4a7cf6, #6f9eff)' // Example gradient
|
|
||||||
},
|
|
||||||
// Add more banner items if needed
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Mock Course List Data
|
// 数据列表
|
||||||
const courseList = ref<CourseItem[]>([
|
const courseList = ref<CourseItem[]>([]);
|
||||||
{
|
const isLoading = ref(false);
|
||||||
id: 'c1',
|
const hasMore = ref(true);
|
||||||
title: '教学力课程',
|
const currentPage = ref(1);
|
||||||
description: '每月第2周例会集中培训+外出培训',
|
const pageSize = ref(10);
|
||||||
date: '2025-02-11 12:33:12',
|
|
||||||
imageUrl: '/static/mock/course-jiaoxue.png' // Replace with actual image paths
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'c2',
|
|
||||||
title: '摄影艺术',
|
|
||||||
description: '每月第3周例会时间',
|
|
||||||
date: '2025-02-11 12:33:12',
|
|
||||||
imageUrl: '/static/mock/course-sheying.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'c3',
|
|
||||||
title: '育人力课程',
|
|
||||||
description: '每月第2周例会集中培训+外出培训',
|
|
||||||
date: '2025-02-11 12:33:12',
|
|
||||||
imageUrl: '/static/mock/course-yuren.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'c4',
|
|
||||||
title: '课程力课程',
|
|
||||||
description: '每月第3周例会时间',
|
|
||||||
date: '2025-02-11 12:33:12',
|
|
||||||
imageUrl: '/static/mock/course-kecheng.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'c5',
|
|
||||||
title: '研究力课程',
|
|
||||||
description: '每月第3周例会时间',
|
|
||||||
date: '2025-02-11 12:33:12',
|
|
||||||
imageUrl: '/static/mock/course-yanjiu.png'
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// --- Methods ---
|
// 处理搜索输入
|
||||||
|
const handleSearchInput = (value: string) => {
|
||||||
const handleSearch = (value: string) => {
|
searchForm.kcmc = value;
|
||||||
console.log('搜索内容:', value);
|
|
||||||
// TODO: Implement search logic
|
|
||||||
uni.showToast({ title: `搜索: ${value}`, icon: 'none' });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerCourse = (courseId: string) => {
|
// 处理搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
console.log('搜索课程名称:', searchForm.kcmc);
|
||||||
|
getCourseList(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置搜索
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.kcmc = '';
|
||||||
|
getCourseList(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取课程列表
|
||||||
|
const getCourseList = async (isLoadMore = false) => {
|
||||||
|
if (isLoading.value) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: isLoadMore ? currentPage.value + 1 : 1,
|
||||||
|
rows: pageSize.value,
|
||||||
|
kcmc: searchForm.kcmc
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('API请求参数:', params);
|
||||||
|
const response = await kcjbFindPageApi(params);
|
||||||
|
const newData = response.rows || [];
|
||||||
|
|
||||||
|
if (isLoadMore) {
|
||||||
|
courseList.value.push(...newData);
|
||||||
|
currentPage.value++;
|
||||||
|
} else {
|
||||||
|
courseList.value = newData;
|
||||||
|
currentPage.value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMore.value = newData.length === pageSize.value;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取课程列表失败:', error);
|
||||||
|
courseList.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态样式类
|
||||||
|
const getStatusClass = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'A':
|
||||||
|
return 'status-normal';
|
||||||
|
case 'B':
|
||||||
|
return 'status-paused';
|
||||||
|
case 'C':
|
||||||
|
return 'status-ended';
|
||||||
|
default:
|
||||||
|
return 'status-default';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'A':
|
||||||
|
return '正常';
|
||||||
|
case 'B':
|
||||||
|
return '暂停';
|
||||||
|
case 'C':
|
||||||
|
return '已结束';
|
||||||
|
default:
|
||||||
|
return '未知';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (time: string) => {
|
||||||
|
if (!time) return '未设置';
|
||||||
|
return new Date(time).toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 报名课程
|
||||||
|
const registerCourse = async (courseId: string) => {
|
||||||
|
try {
|
||||||
console.log('报名课程:', courseId);
|
console.log('报名课程:', courseId);
|
||||||
// TODO: Implement actual registration API call here
|
|
||||||
// For now, just navigate to the success page
|
const params = {
|
||||||
uni.navigateTo({ url: '/pages/view/routine/yishiyice/success' });
|
courseId: courseId
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Add functions to fetch banner and course data from API
|
await kcjbRegisterApi(params);
|
||||||
|
uni.showToast({ title: '报名成功', icon: 'success' });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateTo({ url: '/pages/view/routine/yishiyice/success' });
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('报名失败:', error);
|
||||||
|
uni.showToast({ title: '报名失败,请重试', icon: 'error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看课程详情
|
||||||
|
const viewCourseDetail = (courseId: string) => {
|
||||||
|
console.log('查看课程详情:', courseId);
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/view/routine/yishiyice/detail?id=${courseId}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 新增课程
|
||||||
|
const goToAddCourse = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/view/routine/yishiyice/add'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 加载更多数据
|
||||||
|
const loadMore = () => {
|
||||||
|
if (!isLoading.value && hasMore.value) {
|
||||||
|
getCourseList(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面显示时刷新数据
|
||||||
|
onShow(() => {
|
||||||
|
getCourseList(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载时也调用一次
|
||||||
|
onMounted(() => {
|
||||||
|
getCourseList(false);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss" scoped>
|
||||||
.yishiyice-page {
|
.yishiyice-page {
|
||||||
background-color: #f4f5f7;
|
|
||||||
min-height: 100%; // Ensure it fills the layout height
|
|
||||||
padding-bottom: 15px; // Space at the bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-container {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
// Remove or adjust border based on design
|
|
||||||
// border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-section {
|
|
||||||
padding: 15px 15px 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-swiper {
|
|
||||||
height: 120px; // Adjust height as needed
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swiper-item {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.text-content {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: #ffffff;
|
height: 100vh;
|
||||||
z-index: 1;
|
background-color: #f5f7fa;
|
||||||
}
|
}
|
||||||
.banner-title {
|
|
||||||
font-size: 18px;
|
// 查询组件样式
|
||||||
font-weight: bold;
|
.query-component {
|
||||||
margin-bottom: 5px;
|
padding: 15px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
.banner-subtitle {
|
|
||||||
font-size: 13px;
|
.search-card {
|
||||||
opacity: 0.9;
|
.search-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.search-btn, .reset-btn {
|
||||||
|
min-width: 60px;
|
||||||
}
|
}
|
||||||
.banner-image {
|
|
||||||
width: 100px; // Adjust size as needed
|
|
||||||
height: 80px;
|
|
||||||
position: absolute; // Or adjust flex layout
|
|
||||||
right: 15px;
|
|
||||||
bottom: 0px;
|
|
||||||
z-index: 0;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-placeholder {
|
// 列表组件样式
|
||||||
height: 120px;
|
.list-component {
|
||||||
background-color: #e9e9eb;
|
flex: 1;
|
||||||
border-radius: 8px;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.list-scroll-view {
|
||||||
|
height: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
padding-bottom: 80px; // 为底部按钮留出空间
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
.loading-indicator {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 课程卡片样式
|
||||||
|
.course-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 4px;
|
||||||
|
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 16px 16px 12px 16px;
|
||||||
|
|
||||||
|
.course-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2c3e50;
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-break: break-word;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.arrow-text {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 0 16px 16px 16px;
|
||||||
|
|
||||||
|
.course-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 空状态
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多状态
|
||||||
|
.loading-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无更多数据状态
|
||||||
|
.no-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-list {
|
// 固定底部按钮
|
||||||
|
.add-button-fixed {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
background-color: #fff;
|
||||||
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
|
||||||
.course-card {
|
.add-btn {
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
|
||||||
background-color: #ffffff;
|
border-radius: 12px;
|
||||||
border-radius: 8px;
|
padding: 12px 24px;
|
||||||
padding: 12px;
|
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||||
margin-bottom: 12px;
|
font-weight: 600;
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
.course-image {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-right: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.course-info {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 0; // Prevent text overflow issues
|
|
||||||
margin-right: 10px;
|
|
||||||
|
|
||||||
.course-title {
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
// Ellipsis for long titles
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.course-desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #909399;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
// Ellipsis for long descriptions (optional, maybe 1 line)
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
// display: -webkit-box;
|
|
||||||
// -webkit-line-clamp: 1;
|
|
||||||
// -webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
.course-date {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #c0c4cc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.register-btn {
|
&:active {
|
||||||
background-color: #409eff;
|
transform: translateY(1px);
|
||||||
color: #ffffff;
|
}
|
||||||
border: none;
|
|
||||||
border-radius: 15px; // Make it rounder
|
|
||||||
padding: 4px 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
height: auto; // Let padding define height
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: auto; // Push to the right
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
&:active {
|
}
|
||||||
background-color: #3a8ee6;
|
}
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
@media screen and (max-width: 375px) {
|
||||||
|
.add-button-fixed {
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: If you need to adjust internal padding/height of search bar
|
// 深度选择器用于第三方组件样式调整
|
||||||
// ::v-deep .uni-searchbar__box {
|
:deep(.uni-searchbar__box) {
|
||||||
// height: 34px !important;
|
border-radius: 20px !important;
|
||||||
// line-height: 34px !important;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
|
:deep(.uni-searchbar__text-placeholder) {
|
||||||
|
color: #999999 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
886
src/pages/view/routine/yishiyice/kcrwzx.vue
Normal file
886
src/pages/view/routine/yishiyice/kcrwzx.vue
Normal file
@ -0,0 +1,886 @@
|
|||||||
|
<template>
|
||||||
|
<view class="task-execution-page">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<view class="page-header">
|
||||||
|
<text class="page-title">任务执行情况</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 任务信息 -->
|
||||||
|
<view class="task-info-card">
|
||||||
|
<view class="task-title">{{ taskInfo.rwmc }}</view>
|
||||||
|
<view class="task-meta">
|
||||||
|
<text class="meta-item">截止时间:{{ formatDate(taskInfo.rwjstime) }}</text>
|
||||||
|
<text class="meta-item">负责人:{{ taskInfo.rwfzrxm }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 完成情况统计 -->
|
||||||
|
<view class="completion-summary">
|
||||||
|
<view class="summary-header">
|
||||||
|
<text class="summary-title">完成情况:{{ completionStats.completed }} | {{ completionStats.total }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Tab切换 -->
|
||||||
|
<view class="tab-container">
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'submitted' }"
|
||||||
|
@click="switchTab('submitted')"
|
||||||
|
>
|
||||||
|
<text class="tab-text">已提交</text>
|
||||||
|
<text class="tab-count">({{ completionStats.completed }})</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'pending' }"
|
||||||
|
@click="switchTab('pending')"
|
||||||
|
>
|
||||||
|
<text class="tab-text">未提交</text>
|
||||||
|
<text class="tab-count">({{ completionStats.pending }})</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 执行人员列表 -->
|
||||||
|
<view class="execution-list">
|
||||||
|
<scroll-view scroll-y class="execution-scroll">
|
||||||
|
<view v-if="loading" class="loading-container">
|
||||||
|
<text class="loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="filteredExecutionList.length === 0" class="empty-state">
|
||||||
|
<text class="empty-text">{{ activeTab === 'submitted' ? '暂无已提交记录' : '暂无未提交记录' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else>
|
||||||
|
<view
|
||||||
|
v-for="execution in filteredExecutionList"
|
||||||
|
:key="execution.id"
|
||||||
|
class="execution-item"
|
||||||
|
@click="viewExecutionDetail(execution)"
|
||||||
|
>
|
||||||
|
<!-- 执行人信息 -->
|
||||||
|
<view class="executor-info">
|
||||||
|
<view class="executor-avatar">
|
||||||
|
<text class="avatar-text">{{ execution.rwzxfzrxm?.charAt(0) || '?' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="executor-details">
|
||||||
|
<text class="executor-name">{{ execution.rwzxfzrxm }}</text>
|
||||||
|
<text class="executor-id">{{ execution.rwzxfzr }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 执行状态 -->
|
||||||
|
<view class="execution-status">
|
||||||
|
<view :class="['status-badge', execution.rwzxzt === 'A' ? 'completed' : 'pending']">
|
||||||
|
<text class="status-text">{{ execution.rwzxzt === 'A' ? '已完成' : '未完成' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 完成时间 -->
|
||||||
|
<view v-if="execution.rwzxzt === 'A' && execution.rwzxtime" class="completion-time">
|
||||||
|
<text class="time-text">{{ formatDateTime(execution.rwzxtime) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 箭头图标 -->
|
||||||
|
<view class="arrow-icon">
|
||||||
|
<text class="arrow-text">→</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评论区域(参考后台界面) -->
|
||||||
|
<view class="comment-section">
|
||||||
|
<view class="comment-header">
|
||||||
|
<text class="comment-title">评论交流</text>
|
||||||
|
<view class="comment-stats">
|
||||||
|
<text class="stats-item">👍 {{ commentStats.likes }}</text>
|
||||||
|
<text class="stats-item">💬 {{ commentStats.comments }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评论列表 -->
|
||||||
|
<scroll-view scroll-y class="comment-list" :style="{ maxHeight: '200px' }">
|
||||||
|
<view v-if="commentList.length === 0" class="empty-comments">
|
||||||
|
<text class="empty-text">暂无评论</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else>
|
||||||
|
<view
|
||||||
|
v-for="comment in commentList"
|
||||||
|
:key="comment.id"
|
||||||
|
class="comment-item"
|
||||||
|
>
|
||||||
|
<view class="comment-user">
|
||||||
|
<view class="comment-avatar">
|
||||||
|
<text class="avatar-text">{{ comment.userName?.charAt(0) || '?' }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="comment-username">{{ comment.userName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="comment-content">
|
||||||
|
<text class="comment-text">{{ comment.content }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="comment-time">
|
||||||
|
<text class="time-text">{{ formatDateTime(comment.createTime) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 评论输入 -->
|
||||||
|
<view class="comment-input-area">
|
||||||
|
<input
|
||||||
|
v-model="newComment"
|
||||||
|
placeholder="发表评论..."
|
||||||
|
class="comment-input"
|
||||||
|
@confirm="submitComment"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="comment-submit-btn"
|
||||||
|
@click="submitComment"
|
||||||
|
:disabled="!newComment.trim()"
|
||||||
|
>
|
||||||
|
发送
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, computed, onLoad } from "vue";
|
||||||
|
import { onLoad as onPageLoad } from "@dcloudio/uni-app";
|
||||||
|
import { executedInfoByRwIdApi } from "@/api/base/rwzxApi";
|
||||||
|
|
||||||
|
// 接口类型定义
|
||||||
|
interface TaskInfo {
|
||||||
|
id: string;
|
||||||
|
rwmc: string;
|
||||||
|
rwms: string;
|
||||||
|
rwjstime: string;
|
||||||
|
rwfzrxm: string;
|
||||||
|
rwfzr: string;
|
||||||
|
rwStatus: string;
|
||||||
|
rwlyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecutionItem {
|
||||||
|
id: string;
|
||||||
|
rwId: string;
|
||||||
|
rwzxfzr: string;
|
||||||
|
rwzxfzrxm: string;
|
||||||
|
iszxwc: string; // A: 已提交, B: 未提交
|
||||||
|
rwzxzt: string; // A: 已完成, B: 未完成
|
||||||
|
rwzxtime: string;
|
||||||
|
rwzxnr: string;
|
||||||
|
createTime: string;
|
||||||
|
jsId?: string; // 教师ID(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentItem {
|
||||||
|
id: string;
|
||||||
|
rwId: string;
|
||||||
|
userName: string;
|
||||||
|
content: string;
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const taskInfo = ref<TaskInfo>({
|
||||||
|
id: '',
|
||||||
|
rwmc: '',
|
||||||
|
rwms: '',
|
||||||
|
rwjstime: '',
|
||||||
|
rwfzrxm: '',
|
||||||
|
rwfzr: '',
|
||||||
|
rwStatus: '',
|
||||||
|
rwlyId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseId = ref('');
|
||||||
|
const activeTab = ref('submitted'); // submitted: 已提交, pending: 未提交
|
||||||
|
const loading = ref(false);
|
||||||
|
const executionList = ref<ExecutionItem[]>([]);
|
||||||
|
const commentList = ref<CommentItem[]>([]);
|
||||||
|
const newComment = ref('');
|
||||||
|
|
||||||
|
const completedCount = ref(0);
|
||||||
|
const pendingCount = ref(0);
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const completionStats = computed(() => {
|
||||||
|
const total = completedCount.value + pendingCount.value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
completed: completedCount.value,
|
||||||
|
pending: pendingCount.value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const commentStats = ref({
|
||||||
|
likes: 1587,
|
||||||
|
comments: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据Tab过滤执行列表(基于iszxwc字段:A=已提交,B=未提交)
|
||||||
|
const filteredExecutionList = computed(() => {
|
||||||
|
return executionList.value.filter(item => {
|
||||||
|
if (activeTab.value === 'submitted') {
|
||||||
|
return item.iszxwc === 'A'; // 已提交
|
||||||
|
} else {
|
||||||
|
return item.iszxwc === 'B'; // 未提交
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onPageLoad((options: any) => {
|
||||||
|
console.log('任务执行页面接收到的参数:', options);
|
||||||
|
|
||||||
|
if (options.taskInfo) {
|
||||||
|
try {
|
||||||
|
taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo));
|
||||||
|
courseId.value = taskInfo.value.rwlyId || options.courseId || '';
|
||||||
|
console.log('任务信息:', taskInfo.value);
|
||||||
|
console.log('课程ID:', courseId.value);
|
||||||
|
|
||||||
|
loadExecutionList();
|
||||||
|
loadCommentList();
|
||||||
|
// 在加载执行列表后再加载统计数据
|
||||||
|
setTimeout(() => {
|
||||||
|
loadStatistics();
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析任务信息失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '参数解析失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('缺少任务信息参数');
|
||||||
|
uni.showToast({
|
||||||
|
title: '缺少任务信息',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载统计数据(基于iszxwc字段统计)
|
||||||
|
const loadStatistics = async () => {
|
||||||
|
try {
|
||||||
|
console.log('开始加载统计数据,任务ID:', taskInfo.value.id);
|
||||||
|
|
||||||
|
// 只调用executedInfoByRwId接口
|
||||||
|
const response = await executedInfoByRwIdApi({ rwId: taskInfo.value.id });
|
||||||
|
console.log('执行情况API响应:', response);
|
||||||
|
|
||||||
|
// 处理API响应数据
|
||||||
|
let allData = [];
|
||||||
|
if (response && response.resultCode === 1) {
|
||||||
|
allData = response.result || response.rows || response.data || [];
|
||||||
|
} else if (Array.isArray(response)) {
|
||||||
|
allData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据iszxwc字段统计:A=已提交,B=未提交
|
||||||
|
const submittedData = allData.filter(item => item.iszxwc === 'A');
|
||||||
|
const pendingData = allData.filter(item => item.iszxwc === 'B');
|
||||||
|
|
||||||
|
completedCount.value = submittedData.length;
|
||||||
|
pendingCount.value = pendingData.length;
|
||||||
|
|
||||||
|
console.log('统计结果 - 已提交:', completedCount.value, '未提交:', pendingCount.value);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载统计数据失败:', error);
|
||||||
|
// 失败时使用默认值
|
||||||
|
completedCount.value = 0;
|
||||||
|
pendingCount.value = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载执行情况列表(只调用executedInfoByRwId接口)
|
||||||
|
const loadExecutionList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
console.log('开始加载任务执行情况,任务ID:', taskInfo.value.id);
|
||||||
|
|
||||||
|
// 只调用executedInfoByRwId接口获取所有数据
|
||||||
|
const response = await executedInfoByRwIdApi({ rwId: taskInfo.value.id });
|
||||||
|
console.log('执行情况API响应:', response);
|
||||||
|
|
||||||
|
// 处理API响应数据(兼容多种格式)
|
||||||
|
let executionData = [];
|
||||||
|
if (response) {
|
||||||
|
if (response.hasOwnProperty('resultCode')) {
|
||||||
|
if (response.resultCode === 1 || response.resultCode === 0) {
|
||||||
|
executionData = response.result || response.rows || response.data || [];
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.message || response?.msg || '获取执行情况失败');
|
||||||
|
}
|
||||||
|
} else if (response.rows || response.data) {
|
||||||
|
executionData = response.rows || response.data || [];
|
||||||
|
} else if (Array.isArray(response)) {
|
||||||
|
executionData = response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置所有数据,根据iszxwc字段区分状态
|
||||||
|
executionList.value = executionData.map(item => ({
|
||||||
|
...item,
|
||||||
|
// 根据iszxwc字段设置rwzxzt状态:A=已完成(已提交),B=未完成(未提交)
|
||||||
|
rwzxzt: item.iszxwc === 'A' ? 'A' : 'B'
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('解析后的执行情况列表:', executionList.value);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载任务执行情况失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载执行情况失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
executionList.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载评论列表
|
||||||
|
const loadCommentList = async () => {
|
||||||
|
try {
|
||||||
|
// TODO: 调用真实的评论API
|
||||||
|
// 模拟评论数据
|
||||||
|
commentList.value = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
rwId: taskInfo.value.id,
|
||||||
|
userName: '测试',
|
||||||
|
content: '发表评论...',
|
||||||
|
createTime: new Date().toISOString()
|
||||||
|
}
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载评论列表失败:', error);
|
||||||
|
commentList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tab切换
|
||||||
|
const switchTab = (tab: string) => {
|
||||||
|
activeTab.value = tab;
|
||||||
|
// Tab切换时不需要重新加载数据,通过computed属性filteredExecutionList自动过滤
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看执行详情
|
||||||
|
const viewExecutionDetail = (execution: ExecutionItem) => {
|
||||||
|
// 跳转到任务提交页面,传递任务ID和教师信息
|
||||||
|
const params = {
|
||||||
|
id: taskInfo.value.id,
|
||||||
|
jsId: execution.rwzxfzr,
|
||||||
|
executionId: execution.id
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramStr = encodeURIComponent(JSON.stringify(params));
|
||||||
|
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/view/routine/yishiyice/kcrwzxtj?params=${paramStr}`,
|
||||||
|
success: () => {
|
||||||
|
console.log('跳转到任务提交页面成功', {
|
||||||
|
taskId: taskInfo.value.id,
|
||||||
|
jsId: execution.rwzxfzr,
|
||||||
|
executionId: execution.id,
|
||||||
|
executionData: execution
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
console.error('跳转到任务提交页面失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '页面跳转失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交评论
|
||||||
|
const submitComment = async () => {
|
||||||
|
if (!newComment.value.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: 调用真实的评论提交API
|
||||||
|
const commentData = {
|
||||||
|
rwId: taskInfo.value.id,
|
||||||
|
content: newComment.value.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('提交评论:', commentData);
|
||||||
|
|
||||||
|
// 模拟添加评论到列表
|
||||||
|
commentList.value.push({
|
||||||
|
id: Date.now().toString(),
|
||||||
|
rwId: taskInfo.value.id,
|
||||||
|
userName: '当前用户', // TODO: 获取当前用户名
|
||||||
|
content: newComment.value.trim(),
|
||||||
|
createTime: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
commentStats.value.comments += 1;
|
||||||
|
newComment.value = '';
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '评论成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交评论失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '评论失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTime = (dateStr: string) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.task-execution-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info-card {
|
||||||
|
margin: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.completion-summary {
|
||||||
|
margin: 0 16px 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.summary-header {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
|
||||||
|
.summary-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-right: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #1890ff;
|
||||||
|
|
||||||
|
.tab-text, .tab-count {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-list {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-scroll {
|
||||||
|
max-height: 300px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.executor-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.executor-avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #1890ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.executor-details {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.executor-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.executor-id {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.execution-status {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
&.completed {
|
||||||
|
background-color: #f6ffed;
|
||||||
|
border: 1px solid #b7eb8f;
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pending {
|
||||||
|
background-color: #fff2e8;
|
||||||
|
border: 1px solid #ffbb96;
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
color: #fa8c16;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.completion-time {
|
||||||
|
.time-text {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.arrow-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-section {
|
||||||
|
margin: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.comment-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.comment-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.stats-item {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-user {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.comment-avatar {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #1890ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-username {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-time {
|
||||||
|
.time-text {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-comments {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.comment-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #1890ff;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-submit-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background-color: #1976d2;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container,
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.loading-text,
|
||||||
|
.empty-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式调整
|
||||||
|
@media screen and (max-width: 375px) {
|
||||||
|
.execution-item {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.executor-avatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-stats {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
431
src/pages/view/routine/yishiyice/kcrwzxtj.vue
Normal file
431
src/pages/view/routine/yishiyice/kcrwzxtj.vue
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
<!-- src/pages/base/message/detail.vue -->
|
||||||
|
<template>
|
||||||
|
<view class="message-detail-page">
|
||||||
|
<view v-if="isLoading" class="loading-indicator">加载中...</view>
|
||||||
|
<view v-else class="detail-content">
|
||||||
|
<view class="detail-header">
|
||||||
|
<view class="title-tag-row">
|
||||||
|
<text class="detail-title">{{ rw.rwmc }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text>{{ rw.rwkstime }}</text>
|
||||||
|
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="detail-body">
|
||||||
|
<BasicForm :schema="schema" v-model="formData">
|
||||||
|
<!-- 注册自定义组件 -->
|
||||||
|
<template #ImageVideoUpload="{ field, label, required, componentProps, onChange }">
|
||||||
|
<ImageVideoUpload
|
||||||
|
v-model:image-list="formData[`${field}_images`]"
|
||||||
|
v-model:video-list="formData[`${field}_videos`]"
|
||||||
|
v-model:file-list="formData[`${field}_files`]"
|
||||||
|
@image-upload-success="(file, index) => onChange?.(file, field)"
|
||||||
|
@video-upload-success="(file, index) => onChange?.(file, field)"
|
||||||
|
@file-upload-success="(file, index) => onChange?.(file, field)"
|
||||||
|
v-bind="componentProps"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</BasicForm>
|
||||||
|
</view>
|
||||||
|
<view class="detail-footer">
|
||||||
|
<button type="primary" class="action-button" @click="saveRwZx">提交</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref} from 'vue';
|
||||||
|
import {onLoad} from '@dcloudio/uni-app';
|
||||||
|
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
|
||||||
|
import {useForm} from "@/components/BasicForm/hooks/useForm";
|
||||||
|
import {navigateBack, showToast} from "@/utils/uniapp";
|
||||||
|
import {useUserStore} from "@/store/modules/user";
|
||||||
|
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
|
||||||
|
import { attachmentUpload } from "@/api/system/upload";
|
||||||
|
|
||||||
|
interface MessageDetail {
|
||||||
|
id: string; // Assuming an ID is passed or can be derived
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
date: string;
|
||||||
|
timeAgo: string;
|
||||||
|
tagText: string;
|
||||||
|
tagType: string;
|
||||||
|
// Add other fields as necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData: any = ref({})
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const jsId = ref<string>(''); // 教师ID
|
||||||
|
const executionId = ref<string>(''); // 执行ID
|
||||||
|
const messageDetail = ref<MessageDetail | null>({
|
||||||
|
id: 'todo1',
|
||||||
|
title: '教务通知 (待办)',
|
||||||
|
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
|
||||||
|
date: '2025-02-17',
|
||||||
|
timeAgo: '8 mins 前',
|
||||||
|
tagText: '通知',
|
||||||
|
tagType: 'notice',
|
||||||
|
likes: 6,
|
||||||
|
comments: 12
|
||||||
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const rwflx: any = ref([])
|
||||||
|
const rw = ref({})
|
||||||
|
const schema = reactive<FormsSchema[]>([])
|
||||||
|
const {getUser} = useUserStore()
|
||||||
|
|
||||||
|
async function saveRwZx() {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
const fieldId = rwflx.value[i].id;
|
||||||
|
let fieldValue = formData.value[fieldId];
|
||||||
|
|
||||||
|
// 处理上传类型的任务
|
||||||
|
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
|
||||||
|
// 收集上传文件的URL
|
||||||
|
const fileUrls = [];
|
||||||
|
|
||||||
|
if (rwflx.value[i].rwfl == "sctp") {
|
||||||
|
// 图片上传
|
||||||
|
const images = formData.value[`${fieldId}_images`] || [];
|
||||||
|
fileUrls.push(...images.map((img: any) => img.url).filter(Boolean));
|
||||||
|
} else if (rwflx.value[i].rwfl == "scsp") {
|
||||||
|
// 视频上传
|
||||||
|
const videos = formData.value[`${fieldId}_videos`] || [];
|
||||||
|
fileUrls.push(...videos.map((video: any) => video.url).filter(Boolean));
|
||||||
|
} else if (rwflx.value[i].rwfl == "scwd") {
|
||||||
|
// 文档上传
|
||||||
|
const files = formData.value[`${fieldId}_files`] || [];
|
||||||
|
fileUrls.push(...files.map((file: any) => file.url).filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldValue = fileUrls.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(44, fieldId, fieldValue)
|
||||||
|
if (rwflx.value[i].rwbs && (!fieldValue || fieldValue == "")) {
|
||||||
|
showToast("请填写必填项!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
rwlxId: fieldId,
|
||||||
|
rwzxqdtx: fieldValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await rwzxSaveApi({
|
||||||
|
id: executionId.value, // 执行记录ID,如果有的话就是更新,没有就是新增
|
||||||
|
rwId: rw.value.id, // 任务ID
|
||||||
|
rwzxfzr: jsId.value || getUser.id, // 执行人(教师ID)
|
||||||
|
mobile: getUser.mobile, // 手机号
|
||||||
|
rwzxqdDtos: result // 执行清单
|
||||||
|
})
|
||||||
|
showToast("操作成功!");
|
||||||
|
uni.navigateBack({delta: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rwzxqds = ref([])
|
||||||
|
onLoad(async (options) => {
|
||||||
|
console.log('页面加载参数:', options);
|
||||||
|
|
||||||
|
let taskId = '';
|
||||||
|
|
||||||
|
// 处理参数接收,兼容多种传参方式
|
||||||
|
if (options && options.params) {
|
||||||
|
try {
|
||||||
|
const params = JSON.parse(decodeURIComponent(options.params));
|
||||||
|
taskId = params.id;
|
||||||
|
jsId.value = params.jsId || '';
|
||||||
|
executionId.value = params.executionId || '';
|
||||||
|
console.log('解析参数成功:', { taskId, jsId: jsId.value, executionId: executionId.value });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析参数失败:', error);
|
||||||
|
taskId = options.id || '';
|
||||||
|
}
|
||||||
|
} else if (options && options.id) {
|
||||||
|
taskId = options.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskId) {
|
||||||
|
const {result} = await rwFindInfoByRwId({
|
||||||
|
rwId: taskId
|
||||||
|
});
|
||||||
|
rwflx.value = result.rwlxes;
|
||||||
|
rw.value = result;
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
// 处理上传类型的任务
|
||||||
|
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
|
||||||
|
// 初始化表单数据
|
||||||
|
const fieldId = rwflx.value[i].id;
|
||||||
|
if (rwflx.value[i].rwfl == "sctp") {
|
||||||
|
formData.value[`${fieldId}_images`] = [];
|
||||||
|
} else if (rwflx.value[i].rwfl == "scsp") {
|
||||||
|
formData.value[`${fieldId}_videos`] = [];
|
||||||
|
} else if (rwflx.value[i].rwfl == "scwd") {
|
||||||
|
formData.value[`${fieldId}_files`] = [];
|
||||||
|
}
|
||||||
|
// 根据任务类型配置上传组件
|
||||||
|
let componentConfig = {};
|
||||||
|
|
||||||
|
if (rwflx.value[i].rwfl == "sctp") {
|
||||||
|
// 上传图片
|
||||||
|
componentConfig = {
|
||||||
|
component: "ImageVideoUpload",
|
||||||
|
componentProps: {
|
||||||
|
enableImage: true,
|
||||||
|
enableVideo: false,
|
||||||
|
enableFile: false,
|
||||||
|
maxImageCount: 5,
|
||||||
|
uploadApi: attachmentUpload,
|
||||||
|
compressConfig: COMPRESS_PRESETS.medium
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (rwflx.value[i].rwfl == "scsp") {
|
||||||
|
// 上传视频
|
||||||
|
componentConfig = {
|
||||||
|
component: "ImageVideoUpload",
|
||||||
|
componentProps: {
|
||||||
|
enableImage: false,
|
||||||
|
enableVideo: true,
|
||||||
|
enableFile: false,
|
||||||
|
maxVideoCount: 3,
|
||||||
|
uploadApi: attachmentUpload,
|
||||||
|
compressConfig: COMPRESS_PRESETS.medium
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (rwflx.value[i].rwfl == "scwd") {
|
||||||
|
// 上传文档
|
||||||
|
componentConfig = {
|
||||||
|
component: "ImageVideoUpload",
|
||||||
|
componentProps: {
|
||||||
|
enableImage: false,
|
||||||
|
enableVideo: false,
|
||||||
|
enableFile: true,
|
||||||
|
maxFileCount: 5,
|
||||||
|
allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'wav', 'zip', 'rar'],
|
||||||
|
uploadApi: attachmentUpload,
|
||||||
|
compressConfig: COMPRESS_PRESETS.medium
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
...componentConfig
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "text") {
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicInput",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
type: "textarea",
|
||||||
|
placeholder: "请输入内容"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
|
||||||
|
let options = rwflx.value[i].remark.split(";");
|
||||||
|
let range = [];
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
range.push({
|
||||||
|
name: options[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicPicker",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
range: range,
|
||||||
|
rangeKey: "name",
|
||||||
|
savaKey: "name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await rwzxExecutedInfoByRwIdAndJsApi({
|
||||||
|
rwId: taskId,
|
||||||
|
jsId: jsId.value || getUser.id // 使用传入的jsId,如果没有则使用当前用户ID
|
||||||
|
});
|
||||||
|
if (res && res.result && res.result.length) {
|
||||||
|
rwzxqds.value = res.result;
|
||||||
|
const showData = {};
|
||||||
|
for (let i = 0; i < rwzxqds.value.length; i++) {
|
||||||
|
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
|
||||||
|
}
|
||||||
|
formData.value = showData;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Message ID/Data is missing!');
|
||||||
|
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.message-detail-page {
|
||||||
|
background-color: #f4f5f7;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.title-tag-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start; // Align items to the top
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
flex: 1; // Allow title to take available space
|
||||||
|
margin-right: 10px; // Space between title and tag
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center; // 居中显示
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag { // Reuse tag styles from index page if possible, or define here
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0; // Prevent tag from shrinking
|
||||||
|
|
||||||
|
&.notice {
|
||||||
|
background-color: #447ade;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.task {
|
||||||
|
background-color: #19be6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.approval {
|
||||||
|
background-color: #ff9f0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.submit {
|
||||||
|
background-color: #8e8e93;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
text {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-body {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.detail-desc {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-footer {
|
||||||
|
// text-align: center; // Removed center alignment
|
||||||
|
// Add margin if needed, e.g., margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style for the action button
|
||||||
|
.action-button {
|
||||||
|
width: 100%; // Make button full width
|
||||||
|
height: 44px; // Standard button height
|
||||||
|
line-height: 44px; // Match height for vertical centering
|
||||||
|
font-size: 16px; // Slightly larger font
|
||||||
|
font-weight: 500; // Medium weight
|
||||||
|
border-radius: 8px; // Consistent border radius
|
||||||
|
margin-top: 20px; // Add space above the button
|
||||||
|
// Ensure primary color is applied correctly (uni-app default should work)
|
||||||
|
// background-color: #447ade;
|
||||||
|
// color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为输入框添加基本边框(参考原生 textFiled 样式)
|
||||||
|
:deep(.uni-input),
|
||||||
|
:deep(.uni-textarea) {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 35px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
border: 1px #CCCCCC solid !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为 BasicForm 组件内的输入框添加边框
|
||||||
|
:deep(.basic-form) {
|
||||||
|
.uni-input,
|
||||||
|
.uni-textarea {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 35px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
border: 1px #CCCCCC solid !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试直接选择 input 和 textarea 标签
|
||||||
|
:deep(input),
|
||||||
|
:deep(textarea) {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 35px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
border: 1px #CCCCCC solid !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
875
src/pages/view/routine/yishiyice/push.vue
Normal file
875
src/pages/view/routine/yishiyice/push.vue
Normal file
@ -0,0 +1,875 @@
|
|||||||
|
<template>
|
||||||
|
<view class="push-page">
|
||||||
|
<!-- 任务信息 -->
|
||||||
|
<view class="task-info-card">
|
||||||
|
<view class="task-title">{{ taskInfo.rwmc }}</view>
|
||||||
|
<view class="task-meta">
|
||||||
|
<text class="meta-item">截止时间:{{ formatDate(taskInfo.rwjstime) }}</text>
|
||||||
|
<text class="meta-item">负责人:{{ taskInfo.rwfzrxm }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 推送对象选择 -->
|
||||||
|
<view class="push-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="section-title">选择推送对象</text>
|
||||||
|
<view class="section-actions">
|
||||||
|
<text class="action-btn" @click="selectAllMembers">全选</text>
|
||||||
|
<text class="action-btn" @click="unselectAllMembers">取消全选</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 成员分组列表 -->
|
||||||
|
<scroll-view scroll-y class="members-scroll">
|
||||||
|
<view
|
||||||
|
v-for="(groupMembers, groupName) in groupedMembers"
|
||||||
|
:key="groupName"
|
||||||
|
class="member-group-section"
|
||||||
|
>
|
||||||
|
<!-- 分组头部 -->
|
||||||
|
<view class="group-header">
|
||||||
|
<view class="group-checkbox-container" @click="handleGroupCheckChange(String(groupName))">
|
||||||
|
<view
|
||||||
|
:class="['checkbox-box', {
|
||||||
|
checked: isGroupChecked(String(groupName)),
|
||||||
|
indeterminate: isGroupIndeterminate(String(groupName))
|
||||||
|
}]"
|
||||||
|
>
|
||||||
|
<text v-if="isGroupChecked(String(groupName))" class="check-icon">✓</text>
|
||||||
|
<text v-else-if="isGroupIndeterminate(String(groupName))" class="check-icon">-</text>
|
||||||
|
</view>
|
||||||
|
<view class="group-info">
|
||||||
|
<text class="group-title">{{ groupName }}</text>
|
||||||
|
<text class="group-count">({{ groupMembers.length }}人)</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分组成员列表 -->
|
||||||
|
<view class="group-members-list">
|
||||||
|
<view class="members-grid">
|
||||||
|
<view
|
||||||
|
v-for="member in groupMembers"
|
||||||
|
:key="member.id"
|
||||||
|
class="member-check-item"
|
||||||
|
@click="handleMemberCheckChange(member.id)"
|
||||||
|
>
|
||||||
|
<view :class="['checkbox-box', { checked: selectedMembers[member.id] }]">
|
||||||
|
<text v-if="selectedMembers[member.id]" class="check-icon">✓</text>
|
||||||
|
</view>
|
||||||
|
<view class="member-info">
|
||||||
|
<view class="member-avatar">
|
||||||
|
<text class="avatar-text">{{ member.jsxm?.charAt(0) || '?' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="member-details">
|
||||||
|
<text class="member-name">{{ member.jsxm }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view v-if="Object.keys(groupedMembers).length === 0" class="empty-state">
|
||||||
|
<text class="empty-text">暂无课程成员</text>
|
||||||
|
<text class="empty-hint">请先添加课程成员</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 推送统计 -->
|
||||||
|
<view class="push-summary">
|
||||||
|
<text class="summary-text">已选择 {{ selectedCount }} 名成员</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部操作按钮 -->
|
||||||
|
<view class="bottom-actions">
|
||||||
|
<view class="action-buttons">
|
||||||
|
<button class="cancel-btn" @click="goBack" :disabled="isSubmitting">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="confirm-btn"
|
||||||
|
@click="handleConfirmPush"
|
||||||
|
:disabled="isSubmitting || selectedCount === 0"
|
||||||
|
>
|
||||||
|
{{ isSubmitting ? '推送中...' : '确认推送' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 推送遮罩层 -->
|
||||||
|
<view v-if="isSubmitting" class="push-overlay">
|
||||||
|
<view class="push-loading">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
<text class="loading-text">正在推送任务...</text>
|
||||||
|
<text class="loading-hint">请稍候,不要重复点击</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, computed } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
import { kccyFindByKcjbIdApi } from "@/api/base/kccyApi";
|
||||||
|
import { rwPushJsApi } from "@/api/base/rwApi";
|
||||||
|
|
||||||
|
// 接口类型定义
|
||||||
|
interface TaskInfo {
|
||||||
|
id: string;
|
||||||
|
rwmc: string;
|
||||||
|
rwms: string;
|
||||||
|
rwjstime: string;
|
||||||
|
rwfzrxm: string;
|
||||||
|
rwfzr: string;
|
||||||
|
rwStatus: string;
|
||||||
|
rwlyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MemberItem {
|
||||||
|
id: string;
|
||||||
|
jsId: string;
|
||||||
|
jsxm: string;
|
||||||
|
kcjbId: string;
|
||||||
|
fzmc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式数<EFBFBD>?
|
||||||
|
const taskInfo = ref<TaskInfo>({
|
||||||
|
id: '',
|
||||||
|
rwmc: '',
|
||||||
|
rwms: '',
|
||||||
|
rwjstime: '',
|
||||||
|
rwfzrxm: '',
|
||||||
|
rwfzr: '',
|
||||||
|
rwStatus: '',
|
||||||
|
rwlyId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const courseId = ref('');
|
||||||
|
const memberList = ref<MemberItem[]>([]);
|
||||||
|
const selectedMembers = reactive<{ [key: string]: boolean }>({});
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
const lastClickTime = ref(0);
|
||||||
|
const DEBOUNCE_DELAY = 2000; // 防抖延迟2秒
|
||||||
|
|
||||||
|
// 计算属<EFBFBD>?
|
||||||
|
const groupedMembers = computed(() => {
|
||||||
|
const groups: { [key: string]: MemberItem[] } = {};
|
||||||
|
|
||||||
|
memberList.value.forEach(member => {
|
||||||
|
const groupName = member.fzmc || '未分组';
|
||||||
|
if (!groups[groupName]) {
|
||||||
|
groups[groupName] = [];
|
||||||
|
}
|
||||||
|
groups[groupName].push(member);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCount = computed(() => {
|
||||||
|
return Object.values(selectedMembers).filter(Boolean).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查分组是否全
|
||||||
|
const isGroupChecked = (groupName: string) => {
|
||||||
|
const groupMembers = groupedMembers.value[groupName] || [];
|
||||||
|
return groupMembers.length > 0 && groupMembers.every(member => selectedMembers[member.id]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查分组是否半
|
||||||
|
const isGroupIndeterminate = (groupName: string) => {
|
||||||
|
const groupMembers = groupedMembers.value[groupName] || [];
|
||||||
|
const checkedCount = groupMembers.filter(member => selectedMembers[member.id]).length;
|
||||||
|
return checkedCount > 0 && checkedCount < groupMembers.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onLoad((options: any) => {
|
||||||
|
console.log('推送页面接收到的参数:', options);
|
||||||
|
|
||||||
|
// 从全局存储中获取任务数据
|
||||||
|
const storedTaskData = uni.getStorageSync('pushTaskData');
|
||||||
|
|
||||||
|
if (storedTaskData) {
|
||||||
|
try {
|
||||||
|
taskInfo.value = storedTaskData;
|
||||||
|
courseId.value = taskInfo.value.rwlyId || options.courseId || '';
|
||||||
|
console.log('任务信息:', taskInfo.value);
|
||||||
|
console.log('课程ID:', courseId.value);
|
||||||
|
|
||||||
|
// 清除存储的任务数据
|
||||||
|
uni.removeStorageSync('pushTaskData');
|
||||||
|
|
||||||
|
loadCourseMembers();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析任务信息失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '参数解析失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('缺少任务信息参数');
|
||||||
|
uni.showToast({
|
||||||
|
title: '缺少任务信息',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载课程成员
|
||||||
|
const loadCourseMembers = async () => {
|
||||||
|
try {
|
||||||
|
console.log('开始加载课程成员,课程ID:', courseId.value);
|
||||||
|
|
||||||
|
const response = await kccyFindByKcjbIdApi({ kcjbId: courseId.value });
|
||||||
|
console.log('课程成员API响应:', response);
|
||||||
|
|
||||||
|
// 处理API响应数据(兼容多种格式)
|
||||||
|
let memberData = [];
|
||||||
|
if (response) {
|
||||||
|
if (response.hasOwnProperty('resultCode')) {
|
||||||
|
if (response.resultCode === 1 || response.resultCode === 0) {
|
||||||
|
memberData = response.result || response.rows || response.data || [];
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.message || response?.msg || '获取课程成员失败');
|
||||||
|
}
|
||||||
|
} else if (response.rows || response.data) {
|
||||||
|
memberData = response.rows || response.data || [];
|
||||||
|
} else if (Array.isArray(response)) {
|
||||||
|
memberData = response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memberList.value = memberData;
|
||||||
|
console.log('解析后的成员列表:', memberList.value);
|
||||||
|
|
||||||
|
// 默认全选所有成员
|
||||||
|
memberList.value.forEach(member => {
|
||||||
|
selectedMembers[member.id] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课程成员失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '加载成员失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
memberList.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理分组选择变化
|
||||||
|
const handleGroupCheckChange = (groupName: string) => {
|
||||||
|
const groupMembers = groupedMembers.value[groupName] || [];
|
||||||
|
const isChecked = isGroupChecked(groupName);
|
||||||
|
|
||||||
|
groupMembers.forEach(member => {
|
||||||
|
selectedMembers[member.id] = !isChecked;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理单个成员选择变化
|
||||||
|
const handleMemberCheckChange = (memberId: string) => {
|
||||||
|
selectedMembers[memberId] = !selectedMembers[memberId];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全
|
||||||
|
const selectAllMembers = () => {
|
||||||
|
memberList.value.forEach(member => {
|
||||||
|
selectedMembers[member.id] = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消全
|
||||||
|
const unselectAllMembers = () => {
|
||||||
|
memberList.value.forEach(member => {
|
||||||
|
selectedMembers[member.id] = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 防抖处理确认推送
|
||||||
|
const handleConfirmPush = () => {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
// 防抖检查:如果在2秒内重复点击,则忽略
|
||||||
|
if (currentTime - lastClickTime.value < DEBOUNCE_DELAY) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请勿重复点击,请稍候',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 1500
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在提交中,直接返回
|
||||||
|
if (isSubmitting.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '正在推送中,请稍候',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 1500
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastClickTime.value = currentTime;
|
||||||
|
confirmPush();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认推送
|
||||||
|
const confirmPush = async () => {
|
||||||
|
if (selectedCount.value === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择推送对象',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取选中的成员
|
||||||
|
const selectedMemberList = memberList.value.filter(member => selectedMembers[member.id]);
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认推送',
|
||||||
|
content: `确定要推送任务"${taskInfo.value.rwmc}"给${selectedCount.value}名成员吗?`,
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
await executePush(selectedMemberList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行推
|
||||||
|
const executePush = async (selectedMemberList: MemberItem[]) => {
|
||||||
|
isSubmitting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 调用推送API
|
||||||
|
const pushData = {
|
||||||
|
rwId: taskInfo.value.id,
|
||||||
|
jsIds: selectedMemberList.map(member => member.jsId).join(','),
|
||||||
|
rwlyId: courseId.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await rwPushJsApi(pushData);
|
||||||
|
console.log('推送API响应:', response);
|
||||||
|
|
||||||
|
if (response && response.resultCode === 1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `推送成功!已推送给${selectedCount.value}名成员`,
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.message || '推送失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('推送失败', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: '推送失败,请重试',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回上一<EFBFBD>?
|
||||||
|
const goBack = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.push-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #e8f4fd 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info-card {
|
||||||
|
margin: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(0, 0, 0, 0.06),
|
||||||
|
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #409eff 0%, #67c23a 50%, #e6a23c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.4;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.push-section {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 16px 80px 16px;
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(0, 0, 0, 0.06),
|
||||||
|
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #409eff 0%, #67c23a 50%, #e6a23c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
margin-top: 3px;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(64, 158, 255, 0.1);
|
||||||
|
border: 1px solid rgba(64, 158, 255, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: rgba(64, 158, 255, 0.2);
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.members-scroll {
|
||||||
|
flex: 1;
|
||||||
|
max-height: 60vh;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-group-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.group-checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.group-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-count {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-members-list {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.members-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-check-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
border-color: #409eff;
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.member-avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 1px 4px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-details {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.member-name {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1f2937;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-box {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid #d1d5db;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.indeterminate {
|
||||||
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.push-summary {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: linear-gradient(135deg, #f6ffed 0%, #f0f9ff 100%);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.summary-text {
|
||||||
|
color: #52c41a;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
padding: 12px 16px;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.cancel-btn, .confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #909399;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #82848a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, #3a8ee6 0%, #337ecc 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式调<EFBFBD>?
|
||||||
|
@media screen and (max-width: 375px) {
|
||||||
|
.section-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-check-item {
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
.member-avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推送遮罩层 */
|
||||||
|
.push-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
|
||||||
|
.push-loading {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid rgba(64, 158, 255, 0.2);
|
||||||
|
border-top: 3px solid #409eff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 遮罩层响应式调整 */
|
||||||
|
@media screen and (max-width: 375px) {
|
||||||
|
.push-overlay .push-loading {
|
||||||
|
margin: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -95,7 +95,7 @@ onLoad(async (options) => {
|
|||||||
rwflx.value = result.rwlxes;
|
rwflx.value = result.rwlxes;
|
||||||
rw.value = result;
|
rw.value = result;
|
||||||
for (let i = 0; i < rwflx.value.length; i++) {
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
|
if (rwflx.value[i].rwfl == "sczy") {
|
||||||
schema.push({
|
schema.push({
|
||||||
field: `${rwflx.value[i].id}`,
|
field: `${rwflx.value[i].id}`,
|
||||||
label: `${rwflx.value[i].rwbt}`,
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
|||||||
@ -1,273 +1,322 @@
|
|||||||
<!-- src/pages/base/message/detail.vue -->
|
|
||||||
<template>
|
<template>
|
||||||
<view class="message-detail-page">
|
<div class="container">
|
||||||
<view v-if="isLoading" class="loading-indicator">加载中...</view>
|
<!-- 搜索框 -->
|
||||||
<view v-else class="detail-content">
|
<div class="search-form">
|
||||||
<view class="detail-header">
|
<input
|
||||||
<view class="title-tag-row">
|
v-model="searchKeyword"
|
||||||
<text class="detail-title">{{ rw.rwmc }}</text>
|
type="text"
|
||||||
<view class="tag" :class="rw.tagType">{{ rw.tagText }}</view>
|
placeholder="搜索任务..."
|
||||||
</view>
|
class="search-input"
|
||||||
<view class="detail-meta">
|
@input="onSearch"
|
||||||
<text>{{ rw.rwkstime }}</text>
|
/>
|
||||||
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
|
</div>
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="detail-body">
|
|
||||||
<BasicForm :schema="schema" v-model="formData">
|
|
||||||
|
|
||||||
</BasicForm>
|
<!-- 任务列表 -->
|
||||||
</view>
|
<div class="task-list">
|
||||||
<view class="detail-footer" v-if="rwzxqds.length==0">
|
<div
|
||||||
<button type="primary" class="action-button" @click="saveRwZx">处理</button>
|
v-for="task in filteredTasks"
|
||||||
</view>
|
:key="task.id"
|
||||||
</view>
|
class="item"
|
||||||
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
|
@click="handleTaskClick(task)"
|
||||||
</view>
|
>
|
||||||
|
<div class="item-title">{{ task.rwmc || '任务名称' }}</div>
|
||||||
|
<div class="detail">
|
||||||
|
任务描述:<span class="detail-content">{{ task.rwms || '暂无描述' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
执行状态:<span class="detail-content" :class="getStatusClass(task.zxzt)">{{ getStatusText(task.zxzt) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
发布时间:<span class="detail-content">{{ formatDate(task.fbsj) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
截止时间:<span class="detail-content">{{ formatDate(task.jzsj) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail" v-if="task.zxjg">
|
||||||
|
执行结果:<span class="detail-content">{{ task.zxjg }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="filteredTasks.length === 0 && !loading" class="empty-state">
|
||||||
|
<div class="empty-text">暂无任务数据</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading-state">
|
||||||
|
<div class="loading-text">加载中...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script>
|
||||||
import {ref} from 'vue';
|
import { rwzxFindAllApi } from '@/api/base/rwzxApi'
|
||||||
import {onLoad} from '@dcloudio/uni-app';
|
|
||||||
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
|
|
||||||
import {useForm} from "@/components/BasicForm/hooks/useForm";
|
|
||||||
import {navigateBack, showToast} from "@/utils/uniapp";
|
|
||||||
import {useUserStore} from "@/store/modules/user";
|
|
||||||
|
|
||||||
interface MessageDetail {
|
export default {
|
||||||
id: string; // Assuming an ID is passed or can be derived
|
name: 'TaskList',
|
||||||
title: string;
|
data() {
|
||||||
desc: string;
|
return {
|
||||||
date: string;
|
tasks: [], // 任务列表
|
||||||
timeAgo: string;
|
filteredTasks: [], // 过滤后的任务列表
|
||||||
tagText: string;
|
searchKeyword: '', // 搜索关键词
|
||||||
tagType: string;
|
loading: false, // 加载状态
|
||||||
// Add other fields as necessary
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadTasks()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 加载任务列表
|
||||||
|
async loadTasks() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const response = await rwzxFindAllApi()
|
||||||
|
console.log('API响应数据:', response)
|
||||||
|
|
||||||
const formData: any = ref({})
|
// 手机端API响应格式:resultCode: 1 表示成功,rows 包含数据
|
||||||
const messageId = ref<string>('');
|
if (response && (response.resultCode === 1 || response.resultCode === 0)) {
|
||||||
const messageDetail = ref<MessageDetail | null>({
|
this.tasks = response.result || response.rows || response.data || []
|
||||||
id: 'todo1',
|
this.filteredTasks = [...this.tasks]
|
||||||
title: '教务通知 (待办)',
|
console.log('任务列表加载成功:', this.tasks)
|
||||||
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
|
|
||||||
date: '2025-02-17',
|
|
||||||
timeAgo: '8 mins 前',
|
|
||||||
tagText: '通知',
|
|
||||||
tagType: 'notice',
|
|
||||||
likes: 6,
|
|
||||||
comments: 12
|
|
||||||
});
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const rwflx: any = ref([])
|
|
||||||
const rw = ref({})
|
|
||||||
const schema = reactive<FormsSchema[]>([])
|
|
||||||
const {getUser} = useUserStore()
|
|
||||||
|
|
||||||
async function saveRwZx() {
|
|
||||||
const result = [];
|
|
||||||
for (let i = 0; i < rwflx.value.length; i++) {
|
|
||||||
console.log(44, rwflx.value[i].id, formData.value[rwflx.value[i].id])
|
|
||||||
if (rwflx.value[i].rwbs && (!formData.value[rwflx.value[i].id] || formData.value[rwflx.value[i].id] == "")) {
|
|
||||||
showToast("请填写必填项!")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
result.push({
|
|
||||||
rwlxId: rwflx.value[i].id,
|
|
||||||
rwzxqdtx: formData.value[rwflx.value[i].id],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await rwzxSaveApi({
|
|
||||||
mobile: getUser.mobile,
|
|
||||||
rwId: rw.value.id,
|
|
||||||
rwzxqdDtos: result
|
|
||||||
})
|
|
||||||
showToast("操作成功!");
|
|
||||||
uni.navigateBack({delta: 1})
|
|
||||||
}
|
|
||||||
|
|
||||||
const rwzxqds = ref([])
|
|
||||||
onLoad(async (options) => {
|
|
||||||
if (options && options.id) {
|
|
||||||
const {result} = await rwFindInfoByRwId({
|
|
||||||
rwId: options.id
|
|
||||||
});
|
|
||||||
rwflx.value = result.rwlxes;
|
|
||||||
rw.value = result;
|
|
||||||
for (let i = 0; i < rwflx.value.length; i++) {
|
|
||||||
if (rwflx.value[i].rwfl == "sctp" || rwflx.value[i].rwfl == "scsp" || rwflx.value[i].rwfl == "scwd") {
|
|
||||||
schema.push({
|
|
||||||
field: `${rwflx.value[i].id}`,
|
|
||||||
label: `${rwflx.value[i].rwbt}`,
|
|
||||||
component: "BasicUpload",
|
|
||||||
required: rwflx.value[i].rwbs,
|
|
||||||
componentProps: {}
|
|
||||||
})
|
|
||||||
} else if (rwflx.value[i].rwfl == "text") {
|
|
||||||
schema.push({
|
|
||||||
field: `${rwflx.value[i].id}`,
|
|
||||||
label: `${rwflx.value[i].rwbt}`,
|
|
||||||
component: "BasicInput",
|
|
||||||
required: rwflx.value[i].rwbs,
|
|
||||||
itemProps: {
|
|
||||||
labelPosition: "top",
|
|
||||||
},
|
|
||||||
componentProps: {
|
|
||||||
type: "textarea",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
|
|
||||||
let options = rwflx.value[i].remark.split(";");
|
|
||||||
let range = [];
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
|
||||||
range.push({
|
|
||||||
name: options[i]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
schema.push({
|
|
||||||
field: `${rwflx.value[i].id}`,
|
|
||||||
label: `${rwflx.value[i].rwbt}`,
|
|
||||||
component: "BasicPicker",
|
|
||||||
required: rwflx.value[i].rwbs,
|
|
||||||
itemProps: {
|
|
||||||
labelPosition: "top",
|
|
||||||
},
|
|
||||||
componentProps: {
|
|
||||||
range: range,
|
|
||||||
rangeKey: "name",
|
|
||||||
savaKey: "name",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await rwzxExecutedInfoByRwIdAndJsApi({
|
|
||||||
rwId: options.id,
|
|
||||||
mobile: getUser.mobile
|
|
||||||
});
|
|
||||||
if (res && res.result && res.result.length) {
|
|
||||||
rwzxqds.value = res.result;
|
|
||||||
const showData = {};
|
|
||||||
for (let i = 0; i < rwzxqds.value.length; i++) {
|
|
||||||
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
|
|
||||||
}
|
|
||||||
formData.value = showData;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Message ID/Data is missing!');
|
const errorMsg = response?.message || response?.msg || '获取任务列表失败'
|
||||||
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
|
console.error('获取任务列表失败:', errorMsg)
|
||||||
|
alert(errorMsg)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载任务列表失败:', error)
|
||||||
|
alert('网络错误,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 搜索功能
|
||||||
|
onSearch() {
|
||||||
|
if (!this.searchKeyword.trim()) {
|
||||||
|
this.filteredTasks = [...this.tasks]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = this.searchKeyword.toLowerCase()
|
||||||
|
this.filteredTasks = this.tasks.filter(task => {
|
||||||
|
return (
|
||||||
|
(task.rwmc && task.rwmc.toLowerCase().includes(keyword)) ||
|
||||||
|
(task.rwms && task.rwms.toLowerCase().includes(keyword)) ||
|
||||||
|
(task.zxjg && task.zxjg.toLowerCase().includes(keyword))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 任务点击事件
|
||||||
|
handleTaskClick(task) {
|
||||||
|
// 可以跳转到任务详情页面或执行其他操作
|
||||||
|
console.log('点击任务:', task)
|
||||||
|
// 这里可以添加路由跳转逻辑
|
||||||
|
// this.$router.push(`/task/detail/${task.id}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
getStatusText(status) {
|
||||||
|
const statusMap = {
|
||||||
|
'0': '未开始',
|
||||||
|
'1': '进行中',
|
||||||
|
'2': '已完成',
|
||||||
|
'3': '已逾期',
|
||||||
|
'': '未知状态'
|
||||||
|
}
|
||||||
|
return statusMap[status] || '未知状态'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取状态样式类
|
||||||
|
getStatusClass(status) {
|
||||||
|
const classMap = {
|
||||||
|
'0': 'status-pending',
|
||||||
|
'1': 'status-progress',
|
||||||
|
'2': 'status-completed',
|
||||||
|
'3': 'status-overdue'
|
||||||
|
}
|
||||||
|
return classMap[status] || 'status-unknown'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
formatDate(dateStr) {
|
||||||
|
if (!dateStr) return '暂无'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const date = new Date(dateStr)
|
||||||
|
if (isNaN(date.getTime())) return dateStr
|
||||||
|
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||||
|
} catch (error) {
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
.message-detail-page {
|
/* 基础样式 */
|
||||||
background-color: #f4f5f7;
|
.container {
|
||||||
|
padding: 20px 10px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 15px;
|
background-color: #F6F9FC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框样式 */
|
||||||
|
.search-form {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #CCCCCC;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: white;
|
||||||
|
outline: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-indicator,
|
.search-input:focus {
|
||||||
.empty-state {
|
border-color: #409EFF;
|
||||||
text-align: center;
|
box-shadow: 0 0 5px rgba(64, 158, 255, 0.3);
|
||||||
color: #999;
|
}
|
||||||
padding: 40px 15px;
|
|
||||||
font-size: 14px;
|
/* 任务列表样式 */
|
||||||
|
.task-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin: 5px 0;
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: left;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 3px 0 8px 0;
|
||||||
|
border-bottom: 1px solid #EEEEEE;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 2em;
|
||||||
|
color: #666666;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-content {
|
.detail-content {
|
||||||
background-color: #fff;
|
margin-left: 8px;
|
||||||
border-radius: 8px;
|
flex: 1;
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header {
|
/* 状态样式 */
|
||||||
border-bottom: 1px solid #f0f0f0;
|
.status-pending {
|
||||||
padding-bottom: 15px;
|
color: #909399;
|
||||||
margin-bottom: 20px;
|
}
|
||||||
|
|
||||||
.title-tag-row {
|
.status-progress {
|
||||||
|
color: #409EFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-completed {
|
||||||
|
color: #67C23A;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-overdue {
|
||||||
|
color: #F56C6C;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-unknown {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态样式 */
|
||||||
|
.empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: flex-start; // Align items to the top
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-title {
|
.empty-text {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
color: #909399;
|
||||||
color: #333;
|
margin-top: 20px;
|
||||||
flex: 1; // Allow title to take available space
|
|
||||||
margin-right: 10px; // Space between title and tag
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag { // Reuse tag styles from index page if possible, or define here
|
/* 加载状态样式 */
|
||||||
padding: 4px 8px;
|
.loading-state {
|
||||||
border-radius: 4px;
|
display: flex;
|
||||||
font-size: 12px;
|
flex-direction: column;
|
||||||
font-weight: bold;
|
align-items: center;
|
||||||
color: #ffffff;
|
justify-content: center;
|
||||||
white-space: nowrap;
|
padding: 40px 20px;
|
||||||
flex-shrink: 0; // Prevent tag from shrinking
|
text-align: center;
|
||||||
|
|
||||||
&.notice {
|
|
||||||
background-color: #447ade;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.task {
|
.loading-text {
|
||||||
background-color: #19be6b;
|
font-size: 14px;
|
||||||
|
color: #409EFF;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.approval {
|
/* 响应式适配 */
|
||||||
background-color: #ff9f0a;
|
@media (max-width: 480px) {
|
||||||
|
.container {
|
||||||
|
padding: 15px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.submit {
|
.item {
|
||||||
background-color: #8e8e93;
|
padding: 12px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
.detail-meta {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
|
|
||||||
text {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-body {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
|
|
||||||
.detail-desc {
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #555;
|
|
||||||
line-height: 1.7;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-footer {
|
.detail {
|
||||||
// text-align: center; // Removed center alignment
|
font-size: 12px;
|
||||||
// Add margin if needed, e.g., margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style for the action button
|
|
||||||
.action-button {
|
|
||||||
width: 100%; // Make button full width
|
|
||||||
height: 44px; // Standard button height
|
|
||||||
line-height: 44px; // Match height for vertical centering
|
|
||||||
font-size: 16px; // Slightly larger font
|
|
||||||
font-weight: 500; // Medium weight
|
|
||||||
border-radius: 8px; // Consistent border radius
|
|
||||||
margin-top: 20px; // Add space above the button
|
|
||||||
// Ensure primary color is applied correctly (uni-app default should work)
|
|
||||||
// background-color: #447ade;
|
|
||||||
// color: #ffffff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
273
src/pages/view/rw/indexbak.vue
Normal file
273
src/pages/view/rw/indexbak.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<!-- src/pages/base/message/detail.vue -->
|
||||||
|
<template>
|
||||||
|
<view class="message-detail-page">
|
||||||
|
<view v-if="isLoading" class="loading-indicator">加载中...</view>
|
||||||
|
<view v-else class="detail-content">
|
||||||
|
<view class="detail-header">
|
||||||
|
<view class="title-tag-row">
|
||||||
|
<text class="detail-title">{{ rw.rwmc }}</text>
|
||||||
|
<view class="tag" :class="rw.tagType">{{ rw.tagText }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text>{{ rw.rwkstime }}</text>
|
||||||
|
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="detail-body">
|
||||||
|
<BasicForm :schema="schema" v-model="formData">
|
||||||
|
|
||||||
|
</BasicForm>
|
||||||
|
</view>
|
||||||
|
<view class="detail-footer" v-if="rwzxqds.length==0">
|
||||||
|
<button type="primary" class="action-button" @click="saveRwZx">处理</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref} from 'vue';
|
||||||
|
import {onLoad} from '@dcloudio/uni-app';
|
||||||
|
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
|
||||||
|
import {useForm} from "@/components/BasicForm/hooks/useForm";
|
||||||
|
import {navigateBack, showToast} from "@/utils/uniapp";
|
||||||
|
import {useUserStore} from "@/store/modules/user";
|
||||||
|
|
||||||
|
interface MessageDetail {
|
||||||
|
id: string; // Assuming an ID is passed or can be derived
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
date: string;
|
||||||
|
timeAgo: string;
|
||||||
|
tagText: string;
|
||||||
|
tagType: string;
|
||||||
|
// Add other fields as necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData: any = ref({})
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const messageDetail = ref<MessageDetail | null>({
|
||||||
|
id: 'todo1',
|
||||||
|
title: '教务通知 (待办)',
|
||||||
|
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
|
||||||
|
date: '2025-02-17',
|
||||||
|
timeAgo: '8 mins 前',
|
||||||
|
tagText: '通知',
|
||||||
|
tagType: 'notice',
|
||||||
|
likes: 6,
|
||||||
|
comments: 12
|
||||||
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const rwflx: any = ref([])
|
||||||
|
const rw = ref({})
|
||||||
|
const schema = reactive<FormsSchema[]>([])
|
||||||
|
const {getUser} = useUserStore()
|
||||||
|
|
||||||
|
async function saveRwZx() {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
console.log(44, rwflx.value[i].id, formData.value[rwflx.value[i].id])
|
||||||
|
if (rwflx.value[i].rwbs && (!formData.value[rwflx.value[i].id] || formData.value[rwflx.value[i].id] == "")) {
|
||||||
|
showToast("请填写必填项!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
rwlxId: rwflx.value[i].id,
|
||||||
|
rwzxqdtx: formData.value[rwflx.value[i].id],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await rwzxSaveApi({
|
||||||
|
mobile: getUser.mobile,
|
||||||
|
rwId: rw.value.id,
|
||||||
|
rwzxqdDtos: result
|
||||||
|
})
|
||||||
|
showToast("操作成功!");
|
||||||
|
uni.navigateBack({delta: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rwzxqds = ref([])
|
||||||
|
onLoad(async (options) => {
|
||||||
|
if (options && options.id) {
|
||||||
|
const {result} = await rwFindInfoByRwId({
|
||||||
|
rwId: options.id
|
||||||
|
});
|
||||||
|
rwflx.value = result.rwlxes;
|
||||||
|
rw.value = result;
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
if (rwflx.value[i].rwfl == "sczy") {
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicUpload",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
componentProps: {}
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "text") {
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicInput",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
|
||||||
|
let options = rwflx.value[i].remark.split(";");
|
||||||
|
let range = [];
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
range.push({
|
||||||
|
name: options[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicPicker",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
range: range,
|
||||||
|
rangeKey: "name",
|
||||||
|
savaKey: "name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await rwzxExecutedInfoByRwIdAndJsApi({
|
||||||
|
rwId: options.id,
|
||||||
|
mobile: getUser.mobile
|
||||||
|
});
|
||||||
|
if (res && res.result && res.result.length) {
|
||||||
|
rwzxqds.value = res.result;
|
||||||
|
const showData = {};
|
||||||
|
for (let i = 0; i < rwzxqds.value.length; i++) {
|
||||||
|
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
|
||||||
|
}
|
||||||
|
formData.value = showData;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Message ID/Data is missing!');
|
||||||
|
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.message-detail-page {
|
||||||
|
background-color: #f4f5f7;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.title-tag-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start; // Align items to the top
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
flex: 1; // Allow title to take available space
|
||||||
|
margin-right: 10px; // Space between title and tag
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag { // Reuse tag styles from index page if possible, or define here
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0; // Prevent tag from shrinking
|
||||||
|
|
||||||
|
&.notice {
|
||||||
|
background-color: #447ade;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.task {
|
||||||
|
background-color: #19be6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.approval {
|
||||||
|
background-color: #ff9f0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.submit {
|
||||||
|
background-color: #8e8e93;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
text {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-body {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.detail-desc {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-footer {
|
||||||
|
// text-align: center; // Removed center alignment
|
||||||
|
// Add margin if needed, e.g., margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style for the action button
|
||||||
|
.action-button {
|
||||||
|
width: 100%; // Make button full width
|
||||||
|
height: 44px; // Standard button height
|
||||||
|
line-height: 44px; // Match height for vertical centering
|
||||||
|
font-size: 16px; // Slightly larger font
|
||||||
|
font-weight: 500; // Medium weight
|
||||||
|
border-radius: 8px; // Consistent border radius
|
||||||
|
margin-top: 20px; // Add space above the button
|
||||||
|
// Ensure primary color is applied correctly (uni-app default should work)
|
||||||
|
// background-color: #447ade;
|
||||||
|
// color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
273
src/pages/view/rw/rwlist.vue
Normal file
273
src/pages/view/rw/rwlist.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<!-- src/pages/base/message/detail.vue -->
|
||||||
|
<template>
|
||||||
|
<view class="message-detail-page">
|
||||||
|
<view v-if="isLoading" class="loading-indicator">加载中...</view>
|
||||||
|
<view v-else class="detail-content">
|
||||||
|
<view class="detail-header">
|
||||||
|
<view class="title-tag-row">
|
||||||
|
<text class="detail-title">{{ rw.rwmc }}</text>
|
||||||
|
<view class="tag" :class="rw.tagType">{{ rw.tagText }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text>{{ rw.rwkstime }}</text>
|
||||||
|
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="detail-body">
|
||||||
|
<BasicForm :schema="schema" v-model="formData">
|
||||||
|
|
||||||
|
</BasicForm>
|
||||||
|
</view>
|
||||||
|
<view class="detail-footer" v-if="rwzxqds.length==0">
|
||||||
|
<button type="primary" class="action-button" @click="saveRwZx">处理</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref} from 'vue';
|
||||||
|
import {onLoad} from '@dcloudio/uni-app';
|
||||||
|
import {rwFindInfoByRwId, rwflFindRwlxsByRwId, rwzxExecutedInfoByRwIdAndJsApi, rwzxSaveApi} from "@/api/base/server";
|
||||||
|
import {useForm} from "@/components/BasicForm/hooks/useForm";
|
||||||
|
import {navigateBack, showToast} from "@/utils/uniapp";
|
||||||
|
import {useUserStore} from "@/store/modules/user";
|
||||||
|
|
||||||
|
interface MessageDetail {
|
||||||
|
id: string; // Assuming an ID is passed or can be derived
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
date: string;
|
||||||
|
timeAgo: string;
|
||||||
|
tagText: string;
|
||||||
|
tagType: string;
|
||||||
|
// Add other fields as necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData: any = ref({})
|
||||||
|
const messageId = ref<string>('');
|
||||||
|
const messageDetail = ref<MessageDetail | null>({
|
||||||
|
id: 'todo1',
|
||||||
|
title: '教务通知 (待办)',
|
||||||
|
desc: '学校召开期初教学准备会议暨首次教学工作例会. 会议强调了新学期的教学重点和要求,请各位老师认真准备。',
|
||||||
|
date: '2025-02-17',
|
||||||
|
timeAgo: '8 mins 前',
|
||||||
|
tagText: '通知',
|
||||||
|
tagType: 'notice',
|
||||||
|
likes: 6,
|
||||||
|
comments: 12
|
||||||
|
});
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const rwflx: any = ref([])
|
||||||
|
const rw = ref({})
|
||||||
|
const schema = reactive<FormsSchema[]>([])
|
||||||
|
const {getUser} = useUserStore()
|
||||||
|
|
||||||
|
async function saveRwZx() {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
console.log(44, rwflx.value[i].id, formData.value[rwflx.value[i].id])
|
||||||
|
if (rwflx.value[i].rwbs && (!formData.value[rwflx.value[i].id] || formData.value[rwflx.value[i].id] == "")) {
|
||||||
|
showToast("请填写必填项!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
rwlxId: rwflx.value[i].id,
|
||||||
|
rwzxqdtx: formData.value[rwflx.value[i].id],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await rwzxSaveApi({
|
||||||
|
mobile: getUser.mobile,
|
||||||
|
rwId: rw.value.id,
|
||||||
|
rwzxqdDtos: result
|
||||||
|
})
|
||||||
|
showToast("操作成功!");
|
||||||
|
uni.navigateBack({delta: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
const rwzxqds = ref([])
|
||||||
|
onLoad(async (options) => {
|
||||||
|
if (options && options.id) {
|
||||||
|
const {result} = await rwFindInfoByRwId({
|
||||||
|
rwId: options.id
|
||||||
|
});
|
||||||
|
rwflx.value = result.rwlxes;
|
||||||
|
rw.value = result;
|
||||||
|
for (let i = 0; i < rwflx.value.length; i++) {
|
||||||
|
if (rwflx.value[i].rwfl == "sczy") {
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicUpload",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
componentProps: {}
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "text") {
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicInput",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else if (rwflx.value[i].rwfl == "dxsx" || rwflx.value[i].rwfl == "dxxz") {
|
||||||
|
let options = rwflx.value[i].remark.split(";");
|
||||||
|
let range = [];
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
range.push({
|
||||||
|
name: options[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
schema.push({
|
||||||
|
field: `${rwflx.value[i].id}`,
|
||||||
|
label: `${rwflx.value[i].rwbt}`,
|
||||||
|
component: "BasicPicker",
|
||||||
|
required: rwflx.value[i].rwbs,
|
||||||
|
itemProps: {
|
||||||
|
labelPosition: "top",
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
range: range,
|
||||||
|
rangeKey: "name",
|
||||||
|
savaKey: "name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await rwzxExecutedInfoByRwIdAndJsApi({
|
||||||
|
rwId: options.id,
|
||||||
|
mobile: getUser.mobile
|
||||||
|
});
|
||||||
|
if (res && res.result && res.result.length) {
|
||||||
|
rwzxqds.value = res.result;
|
||||||
|
const showData = {};
|
||||||
|
for (let i = 0; i < rwzxqds.value.length; i++) {
|
||||||
|
showData[rwzxqds.value[i].rwlxId] = rwzxqds.value[i].rwzxqdtx;
|
||||||
|
}
|
||||||
|
formData.value = showData;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Message ID/Data is missing!');
|
||||||
|
uni.showToast({title: '加载失败,缺少信息', icon: 'none'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.message-detail-page {
|
||||||
|
background-color: #f4f5f7;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-indicator,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 40px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.title-tag-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start; // Align items to the top
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
flex: 1; // Allow title to take available space
|
||||||
|
margin-right: 10px; // Space between title and tag
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag { // Reuse tag styles from index page if possible, or define here
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0; // Prevent tag from shrinking
|
||||||
|
|
||||||
|
&.notice {
|
||||||
|
background-color: #447ade;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.task {
|
||||||
|
background-color: #19be6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.approval {
|
||||||
|
background-color: #ff9f0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.submit {
|
||||||
|
background-color: #8e8e93;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
text {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-body {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.detail-desc {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.7;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-footer {
|
||||||
|
// text-align: center; // Removed center alignment
|
||||||
|
// Add margin if needed, e.g., margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style for the action button
|
||||||
|
.action-button {
|
||||||
|
width: 100%; // Make button full width
|
||||||
|
height: 44px; // Standard button height
|
||||||
|
line-height: 44px; // Match height for vertical centering
|
||||||
|
font-size: 16px; // Slightly larger font
|
||||||
|
font-weight: 500; // Medium weight
|
||||||
|
border-radius: 8px; // Consistent border radius
|
||||||
|
margin-top: 20px; // Add space above the button
|
||||||
|
// Ensure primary color is applied correctly (uni-app default should work)
|
||||||
|
// background-color: #447ade;
|
||||||
|
// color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
BIN
src/static/base/home/ysyc.png
Normal file
BIN
src/static/base/home/ysyc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Loading…
x
Reference in New Issue
Block a user