资源调整
This commit is contained in:
parent
33643f78ae
commit
f525a6ae77
@ -204,6 +204,48 @@ export const zymlFindTreeByZylxApi = async (params: any) => {
|
||||
return await get("/api/zyml/qryTreeByZylx", params);
|
||||
};
|
||||
|
||||
// ==================== 资源包(Zy)相关API ====================
|
||||
// 获取资源包分页列表
|
||||
export const zyFindPageApi = async (params: any) => {
|
||||
return await get("/api/zy/findPage", params);
|
||||
};
|
||||
|
||||
// 保存资源包(新增/编辑)
|
||||
export const zySaveApi = async (params: any) => {
|
||||
return await post("/api/zy/save", params);
|
||||
};
|
||||
|
||||
// 收藏/取消收藏资源包
|
||||
export const zyCollectApi = async (params: { zyId: string }) => {
|
||||
return await post(`/api/zy/collect?zyId=${params.zyId}`);
|
||||
};
|
||||
|
||||
// 点赞/取消点赞资源包
|
||||
export const zyLikeApi = async (params: { zyId: string }) => {
|
||||
return await post(`/api/zy/like?zyId=${params.zyId}`);
|
||||
};
|
||||
|
||||
// 评分资源包
|
||||
export const zyScoreApi = async (params: { zyId: string; score: number; comment?: string }) => {
|
||||
const queryParams = new URLSearchParams({
|
||||
zyId: params.zyId,
|
||||
score: params.score.toString(),
|
||||
...(params.comment ? { comment: params.comment } : {})
|
||||
});
|
||||
return await post(`/api/zy/score?${queryParams.toString()}`);
|
||||
};
|
||||
|
||||
// 根据ID查询资源包详情(包含资源明细和用户状态)
|
||||
export const zyGetDetailApi = async (params: { id: string }) => {
|
||||
return await get("/api/zy/getDetail", params);
|
||||
};
|
||||
|
||||
// 删除资源包
|
||||
export const zyLogicDeleteApi = async (params: { ids: string }) => {
|
||||
return await post("/api/zy/logicDelete", params);
|
||||
};
|
||||
|
||||
// ==================== 资源明细(Zymx)相关API ====================
|
||||
// 获取资源明细分页
|
||||
export const zymxFindPageApi = async (params: any) => {
|
||||
return await get("/api/zymx/findPage", params);
|
||||
|
||||
@ -381,6 +381,27 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/JiaoXueZiYuan/zy-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "资源包详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/JiaoXueZiYuan/zy-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "教学资源包",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/JiaoXueZiYuan/zy-add",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增资源包",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/HuoDongZiYuan/index",
|
||||
"style": {
|
||||
|
||||
@ -442,7 +442,7 @@ const sections = reactive<Section[]>([
|
||||
{
|
||||
id: "r5",
|
||||
icon: "stxc",
|
||||
text: "食堂巡查",
|
||||
text: "食堂日志",
|
||||
show: true,
|
||||
permissionKey: "routine-stxc", // 食堂巡查权限编码
|
||||
path: "/pages/view/routine/ShiTangXunCha/index",
|
||||
|
||||
@ -133,8 +133,10 @@ const goTo = function () {
|
||||
}
|
||||
|
||||
const params = {
|
||||
resourType: leafNode.key, // ✅ 动态获取叶子节点的key
|
||||
category: curCategory.value.key,
|
||||
zymlId: leafNode.key, // 资源目录ID(用于资源包查询)
|
||||
zyCategory: curCategory.value.key, // 资源类别
|
||||
resourType: leafNode.key, // 资源类型(用于资源明细查询,兼容旧版)
|
||||
category: curCategory.value.key, // 类别(兼容旧版)
|
||||
};
|
||||
|
||||
console.log('选择的参数:', {
|
||||
@ -148,8 +150,10 @@ const goTo = function () {
|
||||
});
|
||||
|
||||
setData(params);
|
||||
|
||||
// 直接跳转到资源包列表
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/JiaoXueZiYuan/indexList`
|
||||
url: `/pages/view/routine/JiaoXueZiYuan/zy-list`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
703
src/pages/view/routine/JiaoXueZiYuan/zy-add.vue
Normal file
703
src/pages/view/routine/JiaoXueZiYuan/zy-add.vue
Normal file
@ -0,0 +1,703 @@
|
||||
<template>
|
||||
<BasicLayout :show-nav-bar="true" :nav-bar-props="{ title: modalTitle }">
|
||||
<view class="add-page">
|
||||
<!-- 基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<!-- 资源包名称 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label"><text class="required">*</text> 资源包名称</text>
|
||||
<input
|
||||
v-model="formData.zyName"
|
||||
placeholder="请输入资源包名称"
|
||||
class="form-input"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 资源目录 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label"><text class="required">*</text> 资源目录</text>
|
||||
<view class="picker-box" @click="showResourceTree">
|
||||
<text :class="{ placeholder: !formData.zymlId }">
|
||||
{{ selectedTreeText || '请选择资源目录' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源类别 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label"><text class="required">*</text> 资源类别</text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="categoryList"
|
||||
range-key="label"
|
||||
@change="handleCategoryChange"
|
||||
>
|
||||
<view class="picker-box">
|
||||
<text :class="{ placeholder: !formData.zyCategory }">
|
||||
{{ getCategoryText() || '请选择资源类别' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 资源描述 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">资源描述</text>
|
||||
<textarea
|
||||
v-model="formData.zyDesc"
|
||||
placeholder="请输入资源描述"
|
||||
class="form-textarea"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 上传资源 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">
|
||||
<text>上传资源</text>
|
||||
<text class="required">*</text>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<view class="upload-box" @click="chooseFile">
|
||||
<uni-icons type="cloud-upload" size="40" color="#1890ff"></uni-icons>
|
||||
<text class="upload-text">点击选择文件上传</text>
|
||||
<text class="upload-hint">支持 .doc、.pdf、.ppt、.xls 等格式</text>
|
||||
</view>
|
||||
|
||||
<!-- 已上传文件列表 -->
|
||||
<view v-if="uploadedFiles.length > 0" class="files-list">
|
||||
<view class="list-header">已上传文件 ({{ uploadedFiles.length }})</view>
|
||||
<view
|
||||
v-for="(file, index) in uploadedFiles"
|
||||
:key="index"
|
||||
class="file-item"
|
||||
>
|
||||
<view class="file-info">
|
||||
<text class="file-index">{{ index + 1 }}</text>
|
||||
<view class="file-detail">
|
||||
<text class="file-name">{{ file.fileName }}</text>
|
||||
<text class="file-meta">{{ file.fileType }} • {{ formatFileSize(file.fileSize) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资源类型选择 -->
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="resourceTypeOptions"
|
||||
range-key="label"
|
||||
:value="getResourceTypeIndex(file.category)"
|
||||
@change="(e: any) => handleFileTypeChange(e, index)"
|
||||
>
|
||||
<view class="type-picker">
|
||||
<text :class="{ placeholder: !file.category }">
|
||||
{{ getResourceTypeText(file.category) || '选择类型' }}
|
||||
</text>
|
||||
<uni-icons type="right" size="14" color="#999"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<view class="file-remove" @click="removeFile(index)">
|
||||
<uni-icons type="trash" size="18" color="#ff4d4f"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<template #bottom>
|
||||
<view class="bottom-actions">
|
||||
<u-button text="取消" @click="handleCancel" class="action-btn"></u-button>
|
||||
<u-button text="提交" type="primary" @click="handleSubmit" :loading="submitLoading" class="action-btn"></u-button>
|
||||
</view>
|
||||
</template>
|
||||
</BasicLayout>
|
||||
|
||||
<!-- 资源目录树选择 -->
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:range="treeData"
|
||||
idKey="key"
|
||||
rangeKey="title"
|
||||
title="选择资源目录"
|
||||
:multiple="false"
|
||||
:selectParent="true"
|
||||
@confirm="onTreeConfirm"
|
||||
@cancel="onTreeCancel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import BasicTree from '@/components/BasicTree/Tree.vue';
|
||||
import {
|
||||
zySaveApi,
|
||||
zymlFindTreeApi,
|
||||
dicFindByPidApi
|
||||
} from "@/api/base/server";
|
||||
import { attachmentUpload } from "@/api/system/upload";
|
||||
|
||||
const modalTitle = ref('新增资源包');
|
||||
const formData = ref<any>({
|
||||
zyName: '',
|
||||
zymlId: '',
|
||||
zyCategory: '',
|
||||
zyDesc: ''
|
||||
});
|
||||
const uploadedFiles = ref<any[]>([]);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 资源目录树
|
||||
const treeData = ref<any[]>([]);
|
||||
const treeRef = ref();
|
||||
const selectedTreeText = ref('');
|
||||
|
||||
// 资源类别列表(课程包、练习包等)
|
||||
const categoryList = ref<any[]>([]);
|
||||
|
||||
// 资源类型列表(课件、教案、学案等)- 用于每个文件选择类型
|
||||
const resourceTypeOptions = ref<any[]>([]);
|
||||
|
||||
// 获取类别文本
|
||||
const getCategoryText = () => {
|
||||
const item = categoryList.value.find(c => c.value === formData.value.zyCategory);
|
||||
return item?.label || '';
|
||||
};
|
||||
|
||||
// 获取资源类型文本
|
||||
const getResourceTypeText = (typeValue: string) => {
|
||||
const item = resourceTypeOptions.value.find(t => t.value === typeValue);
|
||||
return item?.label || '';
|
||||
};
|
||||
|
||||
// 获取资源类型索引
|
||||
const getResourceTypeIndex = (typeValue: string) => {
|
||||
return resourceTypeOptions.value.findIndex(t => t.value === typeValue);
|
||||
};
|
||||
|
||||
// 处理类别选择
|
||||
const handleCategoryChange = (e: any) => {
|
||||
formData.value.zyCategory = categoryList.value[e.detail.value]?.value;
|
||||
};
|
||||
|
||||
// 处理文件类型选择
|
||||
const handleFileTypeChange = (e: any, index: number) => {
|
||||
uploadedFiles.value[index].category = resourceTypeOptions.value[e.detail.value]?.value;
|
||||
};
|
||||
|
||||
// 显示资源目录树
|
||||
const showResourceTree = () => {
|
||||
if (treeRef.value) {
|
||||
treeRef.value._show();
|
||||
}
|
||||
};
|
||||
|
||||
// 树形选择确认
|
||||
const onTreeConfirm = (selectedItems: any[]) => {
|
||||
if (selectedItems.length > 0) {
|
||||
const selectedItem = selectedItems[0]; // 单选模式,取第一个
|
||||
formData.value.zymlId = selectedItem.key;
|
||||
selectedTreeText.value = selectedItem.title;
|
||||
}
|
||||
};
|
||||
|
||||
// 树形选择取消
|
||||
const onTreeCancel = () => {
|
||||
// 取消选择,不做任何操作
|
||||
};
|
||||
|
||||
// 选择文件
|
||||
const chooseFile = () => {
|
||||
uni.chooseFile({
|
||||
count: 10,
|
||||
type: 'all',
|
||||
// 不限制文件类型
|
||||
success: (res) => {
|
||||
const files = Array.isArray(res.tempFiles) ? res.tempFiles : [res.tempFiles];
|
||||
files.forEach((file: any) => {
|
||||
uploadFile(file);
|
||||
});
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('选择文件失败:', error);
|
||||
uni.showToast({
|
||||
title: '选择文件失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const uploadFile = async (file: any) => {
|
||||
uni.showLoading({ title: '上传中...' });
|
||||
|
||||
try {
|
||||
// 使用封装的 attachmentUpload 方法
|
||||
const uploadResult: any = await attachmentUpload(file.path as any);
|
||||
|
||||
console.log('上传响应:', uploadResult);
|
||||
|
||||
if (uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length > 0) {
|
||||
const fileInfo = uploadResult.result[0];
|
||||
uploadedFiles.value.push({
|
||||
fileName: file.name,
|
||||
filePath: fileInfo.filePath,
|
||||
fileId: fileInfo.id,
|
||||
fileType: getFileExtension(file.name),
|
||||
fileSize: file.size,
|
||||
category: '' // 待用户选择
|
||||
});
|
||||
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else if (uploadResult.resultCode === 1 && uploadResult.result && uploadResult.result.length === 0) {
|
||||
console.error('上传成功但返回数据为空:', uploadResult);
|
||||
uni.showToast({
|
||||
title: '上传失败:服务器未返回文件信息',
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: uploadResult.message || '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文件扩展名
|
||||
const getFileExtension = (fileName: string) => {
|
||||
const parts = fileName.split('.');
|
||||
return parts.length > 1 ? parts[parts.length - 1] : '';
|
||||
};
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (size: number) => {
|
||||
if (!size) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(size) / Math.log(k));
|
||||
return (size / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
// 移除文件
|
||||
const removeFile = (index: number) => {
|
||||
uploadedFiles.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const handleSubmit = async () => {
|
||||
// 表单验证
|
||||
if (!formData.value.zyName) {
|
||||
uni.showToast({
|
||||
title: '请输入资源包名称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.value.zymlId) {
|
||||
uni.showToast({
|
||||
title: '请选择资源目录',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.value.zyCategory) {
|
||||
uni.showToast({
|
||||
title: '请选择资源类别',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (uploadedFiles.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请至少上传一个资源文件',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证每个文件都已选择类型
|
||||
const unselectedFiles = uploadedFiles.value.filter(file => !file.category);
|
||||
if (unselectedFiles.length > 0) {
|
||||
uni.showToast({
|
||||
title: '请为所有文件选择资源类型',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
submitLoading.value = true;
|
||||
|
||||
try {
|
||||
// 构建提交数据
|
||||
const submitData: any = {
|
||||
zyName: formData.value.zyName,
|
||||
zymlId: formData.value.zymlId,
|
||||
zyCategory: formData.value.zyCategory,
|
||||
zyDesc: formData.value.zyDesc || '',
|
||||
// sort 由后端自动生成
|
||||
zymxList: uploadedFiles.value.map((file, index) => ({
|
||||
resourName: file.fileName,
|
||||
resourUrl: file.filePath,
|
||||
resourId: file.fileId,
|
||||
resSuf: file.fileType,
|
||||
category: file.category,
|
||||
sort: index + 1
|
||||
}))
|
||||
};
|
||||
|
||||
console.log('提交资源包数据:', submitData);
|
||||
|
||||
const res = await zySaveApi(submitData);
|
||||
|
||||
if (res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 发送事件通知列表页刷新
|
||||
uni.$emit('refreshResourceList');
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message || '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
uni.showToast({
|
||||
title: '提交失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载资源目录树
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await zymlFindTreeApi();
|
||||
if (res && res.result) {
|
||||
// 递归转换数据格式以适配 BasicTree 组件
|
||||
const convertTreeData = (items: any[]): any[] => {
|
||||
return items.map((item: any) => ({
|
||||
key: item.key,
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
children: item.children ? convertTreeData(item.children) : [],
|
||||
}));
|
||||
};
|
||||
treeData.value = convertTreeData(res.result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源目录失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载资源类别(课程包、练习包等)
|
||||
const loadCategoryList = async () => {
|
||||
try {
|
||||
const pid = '5'; // 资源类别字典父ID
|
||||
const res = await dicFindByPidApi({ pid });
|
||||
|
||||
if (res && res.result && Array.isArray(res.result)) {
|
||||
categoryList.value = res.result.map((item: any) => ({
|
||||
value: item.dictionaryCode || item.dictionaryValue || item.id,
|
||||
label: item.dictionaryName || item.dictionaryValue || item.name
|
||||
}));
|
||||
} else {
|
||||
categoryList.value = [
|
||||
{ value: '1', label: '课程包' },
|
||||
{ value: '2', label: '练习包' },
|
||||
{ value: '3', label: '试卷包' }
|
||||
];
|
||||
}
|
||||
|
||||
// 默认选中第一条记录
|
||||
if (categoryList.value.length > 0 && !formData.value.zyCategory) {
|
||||
formData.value.zyCategory = categoryList.value[0].value;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类别失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载资源类型(课件、教案等)
|
||||
const loadResourceTypeOptions = async () => {
|
||||
try {
|
||||
const pid = '1391443399'; // 资源类型字典父ID
|
||||
const res = await dicFindByPidApi({ pid });
|
||||
|
||||
if (res && res.result && Array.isArray(res.result)) {
|
||||
resourceTypeOptions.value = res.result.map((item: any) => ({
|
||||
value: item.dictionaryCode || item.dictionaryValue || item.id,
|
||||
label: item.dictionaryName || item.dictionaryValue || item.name
|
||||
}));
|
||||
} else {
|
||||
resourceTypeOptions.value = [
|
||||
{ value: '1', label: '课件' },
|
||||
{ value: '2', label: '教案' },
|
||||
{ value: '3', label: '学案' },
|
||||
{ value: '4', label: '作业' },
|
||||
{ value: '5', label: '试卷' }
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类型失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onLoad(async () => {
|
||||
await loadTreeData();
|
||||
await loadCategoryList();
|
||||
await loadResourceTypeOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.add-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 25rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.required {
|
||||
color: #ff4d4f;
|
||||
margin-left: 5rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.required {
|
||||
color: #ff4d4f;
|
||||
margin-right: 5rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
padding: 0 25rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 150rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.picker-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 70rpx;
|
||||
padding: 0 25rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx;
|
||||
border: 2rpx dashed #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
background: #fafafa;
|
||||
|
||||
.upload-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.files-list {
|
||||
margin-top: 30rpx;
|
||||
border: 1rpx solid #f0f0f0;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.list-header {
|
||||
padding: 20rpx 25rpx;
|
||||
background: #fafafa;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 25rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
gap: 15rpx;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
min-width: 0;
|
||||
|
||||
.file-index {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-detail {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 6rpx;
|
||||
font-size: 24rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.file-remove {
|
||||
flex-shrink: 0;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
808
src/pages/view/routine/JiaoXueZiYuan/zy-detail.vue
Normal file
808
src/pages/view/routine/JiaoXueZiYuan/zy-detail.vue
Normal file
@ -0,0 +1,808 @@
|
||||
<template>
|
||||
<BasicLayout :show-nav-bar="true" :nav-bar-props="{ title: '资源包详情' }">
|
||||
<view class="detail-page">
|
||||
<view v-if="loading" class="loading-container">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="resourceDetail" class="detail-content">
|
||||
<!-- 资源包信息卡片 -->
|
||||
<view class="info-card">
|
||||
<view class="package-header">
|
||||
<text class="package-title">{{ resourceDetail.zyName }}</text>
|
||||
<view class="package-category">{{ getCategoryName(resourceDetail.zyCategory) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 评分和操作区域 -->
|
||||
<view class="rating-action-section">
|
||||
<!-- 左侧:评分展示 -->
|
||||
<view class="rating-display">
|
||||
<text class="rating-score">{{ (resourceDetail.scoreAvg || 0).toFixed(1) }}</text>
|
||||
<view class="rating-stars-wrapper">
|
||||
<text class="rating-stars">{{ getStarDisplay(resourceDetail.scoreAvg) }}</text>
|
||||
</view>
|
||||
<text class="rating-label">当前评分</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="action-item" @click="handleRating">
|
||||
<uni-icons type="star" size="24" color="#faad14"></uni-icons>
|
||||
<text class="action-label">评分</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleCollect">
|
||||
<uni-icons
|
||||
:type="resourceDetail.isCollect === '1' ? 'heart-filled' : 'heart'"
|
||||
size="24"
|
||||
:color="resourceDetail.isCollect === '1' ? '#ff4d4f' : '#999'"
|
||||
></uni-icons>
|
||||
<text class="action-label">{{ resourceDetail.isCollect === '1' ? '已收藏' : '收藏' }}</text>
|
||||
<text class="action-count">{{ formatNumber(resourceDetail.collNum || 0) }}</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleLike">
|
||||
<view class="like-icon">
|
||||
<text class="like-emoji">{{ resourceDetail.isLike === '1' ? '👍' : '👍🏻' }}</text>
|
||||
</view>
|
||||
<text class="action-label">{{ resourceDetail.isLike === '1' ? '已点赞' : '点赞' }}</text>
|
||||
<text class="action-count">{{ formatNumber(resourceDetail.likeNum || 0) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 描述 -->
|
||||
<view class="description-section">
|
||||
<text class="section-title">资源描述</text>
|
||||
<text class="description-text">{{ resourceDetail.zyDesc || '暂无描述' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 包含的资源列表 -->
|
||||
<view class="resources-card">
|
||||
<view class="card-title">
|
||||
包含的资源 ({{ resourceDetail.zymxList?.length || 0 }})
|
||||
</view>
|
||||
|
||||
<view v-if="resourceDetail.zymxList && resourceDetail.zymxList.length > 0">
|
||||
<view
|
||||
v-for="(item, index) in resourceDetail.zymxList"
|
||||
:key="item.id"
|
||||
class="resource-item"
|
||||
>
|
||||
<view class="item-index">{{ index + 1 }}</view>
|
||||
<view class="item-info">
|
||||
<text class="item-name">{{ item.resourName }}</text>
|
||||
<view class="item-meta">
|
||||
<text class="meta-tag">{{ item.resSuf }}</text>
|
||||
<text class="meta-stats">浏览 {{ item.lookNum || 0 }}</text>
|
||||
<text class="meta-stats">下载 {{ item.downNum || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-actions">
|
||||
<u-button
|
||||
v-if="canPreview(item.resSuf)"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handlePreview(item)"
|
||||
>
|
||||
预览
|
||||
</u-button>
|
||||
<u-button
|
||||
size="mini"
|
||||
type="success"
|
||||
@click="handleDownload(item)"
|
||||
>
|
||||
下载
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<text class="empty-text">暂无资源</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="error-container">
|
||||
<text class="error-text">加载失败</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部返回按钮 -->
|
||||
<template #bottom>
|
||||
<view class="bottom-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="handleBack"
|
||||
class="back-button"
|
||||
>
|
||||
返回
|
||||
</u-button>
|
||||
</view>
|
||||
</template>
|
||||
</BasicLayout>
|
||||
|
||||
<!-- 评分弹窗(使用原生 modal)-->
|
||||
<view v-if="showRatingModal" class="rating-modal-mask" @click="closeRatingModal">
|
||||
<view class="rating-modal" @click.stop>
|
||||
<view class="modal-title">资源评分</view>
|
||||
<view class="modal-content">
|
||||
<text class="resource-name">{{ resourceDetail?.zyName }}</text>
|
||||
|
||||
<view class="rating-input">
|
||||
<view class="star-picker">
|
||||
<view
|
||||
v-for="star in 10"
|
||||
:key="star"
|
||||
class="star-item"
|
||||
@click="selectRating(star / 2)"
|
||||
>
|
||||
<text class="star-icon">{{ getStarIcon(star / 2, ratingValue) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="rating-value">{{ ratingValue.toFixed(1) }}分</text>
|
||||
</view>
|
||||
|
||||
<textarea
|
||||
v-model="ratingComment"
|
||||
placeholder="请输入评价内容(选填)"
|
||||
maxlength="200"
|
||||
class="comment-textarea"
|
||||
></textarea>
|
||||
<text class="char-count">{{ ratingComment.length }}/200</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-actions">
|
||||
<button class="modal-btn cancel-btn" @click="closeRatingModal">取消</button>
|
||||
<button class="modal-btn confirm-btn" @click="submitRating">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { imagUrl } from "@/utils";
|
||||
import {
|
||||
zyGetDetailApi,
|
||||
zyCollectApi,
|
||||
zyLikeApi,
|
||||
zyScoreApi,
|
||||
dicFindByPidApi
|
||||
} from "@/api/base/server";
|
||||
import {
|
||||
isVideo,
|
||||
isImage,
|
||||
canPreview,
|
||||
previewFile,
|
||||
previewVideo,
|
||||
previewImage,
|
||||
downloadFile
|
||||
} from "@/utils/filePreview";
|
||||
|
||||
const resourceId = ref<string>('');
|
||||
const resourceDetail = ref<any>(null);
|
||||
const loading = ref(false);
|
||||
const categoryOptions = ref<any[]>([]);
|
||||
const showRatingModal = ref(false);
|
||||
const ratingValue = ref(0);
|
||||
const ratingComment = ref('');
|
||||
|
||||
// 获取类别名称
|
||||
const getCategoryName = (category: string) => {
|
||||
const option = categoryOptions.value.find(item => item.key === category);
|
||||
return option?.label || category;
|
||||
};
|
||||
|
||||
// 格式化数字(如:114000 => 11.4万)
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 获取星星显示(只读)
|
||||
const getStarDisplay = (score: number) => {
|
||||
if (!score) return '☆☆☆☆☆';
|
||||
|
||||
const fullStars = Math.floor(score);
|
||||
const hasHalfStar = (score % 1) >= 0.25 && (score % 1) < 0.75;
|
||||
const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
|
||||
|
||||
return '★'.repeat(fullStars) + (hasHalfStar ? '⭐' : '') + '☆'.repeat(Math.max(0, emptyStars));
|
||||
};
|
||||
|
||||
// 获取星星图标(评分选择)
|
||||
const getStarIcon = (value: number, currentValue: number) => {
|
||||
if (value <= currentValue) {
|
||||
// 如果是整数位,显示满星
|
||||
if (value % 1 === 0) {
|
||||
return '★';
|
||||
}
|
||||
// 如果是半星位
|
||||
return '⭐';
|
||||
}
|
||||
return '☆';
|
||||
};
|
||||
|
||||
// 选择评分
|
||||
const selectRating = (value: number) => {
|
||||
ratingValue.value = value;
|
||||
};
|
||||
|
||||
// 加载资源包详情
|
||||
const loadDetail = async () => {
|
||||
if (!resourceId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await zyGetDetailApi({ id: resourceId.value });
|
||||
if (res && res.result) {
|
||||
resourceDetail.value = res.result;
|
||||
console.log('资源包详情:', resourceDetail.value);
|
||||
console.log('资源明细列表:', resourceDetail.value.zymxList);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源包详情失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 收藏/取消收藏
|
||||
const handleCollect = async () => {
|
||||
if (!resourceDetail.value) return;
|
||||
|
||||
try {
|
||||
const res = await zyCollectApi({ zyId: resourceDetail.value.id });
|
||||
if (res.resultCode === 1) {
|
||||
const isCollected = resourceDetail.value.isCollect === '1';
|
||||
resourceDetail.value.isCollect = isCollected ? '0' : '1';
|
||||
resourceDetail.value.collNum = isCollected
|
||||
? (resourceDetail.value.collNum - 1)
|
||||
: (resourceDetail.value.collNum + 1);
|
||||
|
||||
uni.showToast({
|
||||
title: isCollected ? '取消收藏成功' : '收藏成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('收藏操作失败:', error);
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 点赞/取消点赞
|
||||
const handleLike = async () => {
|
||||
if (!resourceDetail.value) return;
|
||||
|
||||
try {
|
||||
const res = await zyLikeApi({ zyId: resourceDetail.value.id });
|
||||
if (res.resultCode === 1) {
|
||||
const isLiked = resourceDetail.value.isLike === '1';
|
||||
resourceDetail.value.isLike = isLiked ? '0' : '1';
|
||||
resourceDetail.value.likeNum = isLiked
|
||||
? (resourceDetail.value.likeNum - 1)
|
||||
: (resourceDetail.value.likeNum + 1);
|
||||
|
||||
uni.showToast({
|
||||
title: isLiked ? '取消点赞成功' : '点赞成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('点赞操作失败:', error);
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 打开评分弹窗
|
||||
const handleRating = () => {
|
||||
ratingValue.value = resourceDetail.value?.userScore || 0;
|
||||
ratingComment.value = '';
|
||||
showRatingModal.value = true;
|
||||
};
|
||||
|
||||
// 关闭评分弹窗
|
||||
const closeRatingModal = () => {
|
||||
showRatingModal.value = false;
|
||||
ratingValue.value = 0;
|
||||
ratingComment.value = '';
|
||||
};
|
||||
|
||||
// 提交评分
|
||||
const submitRating = async () => {
|
||||
if (ratingValue.value === 0) {
|
||||
uni.showToast({
|
||||
title: '请先评分',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await zyScoreApi({
|
||||
zyId: resourceDetail.value.id,
|
||||
score: ratingValue.value,
|
||||
comment: ratingComment.value
|
||||
});
|
||||
|
||||
if (res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: '评分成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
closeRatingModal();
|
||||
loadDetail(); // 刷新详情
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '评分失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('评分失败:', error);
|
||||
uni.showToast({
|
||||
title: '评分失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 预览资源
|
||||
const handlePreview = (item: any) => {
|
||||
const fileUrl = imagUrl(item.resourUrl);
|
||||
const fileName = item.resourName + '.' + item.resSuf;
|
||||
|
||||
if (isVideo(item.resSuf)) {
|
||||
previewVideo(fileUrl, fileName);
|
||||
} else if (isImage(item.resSuf)) {
|
||||
previewImage(fileUrl);
|
||||
} else if (canPreview(item.resSuf)) {
|
||||
previewFile(fileUrl, fileName, item.resSuf);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载资源
|
||||
const handleDownload = (item: any) => {
|
||||
const fileUrl = imagUrl(item.resourUrl);
|
||||
const fileName = item.resourName + '.' + item.resSuf;
|
||||
|
||||
downloadFile(fileUrl, fileName)
|
||||
.then(() => {
|
||||
uni.showToast({
|
||||
title: '下载成功',
|
||||
icon: 'success'
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('下载失败:', error);
|
||||
});
|
||||
};
|
||||
|
||||
// 加载资源类别字典
|
||||
const loadCategoryOptions = async () => {
|
||||
try {
|
||||
const pid = '1391443399';
|
||||
const res = await dicFindByPidApi({ pid });
|
||||
|
||||
if (res && res.result && Array.isArray(res.result)) {
|
||||
categoryOptions.value = res.result.map((item: any) => ({
|
||||
key: item.dictionaryCode || item.dictionaryValue || item.id,
|
||||
label: item.dictionaryName || item.dictionaryValue || item.name
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类别失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
onLoad(async (options) => {
|
||||
if (options?.id) {
|
||||
resourceId.value = options.id;
|
||||
await loadCategoryOptions();
|
||||
await loadDetail();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '无效的资源ID',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.loading-container,
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.loading-text,
|
||||
.error-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.package-header {
|
||||
margin-bottom: 25rpx;
|
||||
|
||||
.package-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.package-category {
|
||||
display: inline-block;
|
||||
padding: 10rpx 25rpx;
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-action-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.rating-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.rating-score {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #faad14;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.rating-stars-wrapper {
|
||||
.rating-stars {
|
||||
font-size: 24rpx;
|
||||
color: #faad14;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 40rpx;
|
||||
align-items: center;
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
cursor: pointer;
|
||||
padding: 10rpx;
|
||||
border-radius: 8rpx;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.like-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
|
||||
.like-emoji {
|
||||
font-size: 48rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-count {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description-section {
|
||||
padding-top: 25rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resources-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 25rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-index {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.item-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
display: flex;
|
||||
gap: 15rpx;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.meta-tag {
|
||||
padding: 4rpx 12rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.meta-stats {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 15rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 80rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.back-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 评分弹窗遮罩层
|
||||
.rating-modal-mask {
|
||||
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;
|
||||
}
|
||||
|
||||
// 评分弹窗内容
|
||||
.rating-modal {
|
||||
width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx;
|
||||
margin: 0 20rpx;
|
||||
|
||||
.modal-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.resource-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 25rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rating-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.star-picker {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
|
||||
.star-item {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.star-icon {
|
||||
font-size: 40rpx;
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-textarea {
|
||||
width: 100%;
|
||||
min-height: 150rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 30rpx;
|
||||
|
||||
.modal-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.cancel-btn {
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.confirm-btn {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
663
src/pages/view/routine/JiaoXueZiYuan/zy-list.vue
Normal file
663
src/pages/view/routine/JiaoXueZiYuan/zy-list.vue
Normal file
@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<BasicListLayout
|
||||
:show-nav-bar="true"
|
||||
:nav-bar-props="{ title: '教学资源包' }"
|
||||
@register="register"
|
||||
>
|
||||
<template #top>
|
||||
<view class="search-section">
|
||||
<view class="search-box">
|
||||
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
||||
<input
|
||||
class="search-input"
|
||||
type="text"
|
||||
placeholder="搜索资源包名称"
|
||||
v-model="searchKeyword"
|
||||
@confirm="onSearchConfirm"
|
||||
/>
|
||||
<view class="search-clear" v-if="searchKeyword" @click="clearSearch">
|
||||
<uni-icons type="clear" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<u-button text="搜索" class="search-btn" type="primary" @click="onSearchConfirm"/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #default="{ data }">
|
||||
<view class="resource-package-card" @click="goToDetail(data)">
|
||||
<!-- 左侧缩略图 -->
|
||||
<view class="card-thumbnail">
|
||||
<image
|
||||
class="thumbnail-image"
|
||||
src="/static/base/view/zyyl.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<view class="card-content">
|
||||
<!-- 类别标签 -->
|
||||
<view class="package-category">{{ getCategoryName(data.zyCategory) }}</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="package-title">{{ data.zyName }}</view>
|
||||
|
||||
<!-- 资源数量 -->
|
||||
<view class="package-meta" v-if="data.zymxList && data.zymxList.length > 0">
|
||||
<uni-icons type="list" size="12" color="#999"></uni-icons>
|
||||
<text class="meta-text">{{ data.zymxList.length }} 个资源</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部统计和评分 -->
|
||||
<view class="card-footer">
|
||||
<view class="stats-row">
|
||||
<view class="stat-item">
|
||||
<uni-icons type="eye" size="14" color="#999"></uni-icons>
|
||||
<text>{{ formatNumber(data.lookNum || 0) }}</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<uni-icons
|
||||
:type="data.isLike === '1' ? 'hand-thumbsup-filled' : 'hand-thumbsup'"
|
||||
size="14"
|
||||
:color="data.isLike === '1' ? '#1890ff' : '#999'"
|
||||
></uni-icons>
|
||||
<text>{{ formatNumber(data.likeNum || 0) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="rating-score">{{ (data.scoreAvg || 0).toFixed(1) }}分</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 详情箭头 -->
|
||||
<view class="card-arrow">
|
||||
<uni-icons type="forward" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 底部新增按钮 -->
|
||||
<template #bottom>
|
||||
<view class="bottom-bar">
|
||||
<u-button
|
||||
text="新增资源包"
|
||||
type="primary"
|
||||
@click="navigateToAdd"
|
||||
class="add-btn"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
|
||||
<!-- 评分弹窗(使用原生 modal)-->
|
||||
<view v-if="showRatingModal" class="rating-modal-mask" @click="closeRatingModal">
|
||||
<view class="rating-modal" @click.stop>
|
||||
<view class="modal-title">资源评分</view>
|
||||
<view class="modal-content">
|
||||
<text class="resource-name">{{ currentResource?.zyName }}</text>
|
||||
|
||||
<view class="rating-input">
|
||||
<view class="star-picker">
|
||||
<view
|
||||
v-for="star in 10"
|
||||
:key="star"
|
||||
class="star-item"
|
||||
@click="selectRating(star / 2)"
|
||||
>
|
||||
<text class="star-icon">{{ getStarIcon(star / 2, ratingValue) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="rating-value">{{ ratingValue.toFixed(1) }}分</text>
|
||||
</view>
|
||||
|
||||
<textarea
|
||||
v-model="ratingComment"
|
||||
placeholder="请输入评价内容(选填)"
|
||||
maxlength="200"
|
||||
class="comment-textarea"
|
||||
></textarea>
|
||||
<text class="char-count">{{ ratingComment.length }}/200</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-actions">
|
||||
<button class="modal-btn cancel-btn" @click="closeRatingModal">取消</button>
|
||||
<button class="modal-btn confirm-btn" @click="submitRating">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
|
||||
import {
|
||||
zyFindPageApi,
|
||||
zyCollectApi,
|
||||
zyLikeApi,
|
||||
zyScoreApi,
|
||||
dicFindByPidApi
|
||||
} from "@/api/base/server";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
|
||||
const { getData } = useDataStore();
|
||||
|
||||
const searchKeyword = ref<string>('');
|
||||
const categoryOptions = ref<any[]>([]);
|
||||
const showRatingModal = ref(false);
|
||||
const currentResource = ref<any>(null);
|
||||
const ratingValue = ref(0);
|
||||
const ratingComment = ref('');
|
||||
|
||||
// 布局Hook
|
||||
const [register, { reload, setParam }] = useLayout({
|
||||
api: zyFindPageApi,
|
||||
componentProps: {
|
||||
defaultPageSize: 10,
|
||||
loadingMoreEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 构建查询参数
|
||||
const buildParams = () => {
|
||||
const params = {
|
||||
...getData,
|
||||
zyName: searchKeyword.value,
|
||||
status: 'A'
|
||||
};
|
||||
console.log('查询资源包参数:', params);
|
||||
setParam(params);
|
||||
reload();
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const onSearchConfirm = () => {
|
||||
buildParams();
|
||||
};
|
||||
|
||||
// 清空搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = '';
|
||||
buildParams();
|
||||
};
|
||||
|
||||
// 获取类别名称
|
||||
const getCategoryName = (category: string) => {
|
||||
const option = categoryOptions.value.find(item => item.key === category);
|
||||
return option?.label || category;
|
||||
};
|
||||
|
||||
// 格式化数字(万为单位)
|
||||
const formatNumber = (num: number) => {
|
||||
if (num >= 10000) {
|
||||
return (num / 10000).toFixed(1) + '万';
|
||||
}
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 获取星星显示(只读)
|
||||
const getStarDisplay = (score: number) => {
|
||||
if (!score) return '☆☆☆☆☆';
|
||||
|
||||
const fullStars = Math.floor(score);
|
||||
const hasHalfStar = (score % 1) >= 0.25 && (score % 1) < 0.75;
|
||||
const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
|
||||
|
||||
return '★'.repeat(fullStars) + (hasHalfStar ? '⭐' : '') + '☆'.repeat(Math.max(0, emptyStars));
|
||||
};
|
||||
|
||||
// 获取星星图标(评分选择)
|
||||
const getStarIcon = (value: number, currentValue: number) => {
|
||||
if (value <= currentValue) {
|
||||
// 如果是整数位,显示满星
|
||||
if (value % 1 === 0) {
|
||||
return '★';
|
||||
}
|
||||
// 如果是半星位
|
||||
return '⭐';
|
||||
}
|
||||
return '☆';
|
||||
};
|
||||
|
||||
// 选择评分
|
||||
const selectRating = (value: number) => {
|
||||
ratingValue.value = value;
|
||||
};
|
||||
|
||||
// 收藏/取消收藏
|
||||
const handleCollect = async (item: any) => {
|
||||
try {
|
||||
const res = await zyCollectApi({ zyId: item.id });
|
||||
if (res.resultCode === 1) {
|
||||
// 更新状态(直接修改对象)
|
||||
const isCollected = item.isCollect === '1';
|
||||
item.isCollect = isCollected ? '0' : '1';
|
||||
item.collNum = isCollected ? (item.collNum - 1) : (item.collNum + 1);
|
||||
|
||||
uni.showToast({
|
||||
title: isCollected ? '取消收藏成功' : '收藏成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('收藏操作失败:', error);
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 点赞/取消点赞
|
||||
const handleLike = async (item: any) => {
|
||||
try {
|
||||
const res = await zyLikeApi({ zyId: item.id });
|
||||
if (res.resultCode === 1) {
|
||||
// 更新状态(直接修改对象)
|
||||
const isLiked = item.isLike === '1';
|
||||
item.isLike = isLiked ? '0' : '1';
|
||||
item.likeNum = isLiked ? (item.likeNum - 1) : (item.likeNum + 1);
|
||||
|
||||
uni.showToast({
|
||||
title: isLiked ? '取消点赞成功' : '点赞成功',
|
||||
icon: 'success'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('点赞操作失败:', error);
|
||||
uni.showToast({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 打开评分弹窗
|
||||
const handleRating = (item: any) => {
|
||||
currentResource.value = item;
|
||||
ratingValue.value = item.userScore || 0;
|
||||
ratingComment.value = '';
|
||||
showRatingModal.value = true;
|
||||
};
|
||||
|
||||
// 关闭评分弹窗
|
||||
const closeRatingModal = () => {
|
||||
showRatingModal.value = false;
|
||||
currentResource.value = null;
|
||||
ratingValue.value = 0;
|
||||
ratingComment.value = '';
|
||||
};
|
||||
|
||||
// 提交评分
|
||||
const submitRating = async () => {
|
||||
if (ratingValue.value === 0) {
|
||||
uni.showToast({
|
||||
title: '请先评分',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await zyScoreApi({
|
||||
zyId: currentResource.value.id,
|
||||
score: ratingValue.value,
|
||||
comment: ratingComment.value
|
||||
});
|
||||
|
||||
if (res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: '评分成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
closeRatingModal();
|
||||
reload(); // 刷新列表
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '评分失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('评分失败:', error);
|
||||
uni.showToast({
|
||||
title: '评分失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到详情页
|
||||
const goToDetail = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/view/routine/JiaoXueZiYuan/zy-detail?id=${item.id}`
|
||||
});
|
||||
};
|
||||
|
||||
// 跳转到新增页面
|
||||
const navigateToAdd = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/view/routine/JiaoXueZiYuan/zy-add'
|
||||
});
|
||||
};
|
||||
|
||||
// 加载资源类别字典
|
||||
const loadCategoryOptions = async () => {
|
||||
try {
|
||||
const pid = '1391443399'; // 资源类别字典父ID(与后台一致)
|
||||
const res = await dicFindByPidApi({ pid });
|
||||
|
||||
if (res && res.result && Array.isArray(res.result)) {
|
||||
categoryOptions.value = res.result.map((item: any) => ({
|
||||
key: item.dictionaryCode || item.dictionaryValue || item.id,
|
||||
label: item.dictionaryName || item.dictionaryValue || item.name
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源类别失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新列表的方法
|
||||
const refreshList = () => {
|
||||
console.log('收到刷新事件,重新加载资源包列表');
|
||||
reload();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadCategoryOptions();
|
||||
buildParams();
|
||||
|
||||
// 监听刷新事件
|
||||
uni.$on('refreshResourceList', refreshList);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除事件监听
|
||||
uni.$off('refreshResourceList', refreshList);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-section {
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 30rpx;
|
||||
padding: 15rpx 25rpx;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
margin-left: 15rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-clear {
|
||||
margin-left: 10rpx;
|
||||
padding: 5rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-package-card {
|
||||
background: #fff;
|
||||
margin: 15rpx 10rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
position: relative;
|
||||
|
||||
// 左侧缩略图
|
||||
.card-thumbnail {
|
||||
flex-shrink: 0;
|
||||
width: 110rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
|
||||
.thumbnail-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧内容
|
||||
.card-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
padding-right: 50rpx; // 为详情箭头留出空间
|
||||
|
||||
.package-category {
|
||||
display: inline-block;
|
||||
align-self: flex-start;
|
||||
padding: 4rpx 16rpx;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 4rpx;
|
||||
font-size: 22rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.package-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
margin-bottom: auto; // 推动底部内容到底部
|
||||
|
||||
.meta-text {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: 25rpx;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-score {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #ff8800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧详情箭头
|
||||
.card-arrow {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 底部新增按钮
|
||||
.bottom-bar {
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 评分弹窗遮罩层
|
||||
.rating-modal-mask {
|
||||
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;
|
||||
}
|
||||
|
||||
// 评分弹窗内容
|
||||
.rating-modal {
|
||||
width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx;
|
||||
margin: 0 20rpx;
|
||||
|
||||
.modal-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
.resource-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 25rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rating-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.star-picker {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
|
||||
.star-item {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.star-icon {
|
||||
font-size: 40rpx;
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-textarea {
|
||||
width: 100%;
|
||||
min-height: 150rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.char-count {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 30rpx;
|
||||
|
||||
.modal-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.cancel-btn {
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.confirm-btn {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -57,18 +57,18 @@
|
||||
<text style="margin-right: 8px;">{{ idx + 1 }}、{{ xm.xcMc }}</text>
|
||||
<view style="display: flex; align-items: center;">
|
||||
<u-icon
|
||||
:name="xm.xcJg === 'B' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'B' ? '#67c23a' : '#f56c6c'"
|
||||
:name="xm.xcJg === 'A' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'A' ? '#67c23a' : '#f56c6c'"
|
||||
size="18"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view style="font-size: 12px; color: #666;">
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #f56c6c;">
|
||||
扣分:-{{ xm.xmFz }}分
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #67c23a;">
|
||||
优点
|
||||
</text>
|
||||
<text v-else style="color: #67c23a;">
|
||||
不扣分
|
||||
<text v-else style="color: #f56c6c;">
|
||||
缺点
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -55,18 +55,18 @@
|
||||
<text style="margin-right: 8px;">{{ idx + 1 }}、{{ xm.xcMc }}</text>
|
||||
<view style="display: flex; align-items: center;">
|
||||
<u-icon
|
||||
:name="xm.xcJg === 'B' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'B' ? '#67c23a' : '#f56c6c'"
|
||||
:name="xm.xcJg === 'A' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'A' ? '#67c23a' : '#f56c6c'"
|
||||
size="18"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view style="font-size: 12px; color: #666;">
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #f56c6c;">
|
||||
扣分:-{{ xm.xmFz }}分
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #67c23a;">
|
||||
优点
|
||||
</text>
|
||||
<text v-else style="color: #67c23a;">
|
||||
不扣分
|
||||
<text v-else style="color: #f56c6c;">
|
||||
缺点
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -85,7 +85,7 @@ import { useDataStore } from "@/store/modules/data";
|
||||
import { getPbPageApi } from "@/api/base/pbApi";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getJs, getUser } = useUserStore();
|
||||
const { getData, setData } = useDataStore();
|
||||
|
||||
// 排班列表数据
|
||||
@ -118,7 +118,7 @@ const loadPbList = async (isRefresh = false) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const params = {
|
||||
const params: any = {
|
||||
page: pagination.page,
|
||||
rows: pagination.pageSize,
|
||||
xclx: 'C', // 巡查类型:C-值周巡查
|
||||
@ -128,6 +128,30 @@ const loadPbList = async (isRefresh = false) => {
|
||||
// xqId: '', // 学期ID
|
||||
};
|
||||
|
||||
// 权限判断:如果不是 admin 用户,则传递当前教师ID进行过滤
|
||||
const user = getUser;
|
||||
const js = getJs;
|
||||
const empCode = user?.empCode || '';
|
||||
|
||||
// 调试日志
|
||||
console.log('=== 值周巡查权限判断 ===');
|
||||
console.log('getUser:', user);
|
||||
console.log('getUser.empCode:', user?.empCode);
|
||||
console.log('empCode:', empCode);
|
||||
console.log('getJs:', js);
|
||||
console.log('getJs.id:', js?.id);
|
||||
|
||||
if (empCode === 'admin') {
|
||||
// admin 用户:不传任何过滤参数,可以查看所有排班
|
||||
console.log('权限:admin 用户,查看所有排班');
|
||||
} else {
|
||||
// 非 admin 用户:传递 dqjsId(使用 FIND_IN_SET 精确匹配),只能查看自己参与的排班
|
||||
params.dqjsId = js?.id; // 当前教师ID
|
||||
console.log('权限:普通用户,过滤条件 dqjsId =', params.dqjsId);
|
||||
}
|
||||
console.log('最终请求参数:', params);
|
||||
console.log('=========================');
|
||||
|
||||
const res: any = await getPbPageApi(params);
|
||||
|
||||
// 根据实际API响应结构判断成功条件
|
||||
|
||||
@ -53,6 +53,20 @@
|
||||
</view>
|
||||
|
||||
<view v-if="canInspect">
|
||||
<!-- 巡查年级班级 -->
|
||||
<view class="section mx-15 mb-15">
|
||||
<view class="section-title-bar">
|
||||
<view class="decorator"></view>
|
||||
<text class="title-text">巡查年级班级</text>
|
||||
</view>
|
||||
<view class="class-select-card bg-white r-md p-15">
|
||||
<view class="class-selector" @click="showClassTree">
|
||||
<text :class="{ placeholder: !selectedClassText }">{{ selectedClassText || "请选择年级班级" }}</text>
|
||||
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 巡查项目 -->
|
||||
<view class="section mx-15 mb-15">
|
||||
<view class="section-title-bar">
|
||||
@ -160,7 +174,7 @@
|
||||
:disabled="isSubmitting"
|
||||
@click="submit"
|
||||
>
|
||||
{{ isSubmitting ? (xcRecordId ? '更新中...' : '提交中...') : (xcRecordId ? '更新巡查' : '提交巡查') }}
|
||||
{{ isSubmitting ? '提交中...' : '提交巡查' }}
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
@ -198,6 +212,19 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 班级选择树 -->
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:range="treeData"
|
||||
idKey="key"
|
||||
rangeKey="title"
|
||||
title="选择年级班级"
|
||||
:multiple="false"
|
||||
:selectParent="false"
|
||||
@confirm="onTreeConfirm"
|
||||
@cancel="onTreeCancel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -206,6 +233,8 @@ import { zbXcSaveApi } from "@/api/base/zbXcApi";
|
||||
import { attachmentUpload } from "@/api/system/upload";
|
||||
import BasicLayout from "@/components/BasicLayout/Layout.vue";
|
||||
import { ImageVideoUpload } from "@/components/ImageVideoUpload";
|
||||
import BasicTree from '@/components/BasicTree/Tree.vue';
|
||||
import { findAllNjBjTree } from '@/api/base/server';
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
@ -233,8 +262,19 @@ const todayInfo = ref({
|
||||
// 巡查项目
|
||||
const checkItems = ref<any[]>([]);
|
||||
|
||||
// 巡查记录ID(用于更新)
|
||||
const xcRecordId = ref('');
|
||||
// 年级班级选择相关
|
||||
const treeData = ref<any[]>([]);
|
||||
const treeRef = ref();
|
||||
const curNj = ref<any>(null);
|
||||
const curBj = ref<any>(null);
|
||||
|
||||
// 计算属性:显示选中的班级文本
|
||||
const selectedClassText = computed(() => {
|
||||
if (curBj.value && curNj.value) {
|
||||
return `${curNj.value.title} ${curBj.value.title}`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 导入类型和配置
|
||||
import { type ImageItem, type VideoItem, COMPRESS_PRESETS } from '@/components/ImageVideoUpload'
|
||||
@ -265,6 +305,60 @@ const formatTime = (timestamp: string) => {
|
||||
return dayjs(timestamp).format('MM-DD');
|
||||
};
|
||||
|
||||
// 加载树形数据
|
||||
const loadTreeData = async () => {
|
||||
try {
|
||||
const res = await findAllNjBjTree();
|
||||
if (res.resultCode === 1 && res.result) {
|
||||
// 递归转换数据格式以适配 BasicTree 组件
|
||||
const convertTreeData = (items: any[]): any[] => {
|
||||
return items.map((item: any) => ({
|
||||
key: item.key,
|
||||
title: item.title,
|
||||
njmcId: item.njmcId,
|
||||
children: item.children ? convertTreeData(item.children) : [],
|
||||
}));
|
||||
};
|
||||
treeData.value = convertTreeData(res.result);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({ title: "加载班级数据失败", icon: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
// 显示班级选择树
|
||||
const showClassTree = () => {
|
||||
if (treeRef.value) {
|
||||
treeRef.value._show();
|
||||
}
|
||||
};
|
||||
|
||||
// 树形选择确认
|
||||
const onTreeConfirm = (selectedItems: any[]) => {
|
||||
if (selectedItems.length > 0) {
|
||||
const selectedItem = selectedItems[0]; // 单选模式,取第一个
|
||||
|
||||
// 如果选择的是班级(有parents表示是班级)
|
||||
if (selectedItem.parents && selectedItem.parents.length > 0) {
|
||||
const parent = selectedItem.parents[0]; // 年级信息
|
||||
const nj = parent; // 年级信息
|
||||
const bj = selectedItem; // 班级信息
|
||||
|
||||
curNj.value = nj;
|
||||
curBj.value = bj;
|
||||
} else {
|
||||
// 如果选择的是年级,清空班级选择
|
||||
curNj.value = selectedItem;
|
||||
curBj.value = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 树形选择取消
|
||||
const onTreeCancel = () => {
|
||||
// 取消选择,不做任何操作
|
||||
};
|
||||
|
||||
// 加载巡查项目
|
||||
const loadCheckItems = async () => {
|
||||
try {
|
||||
@ -289,11 +383,6 @@ const loadCheckItems = async () => {
|
||||
};
|
||||
});
|
||||
console.log('巡查项目列表:', checkItems.value);
|
||||
|
||||
// 如果有巡查记录,回显数据
|
||||
if (zb.value.xcRecord) {
|
||||
restoreXcRecord(zb.value.xcRecord);
|
||||
}
|
||||
} else {
|
||||
checkItems.value = [];
|
||||
}
|
||||
@ -303,85 +392,6 @@ const loadCheckItems = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 回显巡查记录数据
|
||||
const restoreXcRecord = (xcRecord: any) => {
|
||||
try {
|
||||
console.log('========== 开始回显巡查记录 ==========');
|
||||
console.log('巡查记录ID:', xcRecord.id);
|
||||
console.log('巡查记录 zbXcXmList:', xcRecord.zbXcXmList);
|
||||
console.log('当前巡查项目列表:', checkItems.value.map(item => ({
|
||||
id: item.id,
|
||||
xcMc: item.xcMc
|
||||
})));
|
||||
|
||||
// 保存巡查记录ID
|
||||
xcRecordId.value = xcRecord.id || '';
|
||||
console.log('保存的巡查记录ID:', xcRecordId.value);
|
||||
|
||||
// 回显巡查项目数据
|
||||
if (xcRecord.zbXcXmList && xcRecord.zbXcXmList.length > 0) {
|
||||
console.log('开始回显', xcRecord.zbXcXmList.length, '个巡查项目');
|
||||
|
||||
let matchedCount = 0;
|
||||
xcRecord.zbXcXmList.forEach((xcXm: any, index: number) => {
|
||||
console.log(`\n[${index + 1}] 尝试匹配项目:`);
|
||||
console.log(' xcXm.id:', xcXm.id);
|
||||
console.log(' xcXm.xcXmId:', xcXm.xcXmId);
|
||||
console.log(' xcXm.xcMc:', xcXm.xcMc);
|
||||
console.log(' xcXm.xcJg:', xcXm.xcJg);
|
||||
console.log(' xcXm.xcPj:', xcXm.xcPj);
|
||||
|
||||
const checkItem = checkItems.value.find(item => item.id === xcXm.xcXmId);
|
||||
if (checkItem) {
|
||||
console.log(' ✓ 找到匹配项目:', checkItem.xcMc);
|
||||
checkItem.xcJg = xcXm.xcJg || '';
|
||||
checkItem.xcPj = xcXm.xcPj || '';
|
||||
checkItem.xcXmRecordId = xcXm.id || ''; // 保存巡查项目记录ID
|
||||
matchedCount++;
|
||||
console.log(' 设置后 - xcJg:', checkItem.xcJg, ', xcPj:', checkItem.xcPj);
|
||||
} else {
|
||||
console.log(' ✗ 未找到匹配项目!');
|
||||
console.log(' 可用的项目ID:', checkItems.value.map(item => item.id).join(', '));
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`\n成功匹配 ${matchedCount}/${xcRecord.zbXcXmList.length} 个巡查项目`);
|
||||
} else {
|
||||
console.log('⚠️ 没有巡查项目记录需要回显(zbXcXmList 为空或不存在)');
|
||||
}
|
||||
|
||||
// 回显图片
|
||||
if (xcRecord.zp) {
|
||||
const imageUrls = xcRecord.zp.split(',').map((url: string) => url.trim()).filter((url: string) => url);
|
||||
imageList.value = imageUrls.map((url: string) => ({
|
||||
url: url,
|
||||
path: imagUrl(url),
|
||||
uploaded: true
|
||||
}));
|
||||
console.log('✓ 回显图片:', imageList.value.length, '张');
|
||||
}
|
||||
|
||||
// 回显视频
|
||||
if (xcRecord.sp) {
|
||||
const videoUrls = xcRecord.sp.split(',').map((url: string) => url.trim()).filter((url: string) => url);
|
||||
videoList.value = videoUrls.map((url: string) => ({
|
||||
url: url,
|
||||
path: imagUrl(url),
|
||||
uploaded: true
|
||||
}));
|
||||
console.log('✓ 回显视频:', videoList.value.length, '个');
|
||||
}
|
||||
|
||||
console.log('\n回显完成,最终巡查项目状态:');
|
||||
checkItems.value.forEach((item, index) => {
|
||||
console.log(` [${index + 1}] ${item.xcMc} - xcJg: ${item.xcJg || '未选择'}, xcPj: ${item.xcPj || '无'}`);
|
||||
});
|
||||
console.log('========== 回显完成 ==========\n');
|
||||
} catch (error) {
|
||||
console.error('❌ 回显巡查记录失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const onCheckItemChange = (e: any, item: any) => {
|
||||
const value = e.detail.value; // 'A' 或 'B'
|
||||
const label = value === 'A' ? '优点' : '缺点';
|
||||
@ -506,6 +516,16 @@ const submit = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证是否选择了年级班级
|
||||
if (!curBj.value || !curNj.value) {
|
||||
uni.showToast({
|
||||
title: "请选择巡查的年级班级",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证巡查项目是否已选择
|
||||
const hasCheckedItems = checkItems.value.some(item => item.xcJg && item.xcPj);
|
||||
if (!hasCheckedItems) {
|
||||
@ -524,20 +544,13 @@ const submit = async () => {
|
||||
const zbXcXmList = checkItems.value
|
||||
.filter((item: any) => item.xcJg && item.xcPj)
|
||||
.map((item: any) => {
|
||||
const newItem = {
|
||||
...item,
|
||||
return {
|
||||
xcXmId: item.id,
|
||||
xcJg: item.xcJg, // A-优点,B-缺点
|
||||
xcPj: item.xcPj, // 评价内容
|
||||
zbXcId: xcRecordId.value || "", // 巡查记录ID(更新时使用)
|
||||
xmFz: item.xmFz, // 项目分值
|
||||
xcMc: item.xcMc, // 巡查名称
|
||||
};
|
||||
// 如果是更新操作且有巡查项目记录ID,保留该ID
|
||||
if (xcRecordId.value && item.xcXmRecordId) {
|
||||
newItem.id = item.xcXmRecordId;
|
||||
} else {
|
||||
newItem.id = "";
|
||||
}
|
||||
return newItem;
|
||||
});
|
||||
|
||||
const submitData: any = {
|
||||
@ -548,21 +561,20 @@ const submit = async () => {
|
||||
xctime: now.format("YYYY-MM-DD HH:mm:ss"),
|
||||
zp: getImageUrls(),
|
||||
sp: getVideoUrls(),
|
||||
njId: curNj.value.key, // 年级ID
|
||||
njmcId: curNj.value.njmcId || curNj.value.key, // 年级名称ID
|
||||
bjId: curBj.value.key, // 班级ID
|
||||
bc: selectedClassText.value, // 别称(如:三年级1班)
|
||||
zbXcXmList: zbXcXmList,
|
||||
};
|
||||
|
||||
// 如果是更新操作,添加记录ID
|
||||
if (xcRecordId.value) {
|
||||
submitData.id = xcRecordId.value;
|
||||
}
|
||||
|
||||
console.log('提交值周巡查数据:', submitData);
|
||||
|
||||
const res = await zbXcSaveApi(submitData);
|
||||
|
||||
if (res && res.resultCode === 1) {
|
||||
uni.showToast({
|
||||
title: xcRecordId.value ? "更新成功" : "提交成功",
|
||||
title: "提交成功",
|
||||
icon: "success",
|
||||
});
|
||||
setTimeout(() => {
|
||||
@ -575,14 +587,14 @@ const submit = async () => {
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: xcRecordId.value ? "更新失败" : "提交失败",
|
||||
title: "提交失败",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交值周巡查失败:', error);
|
||||
uni.showToast({
|
||||
title: xcRecordId.value ? "更新失败" : "提交失败",
|
||||
title: "提交失败",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
@ -613,6 +625,7 @@ const getVideoUrls = () => {
|
||||
// 页面加载时获取状态选项
|
||||
onMounted(async () => {
|
||||
await loadCheckItems();
|
||||
await loadTreeData(); // 加载年级班级树形数据
|
||||
checkInspectionTime();
|
||||
});
|
||||
</script>
|
||||
@ -819,6 +832,35 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.class-select-card {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.class-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@ -79,13 +79,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getZbXcListApi } from "@/api/base/pbApi";
|
||||
import { zbXcFindPageApi } from "@/api/base/zbXcApi";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { getJs } = useUserStore();
|
||||
const { getJs, getUser } = useUserStore();
|
||||
const dataStore = useDataStore();
|
||||
|
||||
// 值周列表数据
|
||||
@ -214,16 +213,34 @@ const loadZbXcList = async (pbData: any) => {
|
||||
|
||||
const pbId = pbData.pbId;
|
||||
|
||||
// 权限判断:如果不是 admin 用户,则传递 dqjsId 进行权限过滤
|
||||
const user = getUser;
|
||||
const empCode = user?.empCode || '';
|
||||
|
||||
// 构建请求参数
|
||||
const apiParams: any = {
|
||||
pbId: pbId
|
||||
};
|
||||
|
||||
// 根据用户类型决定传递哪个参数
|
||||
if (empCode === 'admin') {
|
||||
// admin 用户:传递 jsId
|
||||
apiParams.jsId = '';
|
||||
apiParams.qdjsId = getJs.id;
|
||||
console.log('权限:admin 用户,传递 jsId');
|
||||
} else {
|
||||
// 非 admin 用户:传递 dqjsId 进行权限过滤
|
||||
apiParams.jsId = getJs.id;
|
||||
apiParams.qdjsId = '';
|
||||
console.log('权限:普通用户,传递 dqjsId 进行权限过滤');
|
||||
}
|
||||
|
||||
console.log('API调用参数:', {
|
||||
jsId: getJs.id,
|
||||
pbId: pbId,
|
||||
...apiParams,
|
||||
pbData: pbData
|
||||
});
|
||||
|
||||
const res = await getZbXcListApi({
|
||||
jsId: getJs.id,
|
||||
pbId: pbId
|
||||
});
|
||||
const res = await getZbXcListApi(apiParams);
|
||||
|
||||
if (res && res.resultCode == 1) {
|
||||
const list = res.result || [];
|
||||
@ -295,8 +312,8 @@ const refreshZbList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到巡查
|
||||
const goXc = async (zb: any) => {
|
||||
// 跳转到巡查(纯新增模式,不查询历史记录)
|
||||
const goXc = (zb: any) => {
|
||||
const pbData = dataStore.getGlobal;
|
||||
|
||||
// 检查排班数据是否有效
|
||||
@ -318,68 +335,6 @@ const goXc = async (zb: any) => {
|
||||
xqmc: pbData.xqmc
|
||||
};
|
||||
|
||||
// 如果已巡查,查询巡查记录数据
|
||||
if (zb.sfxc === '是') {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
console.log('========== 查询已巡查记录 ==========');
|
||||
console.log('查询参数 pbZbId:', zb.id);
|
||||
|
||||
const res = await zbXcFindPageApi({
|
||||
rows: 10,
|
||||
page: 1,
|
||||
pbZbId: zb.id
|
||||
});
|
||||
|
||||
console.log('查询结果完整数据:', res);
|
||||
console.log('查询结果 resultCode:', res?.resultCode);
|
||||
console.log('查询结果 result 数量:', res?.result?.length);
|
||||
console.log('查询结果 rows:', res?.rows);
|
||||
console.log('查询结果 rows 数量:', res?.rows?.length);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
// 尝试从不同的字段获取数据
|
||||
let xcRecordList = res?.result || res?.rows || [];
|
||||
|
||||
if (res && (res.resultCode === 1 || res.resultCode === '1' || xcRecordList.length > 0)) {
|
||||
if (xcRecordList.length > 0) {
|
||||
// 获取最新的一条巡查记录
|
||||
const xcRecord = xcRecordList[0];
|
||||
combinedData.xcRecord = xcRecord;
|
||||
console.log('✓ 获取到巡查记录ID:', xcRecord.id);
|
||||
console.log(' - jsxm:', xcRecord.jsxm);
|
||||
console.log(' - xctime:', xcRecord.xctime);
|
||||
console.log(' - zbXcXmList 数量:', xcRecord.zbXcXmList?.length || 0);
|
||||
if (xcRecord.zbXcXmList && xcRecord.zbXcXmList.length > 0) {
|
||||
console.log(' - zbXcXmList 详情:', xcRecord.zbXcXmList.map((xm: any) => ({
|
||||
xcXmId: xm.xcXmId,
|
||||
xcMc: xm.xcMc,
|
||||
xcJg: xm.xcJg,
|
||||
xcPj: xm.xcPj
|
||||
})));
|
||||
} else {
|
||||
console.log(' ⚠️ zbXcXmList 为空!');
|
||||
}
|
||||
console.log('========== 查询完成 ==========');
|
||||
} else {
|
||||
console.log('❌ 返回数据为空');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 未查询到巡查记录或查询失败');
|
||||
console.log(' 返回数据结构:', Object.keys(res || {}));
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('查询巡查记录失败:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('该值周记录尚未巡查,无需回显');
|
||||
}
|
||||
|
||||
console.log('点击巡查,传递数据:', combinedData);
|
||||
|
||||
dataStore.setData(combinedData);
|
||||
|
||||
@ -63,18 +63,18 @@
|
||||
<text style="margin-right: 8px;">{{ idx + 1 }}、{{ xm.xcMc }}</text>
|
||||
<view style="display: flex; align-items: center;">
|
||||
<u-icon
|
||||
:name="xm.xcJg === 'B' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'B' ? '#67c23a' : '#f56c6c'"
|
||||
:name="xm.xcJg === 'A' ? 'checkmark-circle-fill' : 'close-circle-fill'"
|
||||
:color="xm.xcJg === 'A' ? '#67c23a' : '#f56c6c'"
|
||||
size="18"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view style="font-size: 12px; color: #666;">
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #f56c6c;">
|
||||
扣分:-{{ xm.xmFz }}分
|
||||
<text v-if="xm.xcJg === 'A'" style="color: #67c23a;">
|
||||
优点
|
||||
</text>
|
||||
<text v-else style="color: #67c23a;">
|
||||
不扣分
|
||||
<text v-else style="color: #f56c6c;">
|
||||
缺点
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
BIN
src/static/base/view/zyyl.png
Normal file
BIN
src/static/base/view/zyyl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Loading…
x
Reference in New Issue
Block a user