From 162d7850b620f44e4381def1ae15610500a377ee Mon Sep 17 00:00:00 2001 From: Therainclouds <245141853@qq.com> Date: Thu, 12 Feb 2026 23:13:17 +0800 Subject: [PATCH] n --- .../shop/__pycache__/views.cpython-312.pyc | Bin 69065 -> 69403 bytes backend/shop/views.py | 7 ++ frontend/src/api.js | 2 +- frontend/src/pages/activity/Detail.jsx | 112 ++++++++++++++++-- miniprogram/src/pages/order/checkout.tsx | 15 ++- 5 files changed, 122 insertions(+), 14 deletions(-) diff --git a/backend/shop/__pycache__/views.cpython-312.pyc b/backend/shop/__pycache__/views.cpython-312.pyc index 995e4134d447259eb9ebbee50bc7df28ab35a6ce..8c2c7bf3002767408d183fce690e13aece2a19a3 100644 GIT binary patch delta 2587 zcmZ8j4Nz3q6@KUK4=c<5;EGuds8mu|BIsx*(U>Me3^lQpw5FkP8j!58xCmI4{n?5TFrmY#1R!*HLh`s(_mP)#EVU<}FSK~7h51C53*#QvF%Qm?FXRi&<|e%sEJaQ5vW4X;y`Ri7 zA(?&5DLk27vQF@c4kNnHW1HYJxk}gws~jr@S9CBclSwjr!7UfBl>J4^d1;ZeW+^Ef zUKNvb$D-A8bsXt?{TCbpf*({ZhZ13(h|wn>1djWrmX?Uk&@^TQVUy_66+OwlIH~pt zRqYP7we}y}8>)Hx%fR9O%J)Ls-wpjn>EF6D^Z|yp)r9_f@bcdp`gXqiWmU^~6wBt6 zD_lDEHRod62?jWD#gF+s&$$-k=JH|S5c4461I@o!~K|- zU=2Yj>Be)xH858h#TFxM!NYZl;8uF-UIf^N3->JLs@L{-K~@I#z{_*7_85QDOkp0aAm{n1epR16i`l5 zH31UrBWNTzh;KJ6fUognLvqB>RD&V;npL^cP@tdc;ntwOSbjO19VP}EQC5MQ_A|a= zSNG3@{Yu;cw`go37LQWhc*p{+nAUO?JWA>j6POh;R1h4(w4{NPL+gGQv9{u_wmD`h zn~jllv^@bIqV?Fcuq^YBrJ9eB7Zv22fpfPy@wqiNM^v$#yEez4?eXTj3)mOjcv^Yq zSYL#39XY>(wI_ZB5?(nm+tAMyZz_(Dvcax(PY!CK?aJ#Pm+7M3A_cXc!F#u!T9HN# zxJ0I|sY#Oaa*8~DpDRKwfoW?p&Y?D2LLIeJl!G;M8JASV;47VR*l{W*MqL=SQ8iRs zBK-E$%=lVjp^;|)x}~5q>O4>bu|Y_ zS4+D8k#vQi0xQqVhC|qNCd+(+6#pbRiI&a<#t{;4U`FRMzOg$x=Op}x6b}$VCrMod z6E>UvcH_m)$DtNuyPVPAGxY*C?U{zFyP^!=an}LG+m)<`Jf-gQAsF9~bC1jiegJ)C zqAVR8_(-VD&nI>=sbl1_Y6l?=4bOS&T%u8%DNH<{Ao7#=XXi8F8Rd89Pmfc3d*(wo z?(NA0Ng3)X9M`YyO|y3sxym)2BoiL(eb(sZQ|5Ey!NJFrr!M7-_e%WJ7d4#ciOT^f zRk|;q67NzZmM2n*KkF&hT&-FzS>? zV%8OzC;H&Z>X-+3+d)h2TDM1HazVC~+WsAl+gNu{YbJjshI+=ZwxJA3C?p+>?JT_0mt>+!uY3ln>X&g*} zWqJxS5=$s%?s6s-(rIajxvE^aCStum79Sf5!d)fix&_2M zzzZQKK0f@%drN+8I3{*G#nRY>ossIJA>j{hJPbX`iW@Hjze(O7na=N!(psu?dX2sabk>7T#L;nIq{_ RN7AsM{vBd1S!Q73{{iw-^G*N& delta 2266 zcmZvddr(wW9LM)}S$2VC0Yx5~KnNl014T#8d_U8`M`LO_DIoU>J0c5bS5dS=pa2U~ zf6_8TzH5|VrqP>13KbQaL5&t-ZX%Bn8cfDOL}n1|{O&4&h}eeq!S%PbsQ8s76~L#sYG{ClLEC`222XCYK zXl`K}2~C6s!giWSA?(1&Lra2p(eO6Sm0u!WF}rH67MR9>7{qJdr5S1=kj z%ORhw1Nacr4lm@U%EKnuC{H=E*&*t0>6vfVy zGo2=ufmNp%-=^88C&E$LblNCr&QSoPeEZ9<^-zjfdJl|p+BZ(1J3|v0gtLejZt4`_ zI~2u;DHkhvl<8&Tbw85eCqgAAmyKQiEe*dTbdvNTp^GqqkV+HN2)_`j2-SpN3BPd| z{MlYo<`YWsZrL~;b;9g%F|d3#e2>xP!v`hKEDv)#Pb#X>?9W}XqTCPN^0c0=DN<~F zj3p|;6lY9ijeNdRepLRqRuf11c)U~jHss)}OF@n;+>j+pm!iPKz9uA4YP}-YTuyUv z-A4vmki~pOqN|oqqaF+bQ&!qZls3m~OR$&?TBQnRU#b2WO250QsVmrA^`@7y5=v)^ z(7q-ZQ5_JtpM0hg8aQk&w1uW5DbXl0dyn{0^(&4Yd@f%;SKZa$$(b5YuYbtxAZ58n zqgFx&-mD3Nv)Eg+R`&xLE)gzc)b+`lP8vSI{Oe2jb~aug7krfrJ`~`08vQ{Su*LLR zgW`*3fLeoY)I<2Uuc}?RYP%= zSR-hp;a9FfjeA;$VQ^EpCZ2yrX5+z**W|pWIN@1YLUS&Ux4+p6JLJ%oKLvyRXpsLt zUR5D5B~}DCyu~s(4X*Z@Yj8sQNL-UwFyQleQFtf%WNHCJz)iTXQ6+0V7Ss8ognJXUf5V(ajMT+X#xM> zSMwjv8l#l5O0tBR`RbY*FsXYh|Kr^5zT@z0=YM{@MB_(o2B3HEbp9V&*}E7I_N@p0RQ#MQ~XPMRH41N$_lIsi_2k(_?cmwvo Wdc!mHnrCRIN_p)NOA1|q3I74Atcg?r diff --git a/backend/shop/views.py b/backend/shop/views.py index 26f830f..4f28df1 100644 --- a/backend/shop/views.py +++ b/backend/shop/views.py @@ -669,6 +669,13 @@ class OrderViewSet(viewsets.ModelViewSet): return queryset.filter(wechat_user=user).order_by('-created_at') return queryset.order_by('-created_at') + def perform_create(self, serializer): + """ + 创建订单时自动关联当前微信用户 + """ + user = get_current_wechat_user(self.request) + serializer.save(wechat_user=user) + @action(detail=True, methods=['post']) def prepay_miniprogram(self, request, pk=None): """ diff --git a/frontend/src/api.js b/frontend/src/api.js index f9908aa..5d074ac 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -67,7 +67,7 @@ export const getMyPaidItems = () => api.get('/users/paid-items/'); export const getAnnouncements = () => api.get('/community/announcements/'); export const getActivities = () => api.get('/community/activities/'); export const getActivityDetail = (id) => api.get(`/community/activities/${id}/`); -export const signUpActivity = (id) => api.post(`/community/activities/${id}/signup/`); +export const signUpActivity = (id, data) => api.post(`/community/activities/${id}/signup/`, data); export const getMySignups = () => api.get('/community/activities/my_signups/'); export default api; diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx index 1cd1a21..ac85c50 100644 --- a/frontend/src/pages/activity/Detail.jsx +++ b/frontend/src/pages/activity/Detail.jsx @@ -3,14 +3,19 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { motion, useScroll, useTransform } from 'framer-motion'; -import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined } from '@ant-design/icons'; +import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined, UserOutlined } from '@ant-design/icons'; import confetti from 'canvas-confetti'; -import { message, Spin, Button, Result } from 'antd'; +import { message, Spin, Button, Result, Modal, Form, Input } from 'antd'; import { getActivityDetail, signUpActivity } from '../../api'; import styles from '../../components/activity/activity.module.less'; import { pageTransition, buttonTap } from '../../animation'; import LoginModal from '../../components/LoginModal'; import { useAuth } from '../../context/AuthContext'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import rehypeRaw from 'rehype-raw'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; const ActivityDetail = () => { const { id } = useParams(); @@ -19,6 +24,8 @@ const ActivityDetail = () => { const { scrollY } = useScroll(); const { login } = useAuth(); const [loginVisible, setLoginVisible] = useState(false); + const [signupFormVisible, setSignupFormVisible] = useState(false); + const [form] = Form.useForm(); // Header animation: transparent to white with shadow const headerBg = useTransform(scrollY, [0, 60], ['rgba(255,255,255,0)', 'rgba(255,255,255,1)']); @@ -26,7 +33,7 @@ const ActivityDetail = () => { const headerColor = useTransform(scrollY, [0, 60], ['rgba(255,255,255,1)', 'rgba(0,0,0,0.85)']); const titleOpacity = useTransform(scrollY, [100, 200], [0, 1]); - const { data: activity, isLoading, error } = useQuery({ + const { data: activity, isLoading, error, refetch } = useQuery({ queryKey: ['activity', id], queryFn: async () => { try { @@ -36,13 +43,32 @@ const ActivityDetail = () => { throw new Error(err.response?.data?.detail || 'Failed to load activity'); } }, - staleTime: 5 * 60 * 1000, + staleTime: 0, // Ensure fresh data + refetchOnMount: 'always', // Force refetch on mount }); + // Force a refresh if needed (as requested by user) + useEffect(() => { + // 1. Force React Query refetch + refetch(); + + // 2. Hard refresh logic after 1 second delay + const timer = setTimeout(() => { + const hasRefreshedKey = `has_refreshed_activity_${id}`; + if (!sessionStorage.getItem(hasRefreshedKey)) { + sessionStorage.setItem(hasRefreshedKey, 'true'); + window.location.reload(); + } + }, 1000); + + return () => clearTimeout(timer); + }, [id, refetch]); + const signUpMutation = useMutation({ - mutationFn: () => signUpActivity(id), + mutationFn: (values) => signUpActivity(id, { signup_info: values || {} }), onSuccess: () => { message.success('报名成功!'); + setSignupFormVisible(false); confetti({ particleCount: 150, spread: 70, @@ -53,7 +79,7 @@ const ActivityDetail = () => { queryClient.invalidateQueries(['activities']); }, onError: (err) => { - message.error(err.response?.data?.detail || '报名失败,请稍后重试'); + message.error(err.response?.data?.detail || err.response?.data?.error || '报名失败,请稍后重试'); } }); @@ -81,7 +107,18 @@ const ActivityDetail = () => { setLoginVisible(true); return; } - signUpMutation.mutate(); + + // Check if we need to collect info + if (activity.signup_form_config && activity.signup_form_config.length > 0) { + setSignupFormVisible(true); + } else { + // Direct signup if no info needed + signUpMutation.mutate({}); + } + }; + + const handleFormSubmit = (values) => { + signUpMutation.mutate(values); }; if (isLoading) { @@ -109,6 +146,11 @@ const ActivityDetail = () => { ); } + const cleanUrl = (url) => { + if (!url) return ''; + return url.replace(/[`\s]/g, ''); + }; + return ( {
@@ -189,6 +231,10 @@ const ActivityDetail = () => { {activity.location || '线上活动'}
+
+ + {activity.current_signups || 0} / {activity.max_participants} 已报名 +
@@ -200,7 +246,33 @@ const ActivityDetail = () => {

活动详情

-
暂无详情描述

' }} /> +
+ + {String(children).replace(/\n$/, '')} + + ) : ( + + {children} + + ) + } + }} + > + {activity.description || activity.content || '暂无详情描述'} + +
@@ -232,6 +304,28 @@ const ActivityDetail = () => { // Auto trigger signup after login if needed, or just let user click again }} /> + + setSignupFormVisible(false)} + onOk={form.submit} + confirmLoading={signUpMutation.isPending} + destroyOnHidden + > +
+ {activity.signup_form_config && activity.signup_form_config.map(field => ( + + + + ))} +
+
); }; diff --git a/miniprogram/src/pages/order/checkout.tsx b/miniprogram/src/pages/order/checkout.tsx index 5996fdd..63bfe2a 100644 --- a/miniprogram/src/pages/order/checkout.tsx +++ b/miniprogram/src/pages/order/checkout.tsx @@ -101,14 +101,21 @@ export default function Checkout() { try { const orderPromises = items.map(item => { - const orderData = { - goodid: item.id, + const type = params.type || 'config' + const orderData: any = { quantity: item.quantity, customer_name: address.userName, phone_number: address.telNumber, shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}`, - type: params.type || 'config' + ref_code: Taro.getStorageSync('ref_code') || '' } + + if (type === 'course') { + orderData.course = item.id + } else { + orderData.config = item.id + } + return createOrder(orderData) }) @@ -123,7 +130,7 @@ export default function Checkout() { if (results.length === 1) { // Single order, go to payment - const orderId = results[0].order_id + const orderId = results[0].id Taro.redirectTo({ url: `/pages/order/payment?id=${orderId}` })