# Conflicts:
#	src/pages/view/notice/publish.vue
This commit is contained in:
ywyonui 2025-07-08 22:21:30 +08:00
commit 6a30a11617
12 changed files with 1177 additions and 304 deletions

View File

@ -83,11 +83,27 @@ export const mobilejllistApi = async (params: any) => {
return res.result;
};
export const getByJlIdApi = async (params: any) => {
const res = await get("/mobile/jl/getByJlId", params);
return res.result;
};
// 提交点名信息
export const jsdXkdmListApi = async (params: any) => {
return await post("/mobile/js/xkdm/add", params);
};
// 推送清单相关API
// 根据接龙ID获取学生信息
export const jlzxFindByJlParamsApi = async (params: { jlId: string }) => {
return await get("/api/jlzx/findByJlParams", params);
};
// 保存推送信息
export const xxtsSaveByJlzxParamsApi = async (params: { jlId: string }) => {
return await post("/api/xxts/saveByJlzxParams", params);
};
// 获取待办列表
export const dbListApi = async (params: any) => {
return await get("/api/db/findPage", params);

View File

@ -5,7 +5,7 @@
</template>
<template #empty>
<view class="flex-col-center">
<view style="width: 300rpx;height: 300rpx">
<view v-if="showNoDataImage" style="width: 300rpx;height: 300rpx">
<image src="@/static/system/currency/zwjl.png" class="wh-full"/>
</view>
<view class="color-9">暂无数据...</view>
@ -29,7 +29,7 @@
</z-paging>
</template>
<script setup lang="ts">
import {reactive} from "vue";
import {reactive, watch, computed} from "vue";
import type {LayoutOptions, PagingRefInterface} from "@/components/BasicListLayout/type/useLayout";
const dataList = ref([])
@ -41,7 +41,6 @@ const config = {
'loading-full-fixed': true,
}
let componentProps = ref<any>({})
const emits = defineEmits(['register'])
let props: LayoutOptions
@ -49,7 +48,7 @@ const setProps = (propValue: LayoutOptions) => {
componentProps.value = propValue.componentProps ? propValue.componentProps : {}
props = reactive(propValue)
}
emits('register', setProps, pagingRef)
emits('register', setProps, pagingRef, dataList)
async function query(pageNo: number, pageSize: number) {
if (props.query) {
@ -68,4 +67,7 @@ function changeSearch(e: string) {
}
}
//
const showNoDataImage = computed(() => dataList.value.length === 0)
</script>

View File

@ -1,5 +1,5 @@
import {isFunction} from "lodash";
import {Ref} from "vue";
import {Ref, ref, nextTick} from "vue";
import {hideLoading, showLoading} from "@/utils/uniapp";
import type {
LayoutCallback,
@ -13,21 +13,46 @@ import type {
export function useLayout(options: LayoutOptions): UseLayoutInterfaceReturn {
let methods: Ref<PagingRefInterface>
let dataListRef: Ref<any[]> | undefined
// 强制设置concat为false使用完整数据模式
if (!options.componentProps) options.componentProps = {};
options.componentProps.concat = false;
// 用于维护所有已加载数据
const allData = ref<any[]>([]);
async function requestApi(pageNo: number, pageSize: number) {
if (isFunction(options.api)) {
try {
const result = await options.api(Object.assign({}, {
rows: pageSize,
page: pageNo
pageNo: pageNo,
pageSize: pageSize
}, options.param))
await nextTick()
if (methods.value) {
// @ts-ignore
await methods.value.complete(result[options.resultKey || 'rows'])
let newList = result[options.resultKey || 'rows'] || [];
if (pageNo === 1) {
allData.value = [...newList];
if (dataListRef) dataListRef.value = allData.value;
} else {
// 合并去重
const map = new Map();
[...allData.value, ...newList].forEach(item => {
map.set(item.id, item);
});
allData.value = Array.from(map.values());
if (dataListRef) dataListRef.value = allData.value;
}
const hasMoreData = newList.length > 0;
if (!hasMoreData) {
await methods.value.completeByNoMore(allData.value as any, true);
return;
} else {
await methods.value.complete(allData.value as any);
}
}
} catch (err) {
console.log('err', err)
if (methods.value) {
await methods.value.complete(false);
}
@ -39,8 +64,9 @@ export function useLayout(options: LayoutOptions): UseLayoutInterfaceReturn {
options.query = requestApi
}
const register = (callback: LayoutCallback, pagingRef: Ref<PagingRefInterface>) => {
const register = (callback: LayoutCallback, pagingRef: Ref<PagingRefInterface>, listRef?: Ref<any[]>) => {
methods = pagingRef
dataListRef = listRef
callback(options)
}
return [register,

View File

@ -16,6 +16,7 @@ interface LayoutOptions {
createdReload?: boolean //组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题)auto为true时有效。为否时将在mounted+nextTick后触发reload
autoCleanListWhenReload?: boolean //reload时立即自动清空原list若立即自动清空则在reload之后、请求回调之前页面是空白的
fixed?: boolean //z-paging是否使用fixed布局若使用fixed布局则z-paging的父view无需固定高度z-paging高度默认铺满屏幕页面中的view请放在z-paging标签内需要固定在顶部的view使用slot="top"包住需要固定在底部的view使用slot="bottom"包住。
concat?: boolean //自动拼接complete中传过来的数组
}
}

View File

@ -170,10 +170,10 @@ export default {
parentArr = [...parents]
delete parentArr.children
parentid.push(item[this.idKey]);
parentArr.push({
[this.idKey]: item[this.idKey],
[this.rangeKey]: item[this.rangeKey]
})
// key title
const parentItem = { ...item };
delete parentItem.children; // children
parentArr.push(parentItem);
this._renderTreeList(item.children, rank + 1, parentid, parentArr);
} else {
this.treeList[this.treeList.length - 1].lastRank = true;

View File

@ -1,5 +1,5 @@
// const ip: string = "192.168.239.1:8897";
const ip: string = "yufangzc.com";
const ip: string = "127.0.0.1:8897";
// const ip: string = "yufangzc.com";
// const ip: string = "yufangzc.com";
const fwqip: string = "yufangzc.com";
//打包服务器接口代理标识

View File

@ -377,6 +377,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/push-list",
"style": {
"navigationBarTitleText": "推送清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/selectStudents",
"style": {

View File

@ -166,8 +166,7 @@ function toHome(data: any) {
}
}
const {afterLoginAction} = useUserStore();
const {setFile} = useDataStore();
const {afterLoginAction, setJs} = useUserStore();
const handleVerify = async () => {
if (
@ -191,6 +190,21 @@ const handleVerify = async () => {
if (result.result) {
afterLoginAction(result.result);
uni.showToast({title: "验证成功", icon: "success"});
// jsData
try {
const findJsByPhoneResult = await findJsByPhoneApi({
phone: formData.phone,
});
if (findJsByPhoneResult.resultCode == 1 && findJsByPhoneResult.result) {
// jsData
setJs(findJsByPhoneResult.result);
console.log("教师信息已存储到jsData:", findJsByPhoneResult.result);
}
} catch (error) {
console.error("获取教师信息失败:", error);
}
if (getGlobal.type == 1) {
toHome(getGlobal);
} else {
@ -202,7 +216,7 @@ const handleVerify = async () => {
if (findJsByPhoneResult.result["confirmStatus"] == "A") {
toHome(getGlobal);
} else {
setFile(findJsByPhoneResult.result);
setJs(findJsByPhoneResult.result);
setTimeout(() => {
uni.reLaunch({
url: "/pages/view/hr/teacherProfile/index",
@ -386,6 +400,3 @@ input::placeholder {
font-size: 14px;
}
</style>
function updateUserApi(arg0: { loginName: string; phone: string; code: string;
avatarUrl: string; }) { throw new Error("Function not implemented."); }

View File

@ -6,79 +6,60 @@
<view v-else-if="noticeDetail" class="detail-container">
<!-- 1. 主要内容卡片 -->
<view class="info-card main-content-card">
<!-- 封面 -->
<view v-if="noticeDetail.coverImage" class="cover-image-container">
<view v-if="noticeDetail.jlfm" class="cover-image-container">
<image
:src="noticeDetail.coverImage"
:src="imagUrl(noticeDetail.jlfm)"
mode="aspectFill"
class="cover-image"
></image>
</view>
<view v-else class="cover-placeholder">
<uni-icons type="image" size="30" color="#ccc"></uni-icons>
<text>暂无封面</text>
<text class="notice-title">{{ noticeDetail.jlmc }}</text>
<!-- 发布人和结束时间 -->
<view class="notice-meta">
<text class="meta-item">发布人: {{ noticeDetail.jsxm }}</text>
<text class="meta-item">结束时间: {{ noticeDetail.jljstime || noticeDetail.endTime }}</text>
</view>
<!-- 标题 -->
<text class="notice-title">{{ noticeDetail.title }}</text>
<!-- 内容 -->
<view class="notice-content">
<text>{{ noticeDetail.content }}</text>
<view v-if="!descExpanded">
<text class="desc-preview">{{ descPreview }}</text>
<u-button type="text" size="mini" class="more-btn" @click="descExpanded = true">更多</u-button>
</view>
<!-- 附件 (简单展示) -->
<view v-else>
<rich-text :nodes="noticeDetail.jlms" class="desc-rich-text"></rich-text>
<u-button type="text" size="mini" class="more-btn" @click="descExpanded = false">收起</u-button>
</view>
</view>
</view>
<!-- 2. 附件卡片 -->
<view v-if="noticeDetail.jlfj" class="info-card attachment-card">
<text class="attachment-title">附件</text>
<view class="attachment-list">
<view
v-if="
noticeDetail.attachments && noticeDetail.attachments.length > 0
"
class="attachments-section"
>
<text class="sub-title">附件:</text>
<view
v-for="(att, index) in noticeDetail.attachments"
:key="index"
class="attachment-item"
@click="previewAttachment(noticeDetail.jlfj)"
>
<uni-icons type="link" size="16" color="#007aff"></uni-icons>
<text class="attachment-name">{{ att.name }}</text>
<uni-icons type="paperplane" size="16" color="#409eff"></uni-icons>
<text class="attachment-name">{{ getFileName(noticeDetail.jlfj) }}</text>
</view>
</view>
</view>
<!-- 2. 按名单填写卡片 (反馈情况) -->
<!-- 3. 学生完成状态卡片 -->
<view class="info-card feedback-card">
<text class="feedback-title"
>反馈完成情况 ({{ receivedCount }}/{{ totalStudents }})</text
>
<text class="feedback-title">接龙完成情况 ({{ receivedCount }}/{{ totalStudents }})</text>
<view class="name-tags">
<view
v-for="student in noticeDetail.targetStudents"
:key="student.id"
v-for="stu in studentList"
:key="stu.id || stu.xsId"
class="name-tag"
:class="{ received: student.received }"
:class="{ received: stu.jlwc_status === 'A' }"
>
<text>{{ student.name }}</text>
<view v-if="student.received" class="checkmark-icon">
<uni-icons
type="checkmarkempty"
size="12"
color="#ffffff"
></uni-icons>
<text>{{ stu.xsxm || stu.name }}</text>
<view v-if="stu.jlwc_status === 'A'" class="checkmark-icon">
<uni-icons type="checkmarkempty" size="12" color="#ffffff"></uni-icons>
</view>
</view>
<!-- Modify button is usually not shown in detail view -->
<!-- <button class="modify-btn">修改</button> -->
</view>
</view>
<!-- 4. 时间卡片 -->
<view class="info-card list-item-card">
<view class="list-item-row no-border">
<text class="list-label">结束时间</text>
<view class="list-value">
<text>{{ noticeDetail.endTime }}</text>
</view>
</view>
</view>
</view>
@ -93,167 +74,135 @@
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { ref, computed, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app";
interface Attachment {
name: string;
type: string; // e.g., 'image', 'video', 'file'
url: string;
}
interface StudentFeedback {
id: string;
name: string;
received: boolean;
avatar?: string; // Optional avatar
}
interface NoticeDetail {
id: string;
title: string;
content: string;
coverImage?: string;
attachments?: Attachment[];
targetClass: string;
targetStudents: StudentFeedback[]; // Array of student feedback objects
signatureStatus: string; // e.g., '', ''
startTime: string;
endTime: string;
// Add other fields if needed (e.g., publisher, publishTime)
}
import { getByJlIdApi, jlzxFindByJlParamsApi } from "@/api/base/server";
import { imagUrl } from "@/utils";
import { BASE_IMAGE_URL } from "@/config";
const noticeId = ref<string>("");
const noticeDetail = ref<NoticeDetail | null>(null);
const noticeDetail = ref<any>(null);
const isLoading = ref(false);
const studentList = ref<any[]>([]);
const descExpanded = ref(false);
const descPreview = computed(() => {
if (!noticeDetail.value?.jlms) return '';
const div = document.createElement('div');
div.innerHTML = noticeDetail.value.jlms;
return div.innerText.replace(/\n/g, '').slice(0, 100) + (div.innerText.length > 100 ? '...' : '');
});
//
const getFileName = (filePath: string) => {
if (!filePath) return '';
const parts = filePath.split('/');
return parts[parts.length - 1] || filePath;
};
// Computed properties for feedback status
const receivedCount = computed(() => {
return (
noticeDetail.value?.targetStudents.filter((s) => s.received).length ?? 0
);
});
const totalStudents = computed(() => {
return noticeDetail.value?.targetStudents.length ?? 0;
return studentList.value.filter((s) => s.jlwc_status === 'A').length;
});
const totalStudents = computed(() => studentList.value.length);
onLoad((options) => {
onLoad(async (options) => {
if (options && options.id) {
noticeId.value = options.id;
fetchNoticeDetail();
isLoading.value = true;
// 1.
try {
const detailRes = await getByJlIdApi({ jlId: noticeId.value });
noticeDetail.value = Array.isArray(detailRes) ? detailRes[0] : {};
} catch (e) {
uni.showToast({ title: "加载接龙详情失败", icon: "none" });
}
// 2.
try {
const stuRes = await jlzxFindByJlParamsApi({ jlId: noticeId.value });
studentList.value = stuRes?.rows || stuRes?.result || [];
console.log("学生列表数据:", studentList.value);
} catch (e) {
uni.showToast({ title: "加载学生状态失败", icon: "none" });
}
isLoading.value = false;
uni.setNavigationBarTitle({ title: "接龙情况" });
} else {
console.error("Notice ID is missing!");
uni.showToast({ title: "加载失败缺少通知ID", icon: "none" });
}
});
//
const fetchNoticeDetail = async () => {
console.log(`Fetching details for notice ID: ${noticeId.value}`);
isLoading.value = true;
await new Promise((resolve) => setTimeout(resolve, 500));
// --- Replace with actual API call using noticeId.value ---
// Example Mock Data with targetStudents
const mockDetail: NoticeDetail = {
id: noticeId.value,
title: "重要通知接龙",
content:
"由于xxx原因现下发《关于加强建设xxxx工作的通知》\n希望大家自习阅读认真执行。",
// coverImage: '/static/mock/notice-cover.png', //
attachments: [
{ name: "附件1.docx", type: "file", url: "" },
{ name: "相关图片.png", type: "image", url: "" },
],
targetClass: "一年级3班",
targetStudents: [
{ id: "s111", name: "施延兴", received: true },
{ id: "s112", name: "安苒溪", received: false },
{ id: "s113", name: "罗浩晨", received: false },
{ id: "s114", name: "康萌", received: true },
{ id: "s115", name: "范文昊", received: false },
{ id: "s116", name: "丁贺祥", received: false },
{ id: "s117", name: "韦运昊", received: false },
{ id: "s118", name: "萧润丽", received: true },
{ id: "s119", name: "谢林", received: false },
{ id: "s120", name: "鲍泽远", received: true },
{ id: "s121", name: "杨俊", received: false },
{ id: "s122", name: "秦禹辰", received: false },
{ id: "s123", name: "姜杨", received: false },
{ id: "s124", name: "窦晶滢", received: true },
{ id: "s125", name: "廉佳毅", received: false },
{ id: "s126", name: "许冰枫", received: false },
{ id: "s127", name: "曹佳惠", received: true },
{ id: "s128", name: "元运昊", received: false },
{ id: "s129", name: "孔欣怡", received: false },
{ id: "s130", name: "许润平", received: true },
{ id: "s131", name: "谢汝鑫", received: true },
{ id: "s132", name: "康益帆", received: true },
{ id: "s133", name: "凤帆", received: false },
{ id: "s134", name: "吴雅晗", received: false },
{ id: "s135", name: "曹阳", received: false },
{ id: "s136", name: "萧润丽", received: true }, // Duplicate name example
{ id: "s137", name: "褚慧嘉", received: false },
{ id: "s138", name: "薛欣怡", received: true },
{ id: "s139", name: "倪雄霖", received: false },
{ id: "s140", name: "顾雪", received: true },
], // Mock received status
signatureStatus: "不启用",
startTime: "2025-03-10 12:00:00",
endTime: "2025-03-10 12:00:00",
};
noticeDetail.value = mockDetail; // Assign the mock data
isLoading.value = false;
//
uni.setNavigationBarTitle({ title: "通知详情" });
};
// Placeholder for attachment preview function
const previewAttachment = (attachment: Attachment) => {
console.log("尝试下载/预览附件:", attachment);
if (!attachment.url) {
//
const previewAttachment = (filePath: string) => {
if (!filePath) {
uni.showToast({ title: "附件链接无效", icon: "none" });
return;
}
// URL
const baseUrl = BASE_IMAGE_URL; // 使URL
const fullUrl = filePath.startsWith('http') ? filePath : baseUrl + filePath;
//
const fileExtension = filePath.split('.').pop()?.toLowerCase();
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension || '');
//
if (isImage) {
uni.previewImage({
urls: [fullUrl],
current: fullUrl
});
return;
}
uni.showLoading({ title: "正在准备附件..." });
uni.downloadFile({
url: attachment.url,
url: fullUrl,
success: (res) => {
if (res.statusCode === 200) {
const filePath = res.tempFilePath;
console.log("文件下载成功:", filePath);
const tempFilePath = res.tempFilePath;
uni.hideLoading();
// Attempt to open the downloaded file
uni.openDocument({
filePath: filePath,
filePath: tempFilePath,
success: function (res) {
console.log("打开文档成功");
//
},
fail: function (err) {
console.error("打开文档失败:", err);
uni.showToast({ title: "无法打开该文件类型", icon: "none" });
uni.hideLoading(); // Ensure loading is hidden on failure too
},
});
} else {
console.error("下载文件失败,服务器状态码:", res.statusCode);
uni.hideLoading();
uni.showToast({ title: "下载附件失败", icon: "none" });
}
},
fail: (err) => {
console.error("下载文件请求失败:", err);
uni.hideLoading();
uni.showToast({ title: "下载附件失败", icon: "none" });
},
});
};
watch(noticeDetail, (val) => {
//
}, { immediate: true, deep: true });
//
watch(studentList, (list) => {
list.forEach(stu => {
//
if (stu.jlwcStatus && !stu.jlwc_status) {
stu.jlwc_status = stu.jlwcStatus;
}
// '1''A'
if (stu.jlwc_status === '1') {
stu.jlwc_status = 'A';
}
});
}, { immediate: true, deep: true });
</script>
<style scoped lang="scss">
@ -370,12 +319,31 @@ const previewAttachment = (attachment: Attachment) => {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.notice-meta {
margin-bottom: 15px;
.meta-item {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
}
.notice-content {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 20px;
white-space: pre-wrap; //
position: relative;
.desc-preview {
display: block;
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 4px;
}
.desc-rich-text {
font-size: 14px;
color: #666;
line-height: 1.5;
}
}
// 稿
@ -431,6 +399,37 @@ const previewAttachment = (attachment: Attachment) => {
// Optional: Add specific padding if needed
}
/* 附件卡片 */
.attachment-card {
.attachment-title {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.attachment-list {
.attachment-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.attachment-name {
font-size: 14px;
color: #409eff;
margin-left: 8px;
flex: 1;
}
}
}
}
.feedback-title {
display: block;
font-size: 16px;
@ -455,8 +454,8 @@ const previewAttachment = (attachment: Attachment) => {
flex-grow: 0;
flex-shrink: 0;
box-sizing: border-box;
// Calculate basis for 5 items per row, accounting for 10px gap
flex-basis: calc((100% - 40px) / 5);
// Calculate basis for 4 items per row, accounting for 10px gap
flex-basis: calc((100% - 30px) / 4);
height: 30px; // Slightly taller height
line-height: 20px; // Adjust line-height for vertical centering
border: 1px solid #f4f4f5;
@ -499,10 +498,17 @@ const previewAttachment = (attachment: Attachment) => {
// Modify button (usually hidden in detail)
.modify-btn {
// ... styles ...
flex-basis: calc((100% - 40px) / 5); // Keep consistent basis
flex-basis: calc((100% - 30px) / 4); // Keep consistent basis
}
}
/* 更多按钮样式 */
.more-btn {
font-size: 16px !important;
color: #409eff !important;
font-weight: 500;
}
/* 列表项样式的卡片 */
.list-item-card {
padding: 5px 15px; // padding

View File

@ -2,9 +2,9 @@
<template>
<view class="jl-list-page">
<!-- 列表内容 -->
<BasicListLayout @register="register">
<BasicListLayout @register="register" v-model="dataList">
<template v-slot="{ data }">
<view class="jl-card" @click="goToDetail(data.id)">
<view class="jl-card">
<view class="card-header">
<text class="jl-title">{{ data.jlmc }}</text>
<text class="jl-status" :class="getStatusClass(data.jlStatus)">
@ -21,11 +21,26 @@
<rich-text class="jl-excerpt" :nodes="data.jlms"></rich-text>
</view>
<view class="card-footer">
<text class="footer-item">发布者: {{ data.jsxm }}</text>
<text class="footer-item">发布者: {{ data.jsxm || '未知' }}</text>
<text class="footer-item">{{ formatTime(data.jlFbtime) }}</text>
<text class="footer-item" v-if="data.bjmc"
>范围: {{ data.njmc + data.bjmc }}</text
>
<view class="footer-actions">
<u-button
class="footer-btn"
type="primary"
size="mini"
@click="goToFeedback(data.id)"
>接龙情况</u-button>
<u-button
v-if="data.jlStatus === 'A'"
class="footer-btn"
type="warning"
size="mini"
@click="goToPush(data.id)"
>消息推送</u-button>
</view>
</view>
</view>
</template>
@ -42,6 +57,10 @@
</view>
</template>
</BasicListLayout>
<!-- 用uniqueList渲染 -->
<template v-for="item in uniqueList" :key="item.id">
<!-- 这里可以加自定义渲染内容做二次验证 -->
</template>
</view>
</template>
@ -49,13 +68,14 @@
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { mobilejllistApi } from "@/api/base/server";
import { imagUrl } from "@/utils";
import BasicDragButton from "@/components/BasicDragButton/DragButton.vue";
import { computed, watch } from "vue";
import { onShow } from "@dcloudio/uni-app";
interface JlItem {
id: string;
jlmc: string; //
jlms: string; //
jlStatus: string; // AB
jlStatus: string; // ABC
jlFbr: string; //
jlFbtime: string; //
jlfm: string; //
@ -72,6 +92,21 @@ const [register, { reload }] = useLayout({
componentProps: {},
});
//
const dataList = ref<JlItem[]>([]);
const uniqueList = computed(() => {
const map = new Map();
(dataList.value || []).forEach(item => {
if (item && item.id != null) map.set(String(item.id), item);
});
return Array.from(map.values());
});
// dataList
watch(dataList, (val) => {
//
});
//
const goToDetail = (jlId: string) => {
uni.navigateTo({
@ -86,6 +121,20 @@ const goToPublish = () => {
});
};
// detail
const goToFeedback = (jlId: string) => {
uni.navigateTo({
url: `/pages/view/notice/detail?id=${jlId}`,
});
};
//
const goToPush = (jlId: string) => {
uni.navigateTo({
url: `/pages/view/notice/push-list?jlId=${jlId}`,
});
};
// CSS
const getStatusClass = (status: string) => {
if (status === "A") return "status-published";
@ -95,9 +144,9 @@ const getStatusClass = (status: string) => {
//
const getStatusText = (status: string) => {
if (status === "A") return "已发布";
if (status === "A") return "待推送";
if (status === "B") return "暂存";
return "已结束";
return "已推送";
};
//
@ -109,6 +158,10 @@ const formatTime = (timeStr: string) => {
"0"
)}-${String(date.getDate()).padStart(2, "0")}`;
};
onShow(() => {
reload();
});
</script>
<style scoped lang="scss">
@ -155,7 +208,7 @@ const formatTime = (timeStr: string) => {
flex-shrink: 0;
&.status-published {
background-color: #19be6b; // 绿-
background-color: #BE193FFF; // -
}
&.status-draft {
@ -163,7 +216,7 @@ const formatTime = (timeStr: string) => {
}
&.status-ended {
background-color: #999999; // -
background-color: #19be4d; // 绿-
}
}
}
@ -192,17 +245,31 @@ const formatTime = (timeStr: string) => {
}
.card-footer {
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
font-size: 12px;
color: #999;
display: flex;
flex-wrap: wrap; //
gap: 5px 15px; //
flex-wrap: wrap;
gap: 5px 15px;
.footer-item {
white-space: nowrap;
font-size: 14px;
color: #666;
}
.footer-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 4px;
width: 100%;
flex-basis: 100%; /* 独占一行 */
margin-top: 6px;
padding: 0;
}
.footer-btn {
min-width: 60px;
width: auto;
font-size: 12px;
padding: 0 12px;
flex: none;
margin: 0;
}
}

View File

@ -18,6 +18,7 @@
:value="formData.coverImage ? imagUrl(formData.coverImage) : ''"
@select="handleCoverSelected"
@close="handleCoverClosed"
ref="coverUploadRef"
>
<view class="cover-placeholder">
<uni-icons type="image" size="40" color="#b0b0b0"></uni-icons>
@ -162,8 +163,8 @@
<button class="action-btn preview-btn" @click="previewNotice">
预览
</button>
<button class="action-btn publish-btn" @click="publishNotice">
立即发布
<button class="action-btn publish-btn" @click="publishNotice" :disabled="isPublishing">
{{ isPublishing ? '发布中...' : '立即发布' }}
</button>
</view>
</template>
@ -217,13 +218,15 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { ref, reactive, computed, nextTick } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import BasicTree from "@/components/BasicTree/Tree.vue";
import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils";
import { findAllNjBjTreeApi, mobilejlstudentListApi } from "@/api/base/server";
import { findAllNjBjTree, mobilejlstudentListApi } from "@/api/base/server";
import { post, get } from "@/utils/request";
import { useUserStore } from "@/store/modules/user";
interface Attachment {
name: string;
@ -242,6 +245,7 @@ interface FormData {
targetNames: string[];
targetStudentIds: string[];
targetNjIds: string[]; // ID
targetNjmcIds: string[]; // ID
targetBjIds: string[]; // ID
signatureRequired: boolean;
startTime: string;
@ -259,13 +263,14 @@ const formData = reactive<FormData>({
targetNames: [],
targetStudentIds: [],
targetNjIds: [],
targetNjmcIds: [],
targetBjIds: [],
signatureRequired: false,
signatureRequired: true,
startTime: "",
endTime: "",
});
const signatureOptions = ["启用", "启用"];
const signatureOptions = ["启用" , "启用"];
const signatureStatusText = computed(() => {
return formData.signatureRequired ? "启用" : "不启用";
});
@ -283,18 +288,27 @@ const displayNames = computed(() => {
return formData.targetNames.slice(0, maxDisplayCount);
});
// ref
const coverUploadRef = ref();
//
const loadTreeData = async () => {
try {
const res = await findAllNjBjTreeApi();
const res = await findAllNjBjTree();
if (res.resultCode === 1 && res.result) {
// BasicTree
treeData.value = res.result.map((item: any) => ({
// BasicTree njmcId
const convertTreeData = (items: any[]): any[] => {
return items.map((item: any) => ({
key: item.key,
title: item.title,
children: item.children || [],
njmcId: item.njmcId, // njmcId
children: item.children ? convertTreeData(item.children) : [],
}));
};
treeData.value = convertTreeData(res.result);
console.log("树形数据加载成功:", treeData.value);
console.log("原始树形数据结构:", JSON.stringify(res.result, null, 2));
}
} catch (error) {
console.error("加载树形数据失败:", error);
@ -328,7 +342,129 @@ const fetchStudentsByClass = async (className: string): Promise<string[]> => {
return mockStudents;
};
onLoad((options) => {});
onLoad((options) => {
console.log("页面加载参数:", options);
// ID
if (options.id) {
noticeId.value = options.id;
loadJlData(options.id);
}
});
//
onShow(() => {
console.log("页面显示,当前状态:", {
publishedJlId: publishedJlId.value,
formDataId: formData.id,
isPublishing: isPublishing.value
});
// ID
//
if (publishedJlId.value && !noticeId.value) {
console.log("检测到从推送清单页面返回,重置表单状态");
resetForm();
}
});
//
const resetForm = () => {
//
Object.assign(formData, {
id: "",
title: "",
content: "",
coverImage: null,
attachments: [],
targetClass: "",
targetNames: [],
targetStudentIds: [],
targetNjIds: [],
targetNjmcIds: [],
targetBjIds: [],
signatureRequired: false,
startTime: "",
endTime: "",
});
//
publishedJlId.value = null;
isPublishing.value = false;
noticeId.value = null;
// 使 nextTick DOM
nextTick(() => {
//
if (coverUploadRef.value) {
// CustomUpload
if (typeof coverUploadRef.value.reset === 'function') {
coverUploadRef.value.reset();
} else {
//
handleCoverClosed("coverImage");
}
}
});
console.log("表单已重置,包括封面:", formData.coverImage);
};
// 使
const loadJlData = async (jlId: string) => {
try {
uni.showLoading({ title: "加载数据中..." });
//
const response = await get(`/api/jl/findById?id=${jlId}`);
if (response && response.resultCode === 1 && response.result) {
const jlData = response.result;
//
formData.id = jlData.id;
formData.title = jlData.jlmc || "";
formData.content = jlData.jlms || "";
formData.coverImage = jlData.jlfm || null;
formData.startTime = jlData.jlkstime || "";
formData.endTime = jlData.jljstime || "";
formData.signatureRequired = jlData.mdqz === "1";
//
if (jlData.jlfj) {
const attachmentUrls = jlData.jlfj.split(",");
formData.attachments = attachmentUrls.map(url => ({
name: url.split("/").pop() || "附件",
type: "file",
url: url
}));
}
//
if (jlData.njId) {
formData.targetNjIds = [jlData.njId];
}
if (jlData.njmcId) {
formData.targetNjmcIds = [jlData.njmcId];
}
if (jlData.bjId) {
const bjIds = jlData.bjId.split(",");
formData.targetBjIds = bjIds;
// ID使ID
formData.targetClass = bjIds.join(", ");
}
uni.hideLoading();
uni.showToast({ title: "数据加载成功", icon: "success" });
} else {
throw new Error("获取接龙数据失败");
}
} catch (error) {
uni.hideLoading();
console.error("加载接龙数据失败:", error);
uni.showToast({ title: "加载数据失败", icon: "error" });
}
};
const handleCoverSelected = async (e: any) => {
if (e.tempFilePaths && e.tempFilePaths.length > 0) {
@ -492,6 +628,8 @@ const showClassTree = () => {
//
const onTreeConfirm = async (selectedItems: any[]) => {
console.log("选择的班级:", selectedItems);
console.log("完整的 selectedItems 数据:", JSON.stringify(selectedItems, null, 2));
if (selectedItems.length > 0) {
//
const classNames = selectedItems.map((item) => item.title);
@ -499,34 +637,81 @@ const onTreeConfirm = async (selectedItems: any[]) => {
formData.targetNames = [];
formData.targetStudentIds = [];
formData.targetNjIds = [];
formData.targetNjmcIds = [];
formData.targetBjIds = [];
try {
uni.showLoading({ title: "获取学生列表中..." });
// IDID
// IDIDID
const njIds: string[] = [];
const njmcIds: string[] = [];
const bjIds: string[] = [];
const gradeNames: string[] = []; //
selectedItems.forEach((item) => {
// parents
if (item.parents && item.parents.length > 0) {
const njId = item.parents[0].key; // IDparents[0].key
const parent = item.parents[0]; //
console.log("年级信息:", parent);
const njId = parent.key; // IDparents[0].key
const njmcId = parent.njmcId; // ID
const bjId = item.key; // IDitem.key
const gradeName = parent.title; //
//
if (njId && bjId && gradeName) {
njIds.push(njId);
if (njmcId) {
njmcIds.push(njmcId);
}
bjIds.push(bjId);
gradeNames.push(gradeName);
console.log(
`选择班级: ${item.title}, 年级ID: ${njId}, 班级ID: ${bjId}`
`选择班级: ${item.title}, 年级ID: ${njId}, 年级名称ID: ${njmcId}, 班级ID: ${bjId}, 年级名称: ${gradeName}`
);
} else {
console.warn("年级信息不完整,跳过该班级:", item);
}
} else {
console.warn("选择的项目不是班级或缺少年级信息:", item);
}
});
//
if (gradeNames.length === 0) {
uni.hideLoading();
uni.showToast({
title: "请选择有效的班级",
icon: "none",
duration: 3000
});
return;
}
//
const uniqueGradeNames = [...new Set(gradeNames)];
if (uniqueGradeNames.length > 1) {
uni.hideLoading();
const gradeList = uniqueGradeNames.join("、");
uni.showToast({
title: `不能跨年级选择发布班级,已选择:${gradeList}`,
icon: "none",
duration: 4000
});
return; //
}
if (njIds.length > 0 && bjIds.length > 0) {
// ID
const uniqueNjIds = [...new Set(njIds)];
const uniqueNjmcIds = [...new Set(njmcIds)];
// IDIDformData
// IDIDIDformData
formData.targetNjIds = uniqueNjIds;
formData.targetNjmcIds = uniqueNjmcIds;
formData.targetBjIds = bjIds;
//
@ -592,30 +777,165 @@ const handleSignatureChange = (e: any) => {
formData.signatureRequired = index == 1;
};
const validateForm = (): boolean => {
if (!formData.title.trim()) {
const validateForm = () => {
//
if (!formData.title || !formData.title.trim()) {
uni.showToast({ title: "请输入通知标题", icon: "none" });
return false;
}
if (!formData.content.trim()) {
//
if (!formData.content || !formData.content.trim()) {
uni.showToast({ title: "请输入通知内容", icon: "none" });
return false;
}
if (
formData.startTime &&
formData.endTime &&
formData.startTime >= formData.endTime
) {
//
if (!formData.targetNjIds.length) {
uni.showToast({ title: "请选择年级与班级", icon: "none" });
return false;
}
//
if (!formData.startTime) {
uni.showToast({ title: "请选择开始时间", icon: "none" });
return false;
}
//
if (!formData.endTime) {
uni.showToast({ title: "请选择结束时间", icon: "none" });
return false;
}
//
const start = new Date(formData.startTime).getTime();
const end = new Date(formData.endTime).getTime();
if (end <= start) {
uni.showToast({ title: "结束时间必须晚于开始时间", icon: "none" });
return false;
}
// ...
return true;
};
const saveDraft = () => {
const { getUser, getJs } = useUserStore();
// store
const userStore = useUserStore();
//
const userData = computed(() => getUser);
const jsData = computed(() => getJs);
//
const formatDate = (dateString: string | null): string | null => {
if (!dateString) return null;
const date = new Date(dateString);
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}`;
};
//
const isPublishing = ref(false);
const publishedJlId = ref<string | null>(null);
// JlDto
const buildJlDto = (status: string) => {
//
const user = userData.value;
const js = jsData.value;
console.log("=== 用户信息调试 ===");
console.log("userData.value:", user);
console.log("jsData.value:", js);
console.log("user?.id:", user?.id);
console.log("user?.loginName:", user?.loginName);
console.log("user?.name:", user?.name);
console.log("js?.id:", js?.id);
console.log("js?.jsxm:", js?.jsxm);
console.log("==================");
return {
id: formData.id || "", // ID
jlmc: formData.title.trim(), //
jlms: formData.content.trim(), //
jlfm: formData.coverImage || "", //
jlfj: formData.attachments.length > 0 ? formData.attachments.map(att => att.url).join(",") : "", //
jlkstime: formatDate(formData.startTime), // yyyy-MM-dd HH:mm:ss
jljstime: formatDate(formData.endTime), // yyyy-MM-dd HH:mm:ss
mdqz: formData.signatureRequired ? "1" : "0", // 10
njId: formData.targetNjIds.length > 0 ? formData.targetNjIds[0] : "", // ID
njmcId: formData.targetNjmcIds.length > 0 ? formData.targetNjmcIds[0] : "", // ID
bjId: formData.targetBjIds, // ID
jlFbr: js?.id || "", // IDjsData
jsxm: js?.jsxm || user?.loginName || user?.name || "", // 使jsDatajsxm使userDataloginNamename
createdUserId: user?.id ? parseInt(user.id) : null,
createdUserName: user?.loginName || user?.name || "", // userData
jlFbtime: formatDate(new Date().toISOString()), // yyyy-MM-dd HH:mm:ss
jlStatus: status, // AB
status: "A" //
};
};
const saveDraft = async () => {
if (!validateForm()) return;
console.log("保存草稿", formData);
uni.showToast({ title: "草稿保存成功 (模拟)", icon: "success" });
try {
uni.showLoading({ title: "保存草稿中..." });
// 稿 JlDto
const jlDto = buildJlDto("B"); // B
console.log("保存草稿数据:", jlDto);
//
const response = await post("/api/jl/save", jlDto);
uni.hideLoading();
if (response && response.resultCode === 1) {
// ID
const jlId = response.result || response.data || jlDto.id;
// ID
if (jlId) {
formData.id = jlId;
}
uni.showToast({
title: "草稿保存成功",
icon: "success",
duration: 2000
});
//
setTimeout(() => {
uni.navigateBack();
}, 2000);
} else {
uni.showToast({
title: response?.resultMsg || "保存草稿失败",
icon: "error"
});
}
} catch (error) {
uni.hideLoading();
console.error("保存草稿失败:", error);
//
if (error instanceof Error) {
uni.showToast({
title: error.message,
icon: "none"
});
} else {
uni.showToast({
title: "保存草稿失败,请重试",
icon: "error"
});
}
}
};
const previewNotice = () => {
@ -624,27 +944,109 @@ const previewNotice = () => {
uni.showToast({ title: "预览功能待实现", icon: "none" });
};
const publishNotice = () => {
const publishNotice = async () => {
if (!validateForm()) return;
// IDID
const publishData = {
...formData,
njIds: formData.targetNjIds.join(","), // ID
bjIds: formData.targetBjIds.join(","), // ID
};
//
if (isPublishing.value) {
uni.showToast({ title: "正在发布中,请稍候...", icon: "none" });
return;
}
console.log("发布通知数据:", publishData);
console.log("年级ID:", publishData.njIds);
console.log("班级ID:", publishData.bjIds);
//
if (publishedJlId.value && !formData.id) {
uni.showToast({ title: "该接龙已经发布,请勿重复发布", icon: "none" });
return;
}
// TODO:
// uni.showLoading({ title: "..." });
// setTimeout(() => {
// uni.hideLoading();
// uni.showToast({ title: " ()", icon: "success" });
// uni.navigateBack();
// }, 1000);
// ID
if (formData.id) {
try {
const response = await get(`/api/jl/findById?id=${formData.id}`);
if (response && response.resultCode === 1 && response.result) {
const jlData = response.result;
if (jlData.jlStatus === 'A') {
uni.showToast({ title: "该接龙已经发布,请勿重复发布", icon: "none" });
return;
}
}
} catch (error) {
console.error("检查接龙状态失败:", error);
}
}
try {
isPublishing.value = true;
uni.showLoading({ title: "发布中..." });
// JlDto
const jlDto = buildJlDto("A"); // A
console.log("发布接龙数据:", jlDto);
//
const response = await post("/api/jl/save", jlDto);
uni.hideLoading();
if (response && response.resultCode === 1) {
// ID使ID使ID
const jlId = response.result || response.data || jlDto.id;
console.log("发布成功接龙ID:", jlId, "完整响应:", response);
if (!jlId) {
console.error("发布成功但未获取到接龙ID");
uni.showToast({
title: "发布成功但获取接龙ID失败",
icon: "error"
});
return;
}
//
publishedJlId.value = jlId;
// ID
formData.id = jlId;
uni.showToast({
title: "发布成功",
icon: "success",
duration: 2000
});
//
setTimeout(() => {
uni.navigateTo({
url: `/pages/view/notice/push-list?jlId=${jlId}`
});
}, 2000);
} else {
uni.showToast({
title: response?.resultMsg || "发布失败",
icon: "error"
});
}
} catch (error) {
uni.hideLoading();
console.error("发布接龙失败:", error);
//
if (error instanceof Error) {
uni.showToast({
title: error.message,
icon: "none"
});
} else {
uni.showToast({
title: "发布失败,请重试",
icon: "error"
});
}
} finally {
isPublishing.value = false;
}
};
</script>
@ -1047,6 +1449,12 @@ const publishNotice = () => {
background-color: #409eff;
color: #ffffff;
border: none;
&:disabled {
background-color: #a0cfff;
color: #ffffff;
cursor: not-allowed;
}
}
&.draft-btn:active {
background-color: #e0e0e0;

View File

@ -0,0 +1,329 @@
<template>
<BasicLayout>
<!-- Default slot -->
<scroll-view scroll-y class="push-list-scroll-view">
<view class="push-list-container">
<!-- 页面标题 -->
<view class="page-header">
<text class="page-title">推送清单</text>
<text class="student-count"> {{ studentList.length }} 名学生</text>
</view>
<!-- 学生信息卡片列表 -->
<view class="student-cards">
<view
v-for="(student, index) in studentList"
:key="student.id || student.xsId || index"
class="student-card"
>
<view class="student-main-info">
<text class="student-name">{{ student.xsxm || student.xm || '未知姓名' }}</text>
<text class="grade-class">{{ student.njmc }} {{ student.bjmc }}</text>
</view>
<view class="parent-info">
<text class="parent-label">家长</text>
<text class="parent-name">{{ student.jzxm || '暂无家长信息' }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="studentList.length === 0" class="empty-state">
<uni-icons type="info" size="60" color="#ccc"></uni-icons>
<text class="empty-text">暂无学生信息</text>
</view>
</view>
</scroll-view>
<!-- Bottom slot -->
<template #bottom>
<view class="bottom-actions">
<button class="action-btn cancel-btn" @click="handleCancel">
取消
</button>
<button class="action-btn confirm-btn" @click="handleConfirmPush" :disabled="isPushing">
{{ isPushing ? '推送中...' : '确认推送' }}
</button>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { jlzxFindByJlParamsApi, xxtsSaveByJlzxParamsApi } from "@/api/base/server";
interface StudentInfo {
id: string;
xsId: string;
xsxm: string;
xm: string;
njId: string;
njmcId: string;
njmc: string;
bc: string;
bjId: string;
bjmc: string;
jzIds: string;
jzxm: string;
jlId: string;
jlmc: string;
jlts: string;
jlwcStatus: string;
}
const jlId = ref<string>('');
const studentList = ref<StudentInfo[]>([]);
const isPushing = ref(false);
onLoad((options) => {
console.log("推送清单页面参数:", options);
if (options.jlId && options.jlId !== 'null' && options.jlId !== 'undefined') {
jlId.value = options.jlId;
console.log("获取到接龙ID:", jlId.value);
loadStudentList();
} else {
console.error("缺少接龙ID参数或ID无效:", options.jlId);
uni.showToast({ title: "缺少接龙ID参数", icon: "error" });
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
//
const loadStudentList = async () => {
try {
uni.showLoading({ title: "加载学生信息中..." });
const response = await jlzxFindByJlParamsApi({ jlId: jlId.value });
uni.hideLoading();
console.log("接口返回数据:", response);
//
if (response && (response.rows || response.result)) {
// rows
const students = response.rows || response.result || [];
studentList.value = students;
console.log("学生列表加载成功,共", students.length, "名学生:", studentList.value);
} else {
console.warn("接口返回数据格式异常:", response);
// 使 response
if (Array.isArray(response)) {
studentList.value = response;
console.log("直接使用 response 作为学生列表,共", response.length, "名学生");
} else {
throw new Error("获取学生信息失败:数据格式异常");
}
}
} catch (error) {
uni.hideLoading();
console.error("加载学生列表失败:", error);
uni.showToast({
title: error instanceof Error ? error.message : "加载学生信息失败",
icon: "error"
});
}
};
//
const handleCancel = () => {
uni.navigateBack();
};
//
const handleConfirmPush = async () => {
if (isPushing.value) return;
try {
isPushing.value = true;
uni.showLoading({ title: "推送中..." });
const response = await xxtsSaveByJlzxParamsApi({ jlId: jlId.value });
uni.hideLoading();
isPushing.value = false;
if (response && response.resultCode === 1) {
uni.showToast({
title: "推送成功",
icon: "success",
duration: 2000
});
//
setTimeout(() => {
uni.navigateBack({
delta: 2
});
}, 2000);
} else {
throw new Error(response?.resultMsg || "推送失败");
}
} catch (error) {
uni.hideLoading();
isPushing.value = false;
console.error("推送失败:", error);
uni.showToast({
title: error instanceof Error ? error.message : "推送失败,请重试",
icon: "error"
});
}
};
</script>
<style scoped lang="scss">
.push-list-scroll-view {
height: 100%;
box-sizing: border-box;
}
.push-list-container {
padding: 12px;
box-sizing: border-box;
}
.page-header {
background-color: #ffffff;
border-radius: 8px;
padding: 15px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.student-count {
font-size: 14px;
color: #909399;
}
.student-cards {
display: flex;
flex-direction: column;
gap: 12px;
}
.student-card {
background-color: #ffffff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.student-main-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.student-name {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.grade-class {
font-size: 14px;
color: #606266;
background-color: #f4f4f5;
padding: 2px 8px;
border-radius: 4px;
}
.parent-info {
display: flex;
align-items: center;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
.parent-label {
font-size: 14px;
color: #909399;
margin-right: 4px;
}
.parent-name {
font-size: 14px;
color: #606266;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
background-color: #ffffff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.empty-text {
font-size: 16px;
color: #909399;
margin-top: 16px;
}
.bottom-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background-color: #ffffff;
border-top: 1px solid #e5e5e5;
gap: 12px;
}
.action-btn {
flex: 1;
height: 44px;
line-height: 44px;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
border: none;
outline: none;
&::after {
border: none;
}
&.cancel-btn {
background-color: #f4f4f5;
color: #909399;
border: 1px solid #e9e9eb;
&:active {
background-color: #e0e0e0;
}
}
&.confirm-btn {
background-color: #409eff;
color: #ffffff;
&:active {
background-color: #3a8ee6;
}
&:disabled {
background-color: #c0c4cc;
color: #ffffff;
}
}
}
</style>