Files
smartDriveEEUniApp/pages/champion/components/material-tab.vue
2025-12-28 12:21:16 +08:00

1395 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<scroll-view
class="tab-content material-content"
scroll-y
@scrolltolower="onMaterialReachBottom">
<view class="service-status-toolbar">
<text class="toolbar-total">{{ materialTotal }}</text>
<input
class="toolbar-input"
v-model="materialQuery.title"
placeholder="知识标题"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onMaterialSearch"
/>
<input
class="toolbar-input"
v-model="materialQuery.industry"
placeholder="应用行业"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onMaterialSearch"
/>
<view class="toolbar-actions">
<view class="toolbar-btn toolbar-btn--refresh" @click="onMaterialRefresh">
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
<text>刷新</text>
</view>
<view class="toolbar-btn toolbar-btn--add" @click="showAddMaterial">
<uni-icons type="plus" size="18" color="#2A68FF"></uni-icons>
</view>
</view>
</view>
<view
class="service-card material-card-item"
v-for="(item, index) in materialList"
:key="item.id || index">
<view class="card-header">
<view class="staff-info">
<view class="staff-avatar">
<text class="avatar-text">{{ (item.title || '知').charAt(0) }}</text>
</view>
<text class="staff-name">{{ item.title || '未命名知识' }}</text>
</view>
<view class="card-menu-trigger" @tap.stop="showContextMenu(item, index, $event)">
<uni-icons type="more-filled" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="customer-info">
<text class="customer-name" v-if="item.industry">所属行业: {{ item.industry }}</text>
</view>
<view class="customer-tags">
<view class="tag-item tag-blue" v-if="item.content || item.detail">
<text @tap.stop="showFullText('详情', item.content || item.detail)">详情: {{ getContentPreview(item.content || item.detail, 30) }}</text>
</view>
<view class="tag-item tag-orange" v-if="item.remark">
<text @tap.stop="showFullText('提示词', item.remark)">提示词: {{ getContentPreview(item.remark, 30) }}</text>
</view>
</view>
<!-- 上下文菜单 -->
<view
class="context-menu"
v-if="contextMenuVisible && contextMenuIndex === index"
:style="{ top: contextMenuTop + 'px', left: contextMenuLeft + 'px' }"
@tap.stop>
<view class="context-menu-item" @tap.stop="handleEditMaterial(item)">
<text>编辑</text>
</view>
<view class="context-menu-item context-menu-item--danger" @tap.stop="handleDeleteMaterial(item)">
<text>删除</text>
</view>
</view>
</view>
<view class="service-status-empty" v-if="!materialLoading && !materialList.length">
<text>暂无素材</text>
</view>
<view class="service-status-loading-more" v-if="materialLoading && materialList.length">
<text>正在加载更多...</text>
</view>
</scroll-view>
<!-- 添加素材弹出框 -->
<view class="material-popup-mask" v-if="showMaterialPopup" @click="closeMaterialPopup">
</view>
<view class="material-popup" v-if="showMaterialPopup" @click.stop>
<view class="popup-header">
<text class="popup-title">添加素材</text>
<view class="popup-close" @click="closeMaterialPopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="form-card">
<view class="form-item">
<text class="form-item__label">知识标题</text>
<input
class="form-item__input"
v-model="materialForm.title"
placeholder="请输入知识标题"
placeholder-style="color: #9ca3af"
maxlength="512"
/>
</view>
<view class="form-item">
<text class="form-item__label">知识分类</text>
<input
class="form-item__input"
v-model="materialForm.category"
placeholder="请输入知识分类"
placeholder-style="color: #9ca3af"
maxlength="100"
:disabled="true"
readonly
/>
</view>
<view class="form-item">
<text class="form-item__label">应用行业</text>
<input
class="form-item__input"
v-model="materialForm.industry"
placeholder="请输入应用行业"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">文件上传</text>
<view class="file-upload-area">
<view class="file-item" v-if="materialForm.file">
<text class="file-name">{{ materialForm.file.name || '文件' }}</text>
<view class="file-delete" @click="removeFile">
<uni-icons type="close" size="16" color="#999"></uni-icons>
</view>
</view>
<view class="file-upload-btn" v-if="!materialForm.file" @click="chooseFile">
<uni-icons type="plus" size="24" color="#2A68FF"></uni-icons>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="popup-actions">
<view class="popup-btn popup-btn--cancel" @click="closeMaterialPopup">
<uni-icons class="popup-btn__icon" type="close" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">取消</text>
</view>
<view class="popup-btn popup-btn--submit" @click="submitMaterial">
<uni-icons class="popup-btn__icon" type="checkmarkempty" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">提交</text>
</view>
</view>
</view>
<!-- 编辑素材弹出框 -->
<view class="material-popup-mask" v-if="showEditMaterialPopup" @click="closeEditMaterialPopup">
</view>
<view class="material-popup" v-if="showEditMaterialPopup" @click.stop>
<view class="popup-header">
<text class="popup-title">编辑素材</text>
<view class="popup-close" @click="closeEditMaterialPopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="form-card">
<view class="form-item">
<text class="form-item__label">知识标题</text>
<input
class="form-item__input"
v-model="editMaterialForm.title"
placeholder="请输入知识标题"
placeholder-style="color: #9ca3af"
maxlength="512"
/>
</view>
<view class="form-item">
<text class="form-item__label">知识分类</text>
<input
class="form-item__input"
v-model="editMaterialForm.category"
placeholder="请输入知识分类"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">应用行业</text>
<input
class="form-item__input"
v-model="editMaterialForm.industry"
placeholder="请输入应用行业"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">详情</text>
<textarea
class="form-item__textarea"
v-model="editMaterialForm.content"
placeholder="请输入详情内容"
placeholder-style="color: #9ca3af"
maxlength="2000"
/>
</view>
<view class="form-item">
<text class="form-item__label">备注</text>
<input
class="form-item__input"
v-model="editMaterialForm.remark"
placeholder="请输入备注"
placeholder-style="color: #9ca3af"
maxlength="500"
/>
</view>
</view>
</scroll-view>
<view class="popup-actions">
<view class="popup-btn popup-btn--cancel" @click="closeEditMaterialPopup">
<uni-icons class="popup-btn__icon" type="close" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">取消</text>
</view>
<view class="popup-btn popup-btn--submit" @click="submitEditMaterial">
<uni-icons class="popup-btn__icon" type="checkmarkempty" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">保存</text>
</view>
</view>
</view>
<!-- 上下文菜单遮罩层 -->
<view class="context-menu-mask" v-if="contextMenuVisible" @tap="closeContextMenu"></view>
<!-- 内容详情弹出框 -->
<view class="content-popup-mask" v-if="showContentPopup" @click="closeContentPopup">
</view>
<view class="content-popup" v-if="showContentPopup" @click.stop>
<view class="content-popup-header">
<text class="content-popup-title">{{ contentPopupTitle }}</text>
<view class="content-popup-close" @click="closeContentPopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="content-popup-body" scroll-y>
<text class="content-popup-text">{{ contentPopupText }}</text>
</scroll-view>
</view>
</view>
</template>
<script>
import { getApiUrl } from "@/common/config.js";
// 获取租户ID
function getTenantId() {
try {
return uni.getStorageSync('backend-tenant-id') || '';
} catch (e) {
console.error('获取 tenantId 失败:', e);
return '';
}
}
export default {
name: 'MaterialTab',
data() {
return {
// 素材相关数据
materialList: [],
materialLoading: false,
materialTotal: 0,
materialPage: {
current: 1,
size: 10
},
materialQuery: {
title: '',
category: '基础知识库',
industry: '',
creator: '',
status: ''
},
// 添加素材弹出框
showMaterialPopup: false,
materialForm: {
title: '',
category: '',
industry: '',
file: null
},
// 编辑素材弹出框
showEditMaterialPopup: false,
editMaterialForm: {
id: '',
title: '',
category: '',
industry: '',
content: '',
remark: ''
},
// 上下文菜单
contextMenuVisible: false,
contextMenuIndex: -1,
contextMenuTop: 0,
contextMenuLeft: 0,
// 内容详情弹出框
showContentPopup: false,
contentPopupTitle: '',
contentPopupText: ''
}
},
mounted() {
this.fetchMaterialList();
},
methods: {
// 素材相关方法
onMaterialSearch() {
this.materialPage.current = 1;
this.fetchMaterialList({ force: true });
},
onMaterialRefresh() {
this.materialPage.current = 1;
this.fetchMaterialList({ force: true });
},
onMaterialReachBottom() {
// 已在加载中或全部加载完成则不再触发
if (this.materialLoading) return;
if (this.materialList.length >= this.materialTotal) return;
this.materialPage.current += 1;
this.fetchMaterialList();
},
async fetchMaterialList({ force = false } = {}) {
if (this.materialLoading && !force) {
return;
}
this.materialLoading = true;
try {
const queryParams = {
current: this.materialPage.current,
size: this.materialPage.size,
category: '基础知识库' // 默认添加知识分类参数
};
const trimmedTitle = this.materialQuery?.title?.trim();
const trimmedIndustry = this.materialQuery?.industry?.trim();
const trimmedCreator = this.materialQuery?.creator?.trim();
const trimmedStatus = this.materialQuery?.status?.trim();
if (trimmedTitle) {
queryParams.title = trimmedTitle;
}
if (trimmedIndustry) {
queryParams.industry = trimmedIndustry;
}
if (trimmedCreator) {
queryParams.creator = trimmedCreator;
}
if (trimmedStatus) {
queryParams.status = trimmedStatus;
}
const queryString = Object.keys(queryParams)
.map(key => `${key}=${encodeURIComponent(queryParams[key])}`)
.join('&');
const url = `${getApiUrl('/api/knowledgeBase/list')}?${queryString}`;
// 获取租户ID
const tenantId = getTenantId();
const headers = {};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const res = await uni.request({
url,
method: 'POST',
header: headers,
timeout: 10000
});
if (res.statusCode === 200 && res.data && res.data.success) {
const records = Array.isArray(res.data.data) ? res.data.data : [];
// 第一页或强制刷新时重置列表,否则追加
if (this.materialPage.current === 1 || force) {
this.materialList = records;
} else {
this.materialList = this.materialList.concat(records);
}
this.materialTotal = Number(res.data.total) || 0;
} else {
this.materialList = [];
this.materialTotal = 0;
uni.showToast({
title: res.data?.message || '获取素材列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取素材列表失败:', error);
uni.showToast({
title: '获取素材列表失败,请稍后重试',
icon: 'none'
});
} finally {
this.materialLoading = false;
}
},
formatDateTime(value) {
if (!value) {
return '';
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return '';
}
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');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
getContentPreview(content, limit = 30) {
if (!content) return '';
const text = String(content).replace(/\s+/g, ' ').trim();
if (!text) return '';
return text.length > limit ? `${text.substring(0, limit)}...` : text;
},
showFullText(title, content) {
if (!content) return;
this.contentPopupTitle = title || '详情';
this.contentPopupText = String(content);
this.showContentPopup = true;
},
closeContentPopup() {
this.showContentPopup = false;
this.contentPopupTitle = '';
this.contentPopupText = '';
},
showContextMenu(item, index, event) {
// 阻止事件冒泡
if (event && event.stopPropagation) {
event.stopPropagation();
}
// 获取系统信息以计算位置
const systemInfo = uni.getSystemInfoSync();
// 获取点击位置 - 优先使用触摸事件坐标
let top = 100;
let left = systemInfo.windowWidth - 180;
if (event) {
if (event.touches && event.touches.length > 0) {
// 触摸事件
top = event.touches[0].clientY;
left = event.touches[0].clientX - 80;
} else if (event.detail && typeof event.detail.x === 'number') {
// tap事件坐标单位px需要转换
top = event.detail.y;
left = event.detail.x - 80;
} else if (event.clientY !== undefined) {
// 鼠标事件
top = event.clientY;
left = event.clientX - 80;
}
}
// 确保菜单不超出屏幕
if (left + 160 > systemInfo.windowWidth) {
left = systemInfo.windowWidth - 180;
}
if (left < 0) {
left = 10;
}
this.contextMenuTop = top;
this.contextMenuLeft = left;
// 显示菜单
this.contextMenuIndex = index;
this.contextMenuVisible = true;
},
closeContextMenu() {
this.contextMenuVisible = false;
this.contextMenuIndex = -1;
},
handleEditMaterial(item) {
this.closeContextMenu();
// 填充编辑表单
this.editMaterialForm = {
id: item.id,
title: item.title || '',
category: item.category || '',
industry: item.industry || '',
content: item.content || '',
remark: item.remark || ''
};
this.showEditMaterialPopup = true;
},
closeEditMaterialPopup() {
this.showEditMaterialPopup = false;
this.editMaterialForm = {
id: '',
title: '',
category: '',
industry: '',
content: '',
remark: ''
};
},
async submitEditMaterial() {
// 表单验证
if (!this.editMaterialForm.title || !this.editMaterialForm.title.trim()) {
uni.showToast({
title: "请输入知识标题",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: "保存中..."
});
// 获取租户ID
const tenantId = getTenantId();
const headers = {
"Content-Type": "application/json"
};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
// 同时在数据中也添加租户ID如果后端需要
if (!this.editMaterialForm.tenantId) {
this.editMaterialForm.tenantId = tenantId;
}
}
const res = await uni.request({
url: getApiUrl('/api/knowledgeBase/update'),
method: 'PUT',
data: this.editMaterialForm,
header: headers,
timeout: 15000
});
uni.hideLoading();
if (res.statusCode === 200 && res.data && (res.data.success || res.data.code === 200)) {
uni.showToast({
title: res.data?.message || "保存成功",
icon: "success"
});
// 关闭弹出框并刷新列表
this.closeEditMaterialPopup();
this.materialPage.current = 1;
this.fetchMaterialList({ force: true });
} else {
uni.showToast({
title: res.data?.message || "保存失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("保存素材失败:", error);
uni.showToast({
title: error?.message || "保存失败,请重试",
icon: "none"
});
}
},
handleDeleteMaterial(item) {
this.closeContextMenu();
uni.showModal({
title: '提示',
content: '确认删除该素材吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: "删除中..."
});
// 获取租户ID
const tenantId = getTenantId();
const headers = {};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const deleteRes = await uni.request({
url: getApiUrl(`/api/knowledgeBase/delete/${item.id}`),
method: 'DELETE',
header: headers,
timeout: 15000
});
uni.hideLoading();
if (deleteRes.statusCode === 200 && deleteRes.data && (deleteRes.data.success || deleteRes.data.code === 200)) {
uni.showToast({
title: deleteRes.data?.message || "删除成功",
icon: "success"
});
// 刷新列表
this.materialPage.current = 1;
this.fetchMaterialList({ force: true });
} else {
uni.showToast({
title: deleteRes.data?.message || "删除失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("删除素材失败:", error);
uni.showToast({
title: error?.message || "删除失败,请重试",
icon: "none"
});
}
}
}
});
},
// 添加素材相关方法
showAddMaterial() {
this.showMaterialPopup = true;
this.materialForm = {
title: '',
category: '基础知识库', // 固定值,不可修改
industry: '汽车销售', // 默认值
file: null
};
},
closeMaterialPopup() {
this.showMaterialPopup = false;
},
chooseFile() {
// 限制只能上传一个文件
// #ifdef H5
// H5平台使用chooseFile
uni.chooseFile({
count: 1,
success: (res) => {
const tempFiles = res.tempFiles || [];
if (tempFiles.length > 0) {
const file = tempFiles[0];
// 替换文件,而不是添加
this.materialForm.file = {
name: file.name || '文件',
path: file.path || file.url,
size: file.size
};
}
},
fail: (err) => {
console.error('选择文件失败:', err);
uni.showToast({
title: '选择文件失败',
icon: 'none'
});
}
});
// #endif
// #ifndef H5
// 非H5平台使用chooseImage可以选择图片文件
uni.chooseImage({
count: 1,
success: (res) => {
const tempFiles = res.tempFiles || [];
if (tempFiles.length > 0) {
const file = tempFiles[0];
// 替换文件,而不是添加
this.materialForm.file = {
name: file.name || '图片',
path: file.path,
size: file.size
};
}
},
fail: (err) => {
console.error('选择文件失败:', err);
uni.showToast({
title: '选择文件失败',
icon: 'none'
});
}
});
// #endif
},
removeFile() {
// 清空文件
this.materialForm.file = null;
},
async submitMaterial() {
// 表单验证
if (!this.materialForm.title || !this.materialForm.title.trim()) {
uni.showToast({
title: "请输入知识标题",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: "提交中..."
});
// 获取租户ID
const tenantId = getTenantId();
let res;
// 如果有文件,使用 uni.uploadFile 同时上传文件和其他表单数据
if (this.materialForm.file) {
// 使用 uni.uploadFile 将文件和其他字段一起提交
const formData = {
// 其他表单字段
'title': this.materialForm.title.trim(),
'category': this.materialForm.category?.trim() || '',
'industry': this.materialForm.industry?.trim() || '',
'content': '-',
'fileName': this.materialForm.file.name || 'file' // 文件名
};
// 添加租户ID到formData
if (tenantId) {
formData['tenantId'] = tenantId;
}
res = await uni.uploadFile({
url: getApiUrl('/api/knowledgeBase/add'),
filePath: this.materialForm.file.path,
name: 'file', // 文件字段名
formData: formData,
header: tenantId ? {
'X-Tenant-Id': tenantId
} : {},
timeout: 15000
});
} else {
// 如果没有文件,使用 uni.request 提交表单数据
const payload = {
title: this.materialForm.title.trim(),
category: this.materialForm.category?.trim() || '',
industry: this.materialForm.industry?.trim() || '',
content: "-"
};
// 添加租户ID到payload
if (tenantId) {
payload.tenantId = tenantId;
}
const headers = {
"Content-Type": "application/json"
};
// 添加租户ID到header
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
res = await uni.request({
url: getApiUrl('/api/knowledgeBase/add'),
method: 'POST',
data: payload,
header: headers,
timeout: 15000
});
}
uni.hideLoading();
// 处理响应uni.uploadFile 返回的 data 可能是字符串,需要解析)
let result;
if (this.materialForm.file) {
// uni.uploadFile 返回的数据需要解析
try {
result = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
} catch (e) {
result = res.data;
}
} else {
result = res.data;
}
if (res.statusCode === 200 && result && (result.success || result.code === 200)) {
uni.showToast({
title: result?.message || "提交成功",
icon: "success"
});
// 关闭弹出框并刷新列表
this.closeMaterialPopup();
this.materialPage.current = 1;
this.fetchMaterialList({ force: true });
} else {
uni.showToast({
title: result?.message || "提交失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("提交素材失败:", error);
uni.showToast({
title: error?.message || "提交失败,请重试",
icon: "none"
});
}
}
}
}
</script>
<style scoped>
/* 内容区域 - 使用绝对定位从tab页底部开始到底部导航栏结束 */
.tab-content {
position: absolute;
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距底部0 */
box-sizing: border-box;
overflow-y: auto;
}
/* 素材内容区域使用相同定位 */
.material-content {
position: absolute;
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距与列表项间距一致底部0 */
box-sizing: border-box;
overflow-y: auto;
}
.service-status-toolbar {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 8rpx; /* 增加间距,避免元素被挤压 */
padding: 12rpx 8rpx; /* 增加内边距,给搜索框更多空间 */
margin-bottom: 24rpx; /* 与列表项间距保持一致24rpx */
background-color: #F8F8FA;
border-radius: 8rpx;
border: 1px solid #EFEFF2;
overflow-x: auto;
min-height: 64rpx; /* 设置最小高度,确保搜索区域不被挤压 */
box-sizing: border-box;
}
.toolbar-total {
font-size: 24rpx;
color: #666;
white-space: nowrap;
flex-shrink: 0;
margin-right: 8rpx; /* 增加右边距,与搜索框保持距离 */
line-height: 1.2;
padding: 0 4rpx; /* 增加左右内边距 */
}
.toolbar-input {
flex: 1;
min-width: 120rpx; /* 增加最小宽度,避免被挤压变形 */
max-width: 200rpx; /* 设置最大宽度,保持合理比例 */
height: 56rpx; /* 稍微增加高度,提升可用性 */
line-height: 56rpx;
background-color: #F5F6FA;
border-radius: 8rpx;
padding: 0 12rpx; /* 增加左右内边距 */
font-size: 24rpx;
border: 1px solid transparent;
box-sizing: border-box;
flex-shrink: 1; /* 允许适当收缩,但保持最小宽度 */
}
.toolbar-actions {
display: flex;
align-items: center;
margin-left: auto;
flex-shrink: 0;
gap: 8rpx; /* 增加按钮之间的间距 */
}
.toolbar-btn {
padding: 0 12rpx; /* 增加左右内边距 */
height: 56rpx; /* 与输入框高度保持一致 */
min-width: 56rpx;
color: #2A68FF;
display: flex;
align-items: center;
justify-content: center;
gap: 4rpx; /* 增加图标和文字之间的间距 */
font-size: 24rpx;
font-weight: 400;
line-height: 1;
box-sizing: border-box;
}
.toolbar-btn text {
color: #2A68FF;
}
.service-card {
width: 100%;
box-sizing: border-box;
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
overflow: visible;
}
.material-card-item {
padding-left: 32rpx;
padding-right: 32rpx;
padding-top: 32rpx;
padding-bottom: 32rpx;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
position: relative;
}
.card-menu-trigger {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
margin-left: auto;
margin-right: 0;
margin-top: 0;
margin-bottom: 0;
flex-grow: 0;
position: relative;
padding: 0;
}
.card-menu-trigger:active {
opacity: 0.7;
}
.context-menu {
position: fixed;
background-color: #FFFFFF;
border-radius: 8rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
z-index: 1001;
min-width: 160rpx;
padding: 8rpx 0;
max-width: 200rpx;
}
.context-menu-item {
padding: 24rpx 32rpx;
font-size: 28rpx;
color: #333;
text-align: left;
}
.context-menu-item:active {
background-color: #f5f5f5;
}
.context-menu-item--danger {
color: #FF3B30;
}
.context-menu-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: transparent;
}
.staff-info {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
margin: 0;
padding: 0;
}
.staff-avatar {
width: 64rpx;
height: 64rpx;
background-color: #2196F3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
flex-shrink: 0;
}
.avatar-text {
font-size: 32rpx;
color: #FFFFFF;
font-weight: 500;
}
.staff-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.customer-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
gap: 12rpx;
box-sizing: border-box;
margin-left: 0;
margin-right: 0;
padding: 0;
width: 100%;
max-width: 100%;
}
.customer-info {
margin-bottom: 16rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
box-sizing: border-box;
margin-left: 0;
margin-right: 0;
padding: 0;
width: 100%;
max-width: 100%;
}
.customer-name {
font-size: 28rpx;
color: #555;
}
.tag-item {
padding: 8rpx 16rpx;
border-radius: 8rpx;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
display: inline-block;
max-width: 100%;
}
.tag-blue {
background-color: #E3F2FD;
}
.tag-blue text {
font-size: 24rpx;
color: #1976D2;
}
.tag-orange {
background-color: #FFF3E0;
}
.tag-orange text {
font-size: 24rpx;
color: #F57C00;
}
.service-status-empty {
padding: 48rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
.service-status-loading-more {
padding: 24rpx 0;
text-align: center;
color: #999;
font-size: 26rpx;
}
/* 添加素材弹出框样式 */
.material-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.material-popup {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - 80rpx);
max-width: 90%;
box-sizing: border-box;
height: 80vh;
max-height: 80vh;
background-color: #FFFFFF;
border-radius: 16rpx;
z-index: 1000;
display: flex;
flex-direction: column;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #E0E0E0;
flex-shrink: 0;
}
.popup-title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.popup-content {
flex: 1;
min-height: 0;
padding: 20rpx 20rpx 28rpx;
box-sizing: border-box;
overflow-y: auto;
}
.form-card {
width: 100%;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
gap: 16rpx;
}
.form-item__label {
font-size: 32rpx;
color: #333;
font-weight: 500;
width: 160rpx;
flex-shrink: 0;
text-align: left;
}
.form-item__input {
flex: 1;
min-width: 0;
height: 88rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
}
/* 只读输入框样式 */
.form-item__input[disabled],
.form-item__input[readonly] {
background-color: #f3f4f6;
color: #6b7280;
cursor: not-allowed;
}
.form-item__textarea {
flex: 1;
min-width: 0;
min-height: 200rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 24rpx;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
}
.file-upload-area {
width: 100%;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 24rpx;
background-color: #f9fafb;
border-radius: 12rpx;
margin-bottom: 12rpx;
}
.file-upload-btn {
width: 100%;
height: 88rpx;
border: 1px dashed #d1d5db;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 0;
box-sizing: border-box;
}
.file-name {
font-size: 28rpx;
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-delete {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 16rpx;
}
.file-upload-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 88rpx;
padding: 0;
background-color: #f9fafb;
border: 2rpx dashed #d1d5db;
border-radius: 12rpx;
}
.popup-actions {
display: flex;
gap: 24rpx;
padding: 24rpx;
border-top: 1px solid #E0E0E0;
flex-shrink: 0;
}
.popup-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.popup-btn--cancel {
background-color: #f3f4f6;
}
.popup-btn--submit {
/* 对齐取消按钮的风格:浅色背景与边框 */
background-color: #f3f4f6;
border: 1px solid #d1d5db;
}
.popup-btn__text {
font-size: 32rpx;
font-weight: 500;
}
.popup-btn__icon {
margin-right: 8rpx;
}
.popup-btn--cancel .popup-btn__text {
color: #2A68FF;
}
.popup-btn--submit .popup-btn__text {
color: #2A68FF;
}
/* 内容详情弹出框样式 */
.content-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.content-popup {
position: fixed;
left: 10%;
right: 10%;
top: 50%;
transform: translateY(-50%);
box-sizing: border-box;
max-height: calc(100vh - 320rpx);
height: auto;
background-color: #FFFFFF;
border-radius: 16rpx;
z-index: 1000;
display: flex;
flex-direction: column;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.content-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #E0E0E0;
flex-shrink: 0;
}
.content-popup-title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.content-popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.content-popup-body {
flex: 1;
min-height: 0;
padding: 32rpx;
box-sizing: border-box;
overflow-y: auto;
}
.content-popup-text {
font-size: 32rpx;
color: #333;
line-height: 1.8;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>