commit
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
package com.budwk.app.client.module.activity.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.base.page.PageForm;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.client.module.activity.service.ClientDynamicService;
|
||||
import com.budwk.app.module.activity.models.ActDynamic;
|
||||
import com.budwk.app.module.activity.models.ActDynamicPraise;
|
||||
import com.budwk.app.web.commons.auth.satoken.StpClientUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.adaptor.JsonAdaptor;
|
||||
import org.nutz.mvc.annotation.AdaptBy;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
/**
|
||||
* @ClassName ClientDynamicController
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/4/30 14:16
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/client/activity/dynamic")
|
||||
public class ClientDynamicController {
|
||||
|
||||
@Inject
|
||||
private ClientDynamicService dynamicService;
|
||||
|
||||
@At("/list")
|
||||
@AdaptBy(type = JsonAdaptor.class)
|
||||
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||
public Result list(@Param("pageForm") PageForm pageForm, @Param("activityId") Long activityId) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ad.*,
|
||||
cu.userName,
|
||||
cu.sex,
|
||||
(select count(1) from act_dynamic_praise where dynamicId = ad.id) as praiseCount,
|
||||
(select count(1) from act_dynamic_praise where dynamicId = ad.id and userId = '') as selfPraiseCount
|
||||
FROM
|
||||
`act_dynamic` ad
|
||||
LEFT JOIN client_user cu ON ad.userId = cu.id
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("ad.activityId", "=", activityId);
|
||||
cnd.desc("ad.publishTime");
|
||||
sql.setCondition(cnd);
|
||||
Pagination pagination = dynamicService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At("/onHandle")
|
||||
@AdaptBy(type = JsonAdaptor.class)
|
||||
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||
public Result onHandle(ActDynamic actDynamic) {
|
||||
actDynamic.setUserId(StpClientUtil.STP.getLoginIdAsLong());
|
||||
actDynamic.setPublishTime(DateUtil.now());
|
||||
dynamicService.insert(actDynamic);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/praise/?")
|
||||
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||
public Result praise(Long id) {
|
||||
int count = dynamicService.dao().count(
|
||||
ActDynamicPraise.class,
|
||||
Cnd.where("dynamicId", "=", id).and("userId", "=", StpClientUtil.STP.getLoginIdAsLong())
|
||||
);
|
||||
if(count > 0) {
|
||||
dynamicService.dao().clear(
|
||||
ActDynamicPraise.class,
|
||||
Cnd.where("dynamicId", "=", id).and("userId", "=", StpClientUtil.STP.getLoginIdAsLong())
|
||||
);
|
||||
} else {
|
||||
ActDynamicPraise praise = new ActDynamicPraise();
|
||||
praise.setUserId(StpClientUtil.STP.getLoginIdAsLong());
|
||||
praise.setDynamicId(id);
|
||||
praise.setPraiseTime(DateUtil.now());
|
||||
dynamicService.dao().insert(praise);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/onDelete/?")
|
||||
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result onDelete(Long id) {
|
||||
dynamicService.delete(id);
|
||||
dynamicService.dao().clear(ActDynamicPraise.class, Cnd.where("dynamicId", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.budwk.app.client.module.activity.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.module.activity.models.ActDynamic;
|
||||
|
||||
/**
|
||||
* @ClassName ActHighLightsService
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/4/30 14:17
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
public interface ClientDynamicService extends BaseService<ActDynamic> {
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.budwk.app.client.module.activity.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.client.module.activity.service.ClientDynamicService;
|
||||
import com.budwk.app.module.activity.models.ActDynamic;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
/**
|
||||
* @ClassName ActHighLightsServiceImpl
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/4/30 14:17
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class ClientDynamicServiceImpl extends BaseServiceImpl<ActDynamic> implements ClientDynamicService {
|
||||
|
||||
public ClientDynamicServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.budwk.app.module.activity.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.json.JSONObject;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @ClassName ActDynamic
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/4/30 14:13
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Data
|
||||
@Comment("高光时刻")
|
||||
@Table("act_dynamic")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
public class ActDynamic extends BaseModel {
|
||||
|
||||
@Id
|
||||
@Comment("ID")
|
||||
private Long id;
|
||||
|
||||
@Column
|
||||
@Comment("活动ID")
|
||||
@ColDefine(customType = "bigint")
|
||||
private Long activityId;
|
||||
|
||||
@Column
|
||||
@Comment("用户id")
|
||||
@ColDefine(customType = "bigint")
|
||||
private Long userId;
|
||||
|
||||
@Column
|
||||
@Comment("发布内容")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String content;
|
||||
|
||||
@Comment("发布时间")
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String publishTime;
|
||||
|
||||
@Comment("发布图片")
|
||||
@Column
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<String> files;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.budwk.app.module.activity.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
|
||||
/**
|
||||
* @ClassName ActDynamicPraise
|
||||
* @Author JyuHsin
|
||||
* @Date 2025/4/30 14:27
|
||||
* @Version 1.0
|
||||
* @Description TODO
|
||||
*/
|
||||
@Data
|
||||
@Comment("赞")
|
||||
@Table("act_dynamic_praise")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
public class ActDynamicPraise extends BaseModel {
|
||||
|
||||
@Id
|
||||
@Comment("ID")
|
||||
private Long id;
|
||||
|
||||
@Column
|
||||
@Comment("精彩瞬间id")
|
||||
@ColDefine(customType = "bigint")
|
||||
private Long dynamicId;
|
||||
|
||||
@Column
|
||||
@Comment("用户id")
|
||||
@ColDefine(customType = "bigint")
|
||||
private Long userId;
|
||||
|
||||
@Comment("点赞时间")
|
||||
@Column
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String praiseTime;
|
||||
}
|
||||
@@ -78,6 +78,12 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "成绩查询"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/activity/dynamic/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "精彩瞬间"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
<template>
|
||||
<view class="publish-modal" v-if="show">
|
||||
<view class="modal-mask" @tap="handleClose"></view>
|
||||
<view class="modal-content">
|
||||
<view class="modal-header">
|
||||
<text class="title">发布动态</text>
|
||||
<text class="close" @tap="handleClose">×</text>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<textarea
|
||||
class="content-input"
|
||||
v-model="momentData.content"
|
||||
placeholder="分享你的想法..."
|
||||
:maxlength="500"
|
||||
auto-height
|
||||
:focus="show"
|
||||
/>
|
||||
<view class="input-counter">{{ momentData.content.length }}/500</view>
|
||||
|
||||
<!-- 图片上传区域 -->
|
||||
<view class="image-uploader">
|
||||
<view class="uploader-title">添加图片</view>
|
||||
<view class="upload-grid">
|
||||
<!-- 已上传图片 -->
|
||||
<view
|
||||
v-for="(image, index) in momentData.images"
|
||||
:key="index"
|
||||
class="upload-item"
|
||||
>
|
||||
<image class="upload-image" :src="image.path" mode="aspectFill"/>
|
||||
<view class="remove-icon" @tap.stop="removeImage(index)">×</view>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<view
|
||||
class="upload-btn"
|
||||
@tap="chooseImages"
|
||||
v-if="momentData.images.length < 9"
|
||||
>
|
||||
<text class="upload-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 位置信息 -->
|
||||
<!-- <view class="location-selector">-->
|
||||
<!-- <view class="selector-title">位置信息</view>-->
|
||||
<!-- <view-->
|
||||
<!-- v-if="!momentData.location"-->
|
||||
<!-- class="location-btn"-->
|
||||
<!-- @tap="chooseLocation"-->
|
||||
<!-- >-->
|
||||
<!-- <text class="location-icon">📍</text>-->
|
||||
<!-- <text class="location-text">添加位置</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- <view v-else class="location-info">-->
|
||||
<!-- <view class="location-name">-->
|
||||
<!-- <text class="location-icon">📍</text>-->
|
||||
<!-- <text>{{ momentData.location.name }}</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- <view class="location-address">{{ momentData.location.address }}</view>-->
|
||||
<!-- <text class="clear-location" @tap="clearLocation">×</text>-->
|
||||
<!-- </view>-->
|
||||
<!-- </view>-->
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<button
|
||||
class="publish-button"
|
||||
:class="{ 'button-active': canPublish }"
|
||||
:disabled="!canPublish"
|
||||
@tap="publishMoment"
|
||||
>
|
||||
发布
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useRequest } from "@/utils/request"
|
||||
|
||||
const request = useRequest()
|
||||
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
actId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
// 定义 emit
|
||||
const emit = defineEmits(['update:show', 'close', 'published'])
|
||||
|
||||
// 动态数据
|
||||
const momentData = reactive({
|
||||
content: '',
|
||||
images: [],
|
||||
location: null
|
||||
})
|
||||
|
||||
// 计算是否可以发布
|
||||
const canPublish = computed(() =>
|
||||
momentData.content.trim().length > 0 || momentData.images.length > 0
|
||||
)
|
||||
|
||||
// 关闭弹窗并重置表单
|
||||
function handleClose() {
|
||||
emit('update:show', false)
|
||||
emit('close')
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
momentData.content = ''
|
||||
momentData.images = []
|
||||
momentData.location = null
|
||||
}
|
||||
|
||||
// 选择图片
|
||||
function chooseImages() {
|
||||
if (momentData.images.length >= 9) {
|
||||
uni.showToast({ title: '最多只能上传9张图片', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const count = 9 - momentData.images.length
|
||||
uni.chooseImage({
|
||||
count,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFiles = res.tempFiles
|
||||
momentData.images.push(...tempFiles)
|
||||
console.log( momentData.images)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
function removeImage(index) {
|
||||
momentData.images.splice(index, 1)
|
||||
}
|
||||
|
||||
// 选择位置
|
||||
function chooseLocation() {
|
||||
uni.chooseLocation({
|
||||
success: (res) => {
|
||||
momentData.location = {
|
||||
name: res.name,
|
||||
address: res.address,
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 清除位置
|
||||
function clearLocation() {
|
||||
momentData.location = null
|
||||
}
|
||||
|
||||
// 发布动态
|
||||
async function publishMoment() {
|
||||
if (!canPublish.value) return
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '发布中...' })
|
||||
|
||||
const uploadedImages = []
|
||||
console.log(momentData.images)
|
||||
for (const image of momentData.images) {
|
||||
try {
|
||||
const uploadResult = await request.upload(image)
|
||||
if(uploadResult.code === 0) {
|
||||
uploadedImages.push(uploadResult.data)
|
||||
}
|
||||
} catch (uploadError) {
|
||||
console.error('图片上传失败:', uploadError)
|
||||
uni.showToast({
|
||||
title: '图片上传失败: ' + (uploadError.message || '未知错误'),
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (momentData.images.length > 0 && uploadedImages.length === 0) {
|
||||
throw new Error('所有图片上传失败')
|
||||
}
|
||||
|
||||
const publishData = {
|
||||
content: momentData.content,
|
||||
files: uploadedImages,
|
||||
activityId: props.actId
|
||||
}
|
||||
|
||||
const resp = await request.post('/client/activity/dynamic/onHandle', publishData)
|
||||
if(resp.code === 0) {
|
||||
uni.showToast({ title: '发布成功', icon: 'success' })
|
||||
emit('published')
|
||||
handleClose()
|
||||
} else {
|
||||
throw new Error(resp.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发布动态失败:', error)
|
||||
uni.showToast({ title: error.message || '发布失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.publish-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
|
||||
.modal-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: slideUpIn 0.3s ease;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
position: relative;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30rpx;
|
||||
|
||||
.content-input {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.input-counter {
|
||||
text-align: right;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.image-uploader {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.uploader-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.upload-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -10rpx;
|
||||
|
||||
.upload-item, .upload-btn {
|
||||
width: calc(33.333% - 20rpx);
|
||||
margin: 10rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.upload-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 22rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
aspect-ratio: 1/1;
|
||||
border: 2rpx dashed #ddd;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.upload-icon {
|
||||
font-size: 60rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.location-selector {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.selector-title {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.location-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.location-icon {
|
||||
font-size: 36rpx;
|
||||
color: #ff5777;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.location-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.location-info {
|
||||
background: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
|
||||
.location-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.location-icon {
|
||||
font-size: 32rpx;
|
||||
color: #ff5777;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.location-address {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 42rpx;
|
||||
}
|
||||
|
||||
.clear-location {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
border-radius: 22rpx;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: #666;
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 20rpx 30rpx calc(40rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.publish-button {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
border-radius: 45rpx;
|
||||
background: #f0f0f0;
|
||||
color: #999;
|
||||
font-size: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.button-active {
|
||||
background: #5c6bc0;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUpIn {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.publish-header {
|
||||
background: var(--gradient-primary);
|
||||
|
||||
.title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-button {
|
||||
background: var(--gradient-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<t-dropdown-menu class="top-dropdown">
|
||||
<t-dropdown-item :options="actOptions" :value="selectActId"/>
|
||||
</t-dropdown-menu>
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="list-scroll-view"
|
||||
@scrolltolower="handleScrollToBottom"
|
||||
refresher-enabled
|
||||
:refresher-triggered="refresherTriggered"
|
||||
@refresherrefresh="handleRefresh"
|
||||
refresher-background="#f2f3f5"
|
||||
>
|
||||
<view class="content-container">
|
||||
<view v-for="item in dataList">
|
||||
<view class="content-container-header">
|
||||
<view>
|
||||
<t-avatar shape="circle" image="https://tdesign.gtimg.com/mobile/demos/avatar1.png" />
|
||||
</view>
|
||||
<view style="margin-left: 12px">
|
||||
<view class="title">{{ item.userName }}</view>
|
||||
<view class="content">{{ item.sex }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-container-text">
|
||||
{{ item.content }}
|
||||
</view>
|
||||
<view class="content-container-pic">
|
||||
<t-image
|
||||
v-for="p in JSON.parse(item.files)"
|
||||
v-if="item.files && item.files?.length > 0"
|
||||
class="image-container"
|
||||
:style="{ width: '72px', height: '72px', 'margin-right': '8px'}"
|
||||
fit="contain"
|
||||
@click="onView(item.files)"
|
||||
:src="p"></t-image>
|
||||
</view>
|
||||
<view class="content-container-footer">
|
||||
<view class="time">{{ moment(item.publish).format('MM-DD HH:mm') }}</view>
|
||||
<view @click="onPraise(item)">
|
||||
<t-badge :count="item.praiseCount" :offset="[-2, -2]" class="badge-item" :showZero="true">
|
||||
<t-icon :style="item.praiseCount > 0 ? 'color: red' : ''" name="thumb-up-2-filled"/>
|
||||
</t-badge>
|
||||
</view>
|
||||
</view>
|
||||
<t-divider />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-container">
|
||||
<t-loading v-if="loading" text="加载中..." layout="vertical" theme="circular" size="40rpx"/>
|
||||
<t-divider v-else-if="!hasMore && dataList.length > 0" content="没有更多数据了"/>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<t-empty
|
||||
v-if="!loading && dataList.length === 0"
|
||||
description="暂无精彩瞬间"
|
||||
/>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
|
||||
<t-fab :icon="iconFunc" draggable="all" :y-bounds="[30, 20]" @click="onClick" />
|
||||
<t-image-viewer v-model:images="imagesArray" v-model:visible="imageVisible" />
|
||||
<publish-moment v-model:show="dynamicVisible" :actId="selectActId" @published="getListData"></publish-moment>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, reactive, onMounted, computed, h} from 'vue';
|
||||
import {Toast} from 'tdesign-mobile-vue';
|
||||
import {useRequest} from "@/utils/request";
|
||||
import { AddIcon } from 'tdesign-icons-vue-next';
|
||||
import PublishMoment from "@/pages/activity/dynamic/PublishMoment.vue";
|
||||
import moment from "moment";
|
||||
|
||||
const iconFunc = () => h(AddIcon, { size: '24px' });
|
||||
const request = useRequest()
|
||||
const actList = ref([])
|
||||
const imagesArray = ref([])
|
||||
const imageVisible = ref(false)
|
||||
const refresherTriggered = ref(false);
|
||||
const loading = ref(false);
|
||||
const hasMore = ref(true);
|
||||
const dataList = ref([]);
|
||||
const content = ref(null);
|
||||
|
||||
const dynamicVisible = ref(false);
|
||||
|
||||
const actOptions = computed(() => {
|
||||
return actList.value.map(item => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: item.title
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const selectActId = ref(null)
|
||||
const pageForm = reactive({
|
||||
activityId: null,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
searchKeyword: '',
|
||||
status: '' // 全部
|
||||
})
|
||||
|
||||
async function onClick() {
|
||||
dynamicVisible.value = true
|
||||
}
|
||||
async function onPraise(row) {
|
||||
request.post('/client/activity/dynamic/praise/' + row.id).then(res=>{
|
||||
if(res.code===0){
|
||||
getListData(false)
|
||||
}else{
|
||||
Toast({message: res.msg, theme: 'error'});
|
||||
}
|
||||
})
|
||||
}
|
||||
async function listMineAct() {
|
||||
try {
|
||||
const {data, code, msg} = await request.get('/client/activity/list-mine-act')
|
||||
if (code === 0) {
|
||||
actList.value = data
|
||||
if (actList.value.length) {
|
||||
selectActId.value = actList.value[0].id
|
||||
}
|
||||
} else {
|
||||
Toast.error(msg)
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(e.message)
|
||||
}
|
||||
}
|
||||
async function onView(src) {
|
||||
console.log(src)
|
||||
imagesArray.value = src
|
||||
imageVisible.value = true
|
||||
}
|
||||
// 触底加载更多
|
||||
const handleScrollToBottom = () => {
|
||||
if (loading.value || !hasMore.value) return;
|
||||
getListData();
|
||||
};
|
||||
//下拉刷新
|
||||
const handleRefresh = () => {
|
||||
if (loading.value) {
|
||||
refresherTriggered.value = false;
|
||||
return;
|
||||
}
|
||||
refresherTriggered.value = true;
|
||||
pageForm.pageNumber = 1;
|
||||
getListData(true);
|
||||
};
|
||||
// 获取列表数据
|
||||
const getListData = async (isRefresh = false) => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
if (isRefresh) {
|
||||
pageForm.pageNumber = 1;
|
||||
}
|
||||
// 延时1秒,模拟网络请求
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
try {
|
||||
const response = await useRequest().post('/client/activity/dynamic/list', {
|
||||
pageForm: JSON.stringify(pageForm),
|
||||
activityId: selectActId.value
|
||||
});
|
||||
if (response.code === 0) {
|
||||
const {list, totalCount} = response.data;
|
||||
if (isRefresh) {
|
||||
dataList.value = list;
|
||||
} else {
|
||||
dataList.value = [...dataList.value, ...list];
|
||||
}
|
||||
pageForm.totalCount = totalCount;
|
||||
hasMore.value = dataList.value.length < totalCount;
|
||||
pageForm.pageNumber++;
|
||||
} else {
|
||||
Toast({message: '获取列表失败', theme: 'error'});
|
||||
}
|
||||
} catch (error) {
|
||||
Toast({message: '获取列表失败', theme: 'error'});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
// 重置下拉刷新状态
|
||||
if (refresherTriggered.value) {
|
||||
refresherTriggered.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
listMineAct();
|
||||
getListData();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
position: relative;
|
||||
height: 245px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.textarea {
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
.list-scroll-view {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
.page-container {
|
||||
background: white;
|
||||
}
|
||||
.content-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.content-container-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
color: rgb(35, 110, 180);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.content {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.content-container-text {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.content-container-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
.t-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
.time {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
.button-group {
|
||||
background-color: var(--bg-color-demo, #fff);
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
|
||||
.t-button {
|
||||
height: 32px;
|
||||
flex: 1;
|
||||
|
||||
&:not(:last-child) {
|
||||
flex: 1;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content-container-pic {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="header">
|
||||
<image class="header-bg" src="https://erp-bucket.pek3b.qingstor.com/assistant/89c4b573-c54f-467a-b703-0bc44679ab08.png" mode="aspectFill"></image>
|
||||
<image class="header-bg"
|
||||
src="https://erp-bucket.pek3b.qingstor.com/assistant/89c4b573-c54f-467a-b703-0bc44679ab08.png"
|
||||
mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
<view class="menu-container">
|
||||
@@ -30,6 +32,12 @@
|
||||
</view>
|
||||
<view class="menu-text">项目报名</view>
|
||||
</view>
|
||||
<view class="menu-item" @click="navigateTo('/pages/activity/dynamic/index')">
|
||||
<view class="menu-icon-wrapper blue-bg">
|
||||
<view class="menu-icon star-icon"></view>
|
||||
</view>
|
||||
<view class="menu-text">精彩瞬间</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 精选内容区 -->
|
||||
@@ -41,7 +49,9 @@
|
||||
|
||||
<view class="activity-list">
|
||||
<view class="activity-item" @click="navigateTo('/pages/process/index')">
|
||||
<image class="activity-image" src="https://erp-bucket.pek3b.qingstor.com/assistant/746c37d6-018d-4c64-8a98-ea9ec8b0b3b5.png" mode="widthFix"></image>
|
||||
<image class="activity-image"
|
||||
src="https://erp-bucket.pek3b.qingstor.com/assistant/746c37d6-018d-4c64-8a98-ea9ec8b0b3b5.png"
|
||||
mode="widthFix"></image>
|
||||
<view class="activity-info">
|
||||
<view class="activity-title">一分钟了解运动会流程</view>
|
||||
<view class="activity-meta">
|
||||
@@ -56,7 +66,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
function navigateTo(url) {
|
||||
uni.navigateTo({
|
||||
@@ -64,7 +74,7 @@ function navigateTo(url) {
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
onMounted(() => {
|
||||
console.log('...........')
|
||||
uni.showToast({
|
||||
title: '欢迎使用',
|
||||
|
||||
Reference in New Issue
Block a user