From 3d2b121b926e15f69f3099f211713163b4727362 Mon Sep 17 00:00:00 2001 From: chashaobao Date: Wed, 15 Oct 2025 20:44:20 +0800 Subject: [PATCH] feat: --- src/app.tsx | 30 +++- .../group-certification-list/index.less | 79 +++++++++ .../group-certification-list/index.tsx | 153 ++++++++++++++++++ src/components/join-group-hint/index.tsx | 6 +- src/components/partner-intro/index.tsx | 70 ++++++-- src/constants/app.ts | 3 +- src/hooks/use-service-urls.tsx | 10 ++ src/http/api.ts | 8 + .../group-owner-certification/index.config.ts | 1 - .../group-owner-certification/index.less | 128 ++++++++++++++- src/pages/group-owner-certification/index.tsx | 129 +++++++++++++-- src/pages/group-v2/index.tsx | 24 +-- src/pages/user-batch-publish/index.tsx | 24 +-- src/store/actions/app.ts | 5 +- src/store/constants.ts | 1 + src/store/reducers/app.ts | 5 +- src/store/selector/app.ts | 2 + src/types/location.ts | 11 ++ src/types/partner.ts | 38 +++++ src/types/store.ts | 5 + src/utils/common.ts | 9 +- src/utils/location.ts | 17 +- src/utils/partner.ts | 31 +++- 23 files changed, 724 insertions(+), 65 deletions(-) create mode 100644 src/components/group-certification-list/index.less create mode 100644 src/components/group-certification-list/index.tsx create mode 100644 src/hooks/use-service-urls.tsx diff --git a/src/app.tsx b/src/app.tsx index 9aa1222..1c85406 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,4 +1,4 @@ -import { useLaunch } from '@tarojs/taro'; +import Taro, { useDidShow, useLaunch } from '@tarojs/taro'; import { PropsWithChildren } from 'react'; import { Provider } from 'react-redux'; @@ -6,8 +6,9 @@ import { Provider } from 'react-redux'; import { REFRESH_UNREAD_COUNT_TIME } from '@/constants/message'; import http from '@/http'; import store from '@/store'; +import { requestServiceUrls } from '@/utils/location'; import { requestUnreadMessageCount } from '@/utils/message'; -import { getInviteCode, getInviteCodeFromQuery } from '@/utils/partner'; +import { decryptOpenGid, getInviteCode, getInviteCodeFromQuery } from '@/utils/partner'; import qiniuUpload from '@/utils/qiniu-upload'; import { requestUserInfo, updateLastLoginTime } from '@/utils/user'; @@ -28,6 +29,31 @@ function App({ children }: PropsWithChildren) { setInterval(() => requestUnreadMessageCount(), REFRESH_UNREAD_COUNT_TIME); }); + useDidShow(options => { + requestServiceUrls(); + + console.log(options); + Taro.getGroupEnterInfo() + .then(info => { + const inviteCode = getInviteCodeFromQuery(options?.query || {}); + const authCode = options?.query?.authCode; + decryptOpenGid({ + inviteCode, + authCode, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + iv: info.iv, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + encryptedData: info.encryptedData, + }); + console.log('哈哈哈', info); + }) + .catch(() => { + console.log('没有解析到群', options?.scene); + }); + }); + return {children}; } diff --git a/src/components/group-certification-list/index.less b/src/components/group-certification-list/index.less new file mode 100644 index 0000000..9d2fde6 --- /dev/null +++ b/src/components/group-certification-list/index.less @@ -0,0 +1,79 @@ +@import '@/styles/common.less'; + +.group-certification-list { + min-height: calc(100vh - 98rpx); + &__banner { + font-weight: 400; + font-size: 24px; + height: 72px; + padding: 32px 32px 25px; + line-height: 36px; + color: #999999; + } + &__title { + height: 72px; + width: 100%; + padding: 0 24px; + box-sizing: border-box; + line-height: 72px; + font-size: 24px; + color: rgba(0, 0, 0, 0.5); + position: fixed; + top: 227rpx; + left: 0; + z-index: 1; + background: #fff; + + &-border { + border-bottom: 1px solid #e6e7e8; + .flex-row(); + } + + &-time { + padding: 0 8px; + flex: 0 0 120px; + width: 120px; + flex-shrink: 0; + } + + &-name { + text-align: right; + flex: 1; + } + } + + &__pull-refresh { + margin-top: 72px; + } + + &__item { + height: 100px; + width: 100%; + padding: 24px 32px 0 32px; + box-sizing: border-box; + font-size: 28px; + background: #fff; + + &-border { + border-bottom: 1px solid #e6e7e8; + } + + &-content { + .flex-row(); + width: 100%; + padding-bottom: 24px; + } + + &-time { + padding: 0 8px; + flex: 0 0 120px; + width: 120px; + flex-shrink: 0; + } + + &-name { + text-align: right; + flex: 1; + } + } +} diff --git a/src/components/group-certification-list/index.tsx b/src/components/group-certification-list/index.tsx new file mode 100644 index 0000000..1369d5c --- /dev/null +++ b/src/components/group-certification-list/index.tsx @@ -0,0 +1,153 @@ +import { List, PullRefresh } from '@taroify/core'; +import classNames from 'classnames'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import ListPlaceholder from '@/components/list-placeholder'; +import { AuthedGroupInfo } from '@/types/partner'; +import { logWithPrefix } from '@/utils/common'; +import { formatTimestamp, getAuthedGroupList as requestData } from '@/utils/partner'; + +import './index.less'; + +const PREFIX = 'group-certification-list'; +const log = logWithPrefix(PREFIX); + +const FIRST_PAGE = 0; + +function GroupCertificationList(props: { + refreshDisabled?: boolean; + visible?: boolean; + listHeight?: number; + className?: string; + onListEmpty?: () => void; +}) { + const { className, listHeight, refreshDisabled, visible = true, onListEmpty } = props; + const [hasMore, setHasMore] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [loadingMore, setLoadingMore] = useState(false); + const [loadMoreError, setLoadMoreError] = useState(false); + const [dataList, setDataList] = useState([]); + const currentPage = useRef(FIRST_PAGE); + const onListEmptyRef = useRef(onListEmpty); + + const handleRefresh = useCallback(async () => { + log('start pull refresh'); + try { + setRefreshing(true); + setLoadMoreError(false); + const content = await requestData(); + setDataList(content); + currentPage.current = 1; + // setHasMore(currentPage.current < totalPages); + !content.length && onListEmptyRef.current?.(); + log('pull refresh success'); + } catch (e) { + setDataList([]); + setHasMore(false); + setLoadMoreError(true); + currentPage.current = FIRST_PAGE; + log('pull refresh failed'); + } finally { + setRefreshing(false); + } + }, []); + + const handleLoadMore = useCallback(async () => { + log('start load more', hasMore); + if (!hasMore) { + return; + } + setLoadMoreError(false); + setLoadingMore(true); + try { + const content = await requestData(); + setDataList([...dataList, ...content]); + currentPage.current = currentPage.current + 1; + // setHasMore(currentPage.current < totalPages); + log('load more success'); + } catch (e) { + setLoadMoreError(true); + log('load more failed'); + } finally { + setLoadingMore(false); + } + }, [dataList, hasMore]); + + useEffect(() => { + onListEmptyRef.current = onListEmpty; + }, [onListEmpty]); + + // 初始化数据&配置变更后刷新数据 + useEffect(() => { + // 列表不可见时,先不做处理 + if (!visible) { + log('visible changed, but is not visible, only clear list'); + return; + } + + const refresh = async () => { + log('visible changed, start refresh list data'); + try { + setDataList([]); + setLoadingMore(true); + setLoadMoreError(false); + const content = await requestData(); + setDataList(content); + currentPage.current = 1; + // setHasMore(currentPage.current < totalPages); + !content.length && onListEmptyRef.current?.(); + } catch (e) { + setDataList([]); + setHasMore(false); + setLoadMoreError(true); + } finally { + log('visible changed, refresh list data end'); + setLoadingMore(false); + } + }; + refresh(); + }, [visible]); + + return ( +
+
+ 以下均为认证成功的群,没认证成功请先确认是否有拉运营进群,如果在,可以尝试重新分享小程序 +
+
+
+
认证日期
+
群名称
+
+
+ + + {dataList.map(item => ( +
+
+
+
{formatTimestamp(item.authDate, true)}
+
{item.groupName}
+
+
+
+ ))} + +
+
+
+ ); +} + +export default GroupCertificationList; diff --git a/src/components/join-group-hint/index.tsx b/src/components/join-group-hint/index.tsx index b5664ee..993684b 100644 --- a/src/components/join-group-hint/index.tsx +++ b/src/components/join-group-hint/index.tsx @@ -7,12 +7,11 @@ import { useCallback, useState } from 'react'; import { RoleType } from '@/constants/app'; import { CacheKey } from '@/constants/cache-key'; import { CITY_CODE_TO_NAME_MAP } from '@/constants/city'; -import { GROUPS } from '@/constants/group'; +import useServiceUrls from '@/hooks/use-service-urls'; import { getRoleTypeWithDefault } from '@/utils/app'; import { openCustomerServiceChat } from '@/utils/common'; import { getCurrentCityCode } from '@/utils/location'; import { checkCityCode, validCityCode } from '@/utils/user'; - import './index.less'; const PREFIX = 'join-group-hint'; @@ -25,7 +24,8 @@ const DEFAULT_GROUP = { export function JoinGroupHint() { const cityCode = getCurrentCityCode(); const roleType = getRoleTypeWithDefault(); - const group = GROUPS.find(g => String(g.cityCode) === cityCode); + const serviceUrls = useServiceUrls(); + const group = serviceUrls.find(g => String(g.cityCode) === cityCode); const [clicked, setClicked] = useState(!!Taro.getStorageSync(CacheKey.JOIN_GROUP_CARD_CLICKED)); const handleClick = useCallback(() => { if (group && !checkCityCode(cityCode)) { diff --git a/src/components/partner-intro/index.tsx b/src/components/partner-intro/index.tsx index 3327e92..c326de7 100644 --- a/src/components/partner-intro/index.tsx +++ b/src/components/partner-intro/index.tsx @@ -3,10 +3,14 @@ import Taro from '@tarojs/taro'; import { Swiper } from '@taroify/core'; import { GoodJob } from '@taroify/icons'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { PageUrl } from '@/constants/app'; +import { EarnType, UserProfitListItem } from '@/types/partner'; import { openCustomerServiceChat } from '@/utils/common'; -import { getCouponQrCode, generateMembershipCoupon } from '@/utils/coupon'; +import { generateMembershipCoupon, getCouponQrCode } from '@/utils/coupon'; +import { formatMoney, formatTimestamp, getLastProfitList } from '@/utils/partner'; +import { navigateTo } from '@/utils/route'; import './index.less'; const PREFIX = 'partner-intro'; @@ -112,11 +116,44 @@ export default function PartnerIntro() { } }; - const handleConfirm = useCallback(() => {}, []); + const handleConfirm = useCallback(() => { + navigateTo(PageUrl.GroupOwnerCertificate); + }, []); const handleOpenService = useCallback(() => { openCustomerServiceChat('https://work.weixin.qq.com/kfid/kfc4fcf6b109b3771d7'); }, []); + + const timerRef = useRef(null); + const [bannerList, setBannerList] = useState([]); + + const getBannerList = useCallback(async () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + + const list = await getLastProfitList(); + setBannerList(s => [...s, ...list]); + + timerRef.current = setTimeout( + () => { + getBannerList(); + }, + 3000 * (list.length || 10) + ); + }, []); + + useEffect(() => { + getBannerList(); + + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + }; + }, [getBannerList]); return (
@@ -130,18 +167,23 @@ export default function PartnerIntro() { mode="aspectFill" /> - -
2024.02.02 12:23:23
-
-
zbldakjdjsksada
-
- 主播被开聊
+2.15
+ {bannerList.map((item, index) => ( + +
{formatTimestamp(item.updatedAt)}
+
+
{item.userId}
+
+ {[EarnType.CHAT_ACTIVITY_SHARE_L1, EarnType.CHAT_ACTIVITY_SHARE_L2].includes(item.earnType) + ? '主播被开聊' + : '会员支付'} +
+{formatMoney(item.total)}
+
+
+ 累计
{formatMoney(item.amount)}
+
-
- 累计
1200.15
-
-
- + + ))}
diff --git a/src/constants/app.ts b/src/constants/app.ts index 43152fd..f2965c7 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -35,6 +35,7 @@ export enum OpenSource { UserPage = 'user_page', AnchorPage = 'anchor_page', MaterialViewPage = 'material_view_page', + GroupOwnerCertificate = 'group_owner_certificate', } export enum PageUrl { @@ -77,7 +78,7 @@ export enum PageUrl { WithdrawRecord = 'pages/withdraw-record/index', GroupDelegatePublish = 'pages/group-delegate-publish/index', GiveVip = 'pages/give-vip/index', - GroupOwnerCertificate = 'pages/group-owner-certificate/index', + GroupOwnerCertificate = 'pages/group-owner-certification/index', } export enum PluginUrl { diff --git a/src/hooks/use-service-urls.tsx b/src/hooks/use-service-urls.tsx new file mode 100644 index 0000000..5993ddc --- /dev/null +++ b/src/hooks/use-service-urls.tsx @@ -0,0 +1,10 @@ +import { useSelector } from 'react-redux'; + +import { selectServiceUrls } from '@/store/selector'; + +function useServiceUrls() { + const data = useSelector(selectServiceUrls); + return data || []; +} + +export default useServiceUrls; diff --git a/src/http/api.ts b/src/http/api.ts index 024cd42..30e7e1c 100644 --- a/src/http/api.ts +++ b/src/http/api.ts @@ -88,4 +88,12 @@ export enum API { GENERATE_MEMBERSHIP_COUPON = '/coupon/membership/generate', CLAIM_MEMBERSHIP_COUPON = '/coupon/membership/claim', GET_VIP_QRCODE = '/user/getVipQrCode', + // 群认证 + GENERATE_GROUP_AUTH_CODE = '/partner/generateGroupAuthCode', + DECRYPT_OPEN_GID = '/partner/decryptOpenGid', + GET_USER_PROFIT_LIST = '/partner/getLatestUserProfitList', + GET_AUTHED_GROUP_LIST = '/partner/getAuthedGroupList', + GET_STAFF_CODE = '/partner/staff/{cityCode}', + // 所有城市运营 + GET_ALL_CITY_OPERATOR = '/group/getAllGroup', } diff --git a/src/pages/group-owner-certification/index.config.ts b/src/pages/group-owner-certification/index.config.ts index 2d789b8..9931a1f 100644 --- a/src/pages/group-owner-certification/index.config.ts +++ b/src/pages/group-owner-certification/index.config.ts @@ -1,5 +1,4 @@ export default definePageConfig({ navigationBarTitleText: '群主认证', enableShareAppMessage: true, - usingComponents: {}, }); diff --git a/src/pages/group-owner-certification/index.less b/src/pages/group-owner-certification/index.less index b04c63b..041f6f8 100644 --- a/src/pages/group-owner-certification/index.less +++ b/src/pages/group-owner-certification/index.less @@ -1 +1,127 @@ -.group-owner-certification {} +@import '@/styles/common.less'; +@import '@/styles/variables.less'; + +.group-owner-certification { + &__tabs { + --tabs-active-color: @blHighlightColor; + --tabs-nav-background-color: #fff; + --tabs-wrap-height: 98px; + + > .taroify-tabs__wrap { + position: fixed; + width: 100vw; + top: 0; + left: 0; + z-index: 2; + } + + > .taroify-tabs__content { + padding-top: var(--tabs-wrap-height); + } + } + + &__main { + padding-left: 24px; + padding-right: 24px; + padding-top: 48px; + } + + &__block { + margin-bottom: 40px; + } + + &__card { + background: #fff; + border-radius: 24px; + padding: 24px 32px; + margin-bottom: 48px; + } + + &__bold { + font-weight: 500; + margin-bottom: 8px; + } + + &__body { + font-weight: 400; + font-size: 28px; + line-height: 40px; + color: @blColor; + + .highlight { + color: @blHighlightColor; + display: inline; + } + + &.center { + text-align: center; + } + } + + &__title { + margin-bottom: 24px; + font-weight: 500; + font-size: 32px; + line-height: 32px; + + color: #1d2129; + } + + &__share { + .button(@height: 72px; @width: 384px; @fontSize: 28px; @fontWeight: 400; @borderRadius: 44px; @highlight: 0); + margin-top: 32px; + margin-left: auto; + margin-right: auto; + } + &__lined-wrapper { + text-align: center; + margin-top: 48px; + margin-bottom: 24px; + } + &__lined-title { + text-align: center; + font-weight: 400; + font-size: 28px; + line-height: 40px; + color: #333333; + position: relative; + display: inline-block; + + &:before { + content: ''; + position: absolute; + left: -68px; + width: 56px; + height: 1px; + background: #ccc; + top: 50%; + } + + &:after { + content: ''; + position: absolute; + right: -68px; + width: 56px; + height: 1px; + background: #ccc; + top: 50%; + } + } + &__city-select { + background: #F7F7F7; + border-radius: 16px; + padding: 34px 32px; + font-weight: 400; + font-size: 32px; + line-height: 32px; + color: #333333; + display: flex; + justify-content: space-between; + } + &__qrcode { + width: 280px; + height: 280px; + background: #6F7686; + margin: auto auto 24px; + } +} diff --git a/src/pages/group-owner-certification/index.tsx b/src/pages/group-owner-certification/index.tsx index 6a4eb36..d4bd3b3 100644 --- a/src/pages/group-owner-certification/index.tsx +++ b/src/pages/group-owner-certification/index.tsx @@ -1,26 +1,123 @@ +import { Button, Image } from '@tarojs/components'; +import Taro, { useShareAppMessage } from '@tarojs/taro'; + import { Tabs } from '@taroify/core'; +import { Arrow } from '@taroify/icons'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import GroupCertificationList from '@/components/group-certification-list'; +import { EventName, OpenSource, PageUrl } from '@/constants/app'; +import { CITY_CODE_TO_NAME_MAP } from '@/constants/city'; +import useInviteCode from '@/hooks/use-invite-code'; +import useLocation from '@/hooks/use-location'; +import { StaffInfo } from '@/types/partner'; +import { generateGroupAuthCode, getStaffInfo } from '@/utils/partner'; +import { navigateTo } from '@/utils/route'; +import { getCommonShareMessage } from '@/utils/share'; import './index.less'; const PREFIX = 'group-owner-certification'; export default function GroupOwnerCertification() { - return; -
- - -
-
-
第一步:拉运营入群
-
-
-
拉播络城市运营进入你的带货主播群,
-
请确认你是群主且是带货主播群,不然不要拉
+ const location = useLocation(); + const inviteCode = useInviteCode(); + const [cityCode, setCityCode] = useState(location.cityCode); + const cityValuesChangedRef = useRef(false); + Taro.showShareMenu({ + withShareTicket: true, + }); + useShareAppMessage(async () => { + const { authCode } = await generateGroupAuthCode(); + return getCommonShareMessage({ + useCapture: false, + title: `群主测试,${authCode}`, + inviteCode, + params: { authCode }, + path: PageUrl.GroupOwnerCertificate, + imageUrl: 'https://publiccdn.neighbourhood.com.cn/img/share-group-owner-certificate.png', + }); + }); + + const handleClickCityMenu = useCallback(() => { + navigateTo(PageUrl.CitySearch, { city: cityCode, source: OpenSource.GroupOwnerCertificate }); + }, [cityCode]); + + const handleCityChange = useCallback(data => { + console.log('handleCityChange', data); + const { openSource, cityCode: cCode } = data; + if (openSource !== OpenSource.GroupOwnerCertificate) { + return; + } + cityValuesChangedRef.current = true; + setCityCode(cCode); + }, []); + + useEffect(() => { + if (cityValuesChangedRef.current) { + return; + } + setCityCode(location.cityCode); + }, [location]); + useEffect(() => { + Taro.eventCenter.on(EventName.SELECT_CITY, handleCityChange); + return () => { + Taro.eventCenter.off(EventName.SELECT_CITY, handleCityChange); + }; + }, [handleCityChange]); + + const [staffInfo, setStaffInfo] = useState(null); + useEffect(() => { + getStaffInfo(cityCode) + .then(data => { + setStaffInfo(data); + }) + .catch(() => { + setStaffInfo(null); + }); + }, [cityCode]); + + return ( +
+ + +
+
+
第一步:拉运营入群
+
+
+
拉播络城市运营进入你的带货主播群,
+
请确认你是群主且是带货主播群,不然不要拉
+
+
+
选择城市,添加运营
+
+
+ {CITY_CODE_TO_NAME_MAP.get(cityCode)} + +
+
+
长按并识别二维码添加运营
+
+ {staffInfo && } +
+
第二步
+
+
点击以下按钮分享小程序到群,并在群里打开小程序
+
+
1次分享只能认证一个群,有多个群请分享多次,不可在微信聊天里直接转发;
+ 一般1天内完成,超时未完成认证请重新分享或者咨询运营 +
+
-
- - - -
; + + + + + +
+ ); } diff --git a/src/pages/group-v2/index.tsx b/src/pages/group-v2/index.tsx index 9044a36..863d183 100644 --- a/src/pages/group-v2/index.tsx +++ b/src/pages/group-v2/index.tsx @@ -5,8 +5,8 @@ import { useCallback } from 'react'; import HomePage from '@/components/home-page'; import SearchCity from '@/components/search-city'; import { PageType, PageUrl, RoleType } from '@/constants/app'; -import { GROUPS } from '@/constants/group'; import useInviteCode from '@/hooks/use-invite-code'; +import useServiceUrls from '@/hooks/use-service-urls'; import { switchRoleType } from '@/utils/app'; import { openCustomerServiceChat } from '@/utils/common'; import { getCurrentCityCode } from '@/utils/location'; @@ -20,6 +20,7 @@ const PREFIX = 'group-v2-page'; export default function GroupV2() { const inviteCode = useInviteCode(); + const serviceUrls = useServiceUrls(); useLoad(() => { switchRoleType(RoleType.Anchor); @@ -32,15 +33,18 @@ export default function GroupV2() { getCommonShareMessage({ inviteCode, title: '邀请你加入本地主播求职招聘群', path: PageUrl.GroupV2 }) ); - const handleSelectCity = useCallback(cityCode => { - if (!checkCityCode(cityCode)) { - return; - } - const group = GROUPS.find(g => String(g.cityCode) === cityCode); - if (group) { - openCustomerServiceChat(group.serviceUrl); - } - }, []); + const handleSelectCity = useCallback( + cityCode => { + if (!checkCityCode(cityCode)) { + return; + } + const group = serviceUrls.find(g => String(g.cityCode) === cityCode); + if (group) { + openCustomerServiceChat(group.serviceUrl); + } + }, + [serviceUrls] + ); return ( diff --git a/src/pages/user-batch-publish/index.tsx b/src/pages/user-batch-publish/index.tsx index f4e2d8c..551b875 100644 --- a/src/pages/user-batch-publish/index.tsx +++ b/src/pages/user-batch-publish/index.tsx @@ -7,8 +7,8 @@ import { useCallback, useState } from 'react'; import HomePage from '@/components/home-page'; import SearchCity from '@/components/search-city'; import { PageType, PageUrl, RoleType } from '@/constants/app'; -import { GROUPS } from '@/constants/group'; import useInviteCode from '@/hooks/use-invite-code'; +import useServiceUrls from '@/hooks/use-service-urls'; import { switchRoleType } from '@/utils/app'; import { openCustomerServiceChat } from '@/utils/common'; import { getCurrentCityCode } from '@/utils/location'; @@ -23,6 +23,7 @@ const EXAMPLE_IMAGE = 'https://publiccdn.neighbourhood.com.cn/img/delegate-examp const COMMENT_IMAGE = 'https://publiccdn.neighbourhood.com.cn/img/delegate-comments.png'; export default function BizService() { const inviteCode = useInviteCode(); + const serviceUrls = useServiceUrls(); const [value, setValue] = useState('0'); const handleClickDelegate = useCallback(() => { @@ -37,15 +38,18 @@ export default function BizService() { const handleOpenService = useCallback(() => { openCustomerServiceChat('https://work.weixin.qq.com/kfid/kfcd60708731367168d'); }, []); - const handleSelectCity = useCallback(cityCode => { - if (!checkCityCode(cityCode)) { - return; - } - const group = GROUPS.find(g => String(g.cityCode) === cityCode); - if (group) { - openCustomerServiceChat(group.serviceUrl); - } - }, []); + const handleSelectCity = useCallback( + cityCode => { + if (!checkCityCode(cityCode)) { + return; + } + const group = serviceUrls.find(g => String(g.cityCode) === cityCode); + if (group) { + openCustomerServiceChat(group.serviceUrl); + } + }, + [serviceUrls] + ); const handleChange = useCallback(v => { setValue(v); }, []); diff --git a/src/store/actions/app.ts b/src/store/actions/app.ts index cec2fcd..25eee2f 100644 --- a/src/store/actions/app.ts +++ b/src/store/actions/app.ts @@ -1,10 +1,13 @@ import { RoleType, PageType } from '@/constants/app'; import { LocationInfo } from '@/types/location'; +import { AppState } from '@/types/store'; -import { CHANGE_ROLE_TYPE, CHANGE_HOME_PAGE, SET_LOCATION_INFO } from '../constants'; +import { CHANGE_ROLE_TYPE, CHANGE_HOME_PAGE, SET_LOCATION_INFO, SET_SERVICE_URLS } from '../constants'; export const changeRoleType = (value: RoleType) => ({ type: CHANGE_ROLE_TYPE, value }); export const changeHomePage = (value: PageType) => ({ type: CHANGE_HOME_PAGE, value }); export const setLocationInfo = (value: LocationInfo) => ({ type: SET_LOCATION_INFO, value }); + +export const setServiceUrls = (value: AppState['serviceUrls']) => ({ type: SET_SERVICE_URLS, value }); diff --git a/src/store/constants.ts b/src/store/constants.ts index 2e94005..953d96f 100644 --- a/src/store/constants.ts +++ b/src/store/constants.ts @@ -6,3 +6,4 @@ export const SET_BIND_PHONE = 'SET_BIND_PHONE'; export const SET_USER_MESSAGE = 'SET_USER_MESSAGE'; export const SET_INVITE_CODE = 'SET_INVITE_CODE'; export const SET_JOB_ID = 'SET_JOB_ID'; +export const SET_SERVICE_URLS = 'SET_SERVICE_URLS'; diff --git a/src/store/reducers/app.ts b/src/store/reducers/app.ts index 158157b..3b6ea79 100644 --- a/src/store/reducers/app.ts +++ b/src/store/reducers/app.ts @@ -7,7 +7,7 @@ import { CacheKey } from '@/constants/cache-key'; import { LocationInfo } from '@/types/location'; import { AppState } from '@/types/store'; -import { CHANGE_ROLE_TYPE, CHANGE_HOME_PAGE, SET_LOCATION_INFO } from '../constants'; +import { CHANGE_ROLE_TYPE, CHANGE_HOME_PAGE, SET_LOCATION_INFO, SET_SERVICE_URLS } from '../constants'; const DEFAULT_LOCATION: LocationInfo = { provinceCode: '440000', @@ -23,6 +23,7 @@ const INIT_STATE: AppState = { roleType: defaultAppMode, homePageType: defaultAppMode === RoleType.Company ? PageType.Anchor : PageType.JOB, location: Taro.getStorageSync(CacheKey.CACHE_LOCATION_INFO) || DEFAULT_LOCATION, + serviceUrls: [], }; const appState = (state: AppState = INIT_STATE, action: Action): AppState => { @@ -33,6 +34,8 @@ const appState = (state: AppState = INIT_STATE, action: Action): AppState => { return { ...state, roleType: value }; case CHANGE_HOME_PAGE: return { ...state, homePageType: value }; + case SET_SERVICE_URLS: + return { ...state, serviceUrls: value }; case SET_LOCATION_INFO: Taro.setStorageSync(CacheKey.CACHE_LOCATION_INFO, value); return { ...state, location: value }; diff --git a/src/store/selector/app.ts b/src/store/selector/app.ts index f6645f3..b6be4d1 100644 --- a/src/store/selector/app.ts +++ b/src/store/selector/app.ts @@ -5,3 +5,5 @@ export const selectRoleType = (state: IState) => state.appState.roleType; export const selectHomePageType = (state: IState) => state.appState.homePageType; export const selectLocation = (state: IState) => state.appState.location; + +export const selectServiceUrls = (state: IState) => state.appState.serviceUrls || {}; diff --git a/src/types/location.ts b/src/types/location.ts index aeea0ae..3827722 100644 --- a/src/types/location.ts +++ b/src/types/location.ts @@ -20,3 +20,14 @@ export interface GetCityCodeRequest { latitude: number; // 纬度,浮点数,范围为-90~90,负数表示南纬 longitude: number; // 经度,范围为-180~180,负数表示西经 } + +export interface CityOperatorListItem { + id: number; + staffId: number; + staffName: string; + cityName: string; + cityCode: string; + groupLink: string; + created: string; + updated: string; +} diff --git a/src/types/partner.ts b/src/types/partner.ts index 36ab9bd..f261e19 100644 --- a/src/types/partner.ts +++ b/src/types/partner.ts @@ -87,3 +87,41 @@ export interface PartnerPagination { content: T[]; totalPages: number; } +export enum EarnType { + ORDER_PAYMENT_SHARE_L1 = 'ORDER_PAYMENT_SHARE_L1', + ORDER_PAYMENT_SHARE_L2 = 'ORDER_PAYMENT_SHARE_L2', + CHAT_ACTIVITY_SHARE_L1 = 'CHAT_ACTIVITY_SHARE_L1', + CHAT_ACTIVITY_SHARE_L2 = 'CHAT_ACTIVITY_SHARE_L2', + OTHER = 'OTHER', +} +export interface UserProfitListItem { + userId: string; + total: number; + earnType: EarnType; + amount: number; + updatedAt: string; +} +export interface GroupAuthCode { + shareUserId: string; + authCode: string; +} +export interface AuthedGroupInfo { + userId: string; + openGid: string; + groupId: string; + groupName: string; + groupAvatar: string; + authDate: string; +} +export interface DecryptOpenGidBody { + authCode?: string; + inviteCode?: string; + encryptedData: string; + iv: string; +} +export interface StaffInfo { + id: number; + staffName: string; + staffQrCode: string; + isDefault: 0 | 1; +} diff --git a/src/types/store.ts b/src/types/store.ts index 9bdae4c..c4e6582 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -17,4 +17,9 @@ export interface AppState { roleType: RoleType; homePageType: PageType; location: LocationInfo; + serviceUrls: Array<{ + title: string; + cityCode: number; + serviceUrl: string; + }>; } diff --git a/src/utils/common.ts b/src/utils/common.ts index 499f237..f6e881c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,5 +1,7 @@ import Taro from '@tarojs/taro'; +import { API } from '@/http/api'; + export const isDev = () => process.env.NODE_ENV === 'development'; // export const isDev = () => true; @@ -13,7 +15,6 @@ export const isDesktop = (() => { return info.platform === 'windows' || info.platform === 'mac'; })(); - export const logWithPrefix = isDev() ? (prefix: string) => (...args: BL.Anything[]) => @@ -87,3 +88,9 @@ export const isValidIdCard = (idCard: string) => export const isValidPhone = (phone: string) => /^1[3-9]\d{9}$/.test(phone); export const getScrollItemId = (id?: string) => (id ? `sid-${id}` : id); + +export function buildUrl(url: API, params: Record): API { + return Object.entries(params).reduce((result, [key, value]) => { + return result.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value)); + }, url) as API; +} diff --git a/src/utils/location.ts b/src/utils/location.ts index 35e2f8d..9885f14 100644 --- a/src/utils/location.ts +++ b/src/utils/location.ts @@ -6,9 +6,9 @@ import { CITY_CODE_TO_NAME_MAP, COUNTY_CODE_TO_NAME_MAP, PROVINCE_CODE_TO_NAME_M import http from '@/http'; import { API } from '@/http/api'; import store from '@/store'; -import { setLocationInfo } from '@/store/actions'; +import { setLocationInfo, setServiceUrls } from '@/store/actions'; import { selectLocation } from '@/store/selector'; -import { GetCityCodeRequest, LocationInfo } from '@/types/location'; +import { CityOperatorListItem, GetCityCodeRequest, LocationInfo } from '@/types/location'; import { authorize, getWxSetting } from './wx'; @@ -134,3 +134,16 @@ export async function requestLocation(force: boolean = false) { store.dispatch(setLocationInfo(location)); return location; } + +export async function requestServiceUrls() { + const list = await http.post(API.GET_ALL_CITY_OPERATOR); + store.dispatch( + setServiceUrls( + (list || []).map(it => ({ + title: `【${it.cityName}】`, + cityCode: Number(it.cityCode), + serviceUrl: it.groupLink, + })) + ) + ); +} diff --git a/src/utils/partner.ts b/src/utils/partner.ts index 047831f..170c7de 100644 --- a/src/utils/partner.ts +++ b/src/utils/partner.ts @@ -7,16 +7,22 @@ import store from '@/store'; import { setInviteCode } from '@/store/actions/partner'; import { IPaginationRequest } from '@/types/common'; import { + AuthedGroupInfo, + DecryptOpenGidBody, GetProfitRequest, + GroupAuthCode, InviteUserInfo, PartnerInviteCode, PartnerPagination, PartnerProfitItem, PartnerProfitsState, + StaffInfo, + UserProfitListItem, WithdrawRecord, WithdrawResponse, } from '@/types/partner'; import { requestUserInfo } from '@/utils/user'; +import { buildUrl } from '@/utils/common'; export const getInviteCodeFromQuery = (query: Record): string | undefined => { if (query) { @@ -82,7 +88,7 @@ export const formatMoney = (cents: number) => { const yuan = cents / 100; return yuan.toFixed(2); }; -export function formatTimestamp(timestamp: string): string { +export function formatTimestamp(timestamp: string, dateOnly?: boolean): string { // 创建 Date 对象 const date = new Date(/^\d+$/.test(timestamp) ? Number(timestamp) : timestamp); @@ -94,7 +100,7 @@ export function formatTimestamp(timestamp: string): string { const mm = String(date.getMinutes()).padStart(2, '0'); // 拼接成所需的格式 - return `${YYYY}.${MM}.${DD} ${HH}:${mm}`; + return dateOnly ? `${YYYY}.${MM}.${DD}` : `${YYYY}.${MM}.${DD} ${HH}:${mm}`; } export function formatUserId(input: string): string { @@ -127,3 +133,24 @@ export async function getWithdrawList(data: IPaginationRequest) { contentType: 'application/x-www-form-urlencoded', }); } +export async function getLastProfitList() { + const result = await http.get(API.GET_PROFIT_LIST); + return Array.isArray(result) ? result : []; +} + +export async function generateGroupAuthCode() { + return await http.get(API.GENERATE_GROUP_AUTH_CODE); +} + +export async function getAuthedGroupList() { + return await http.get(API.GET_AUTHED_GROUP_LIST); +} +export async function decryptOpenGid(data: DecryptOpenGidBody) { + return await http.post(API.DECRYPT_OPEN_GID, { + data, + }); +} +export async function getStaffInfo(cityCode: string) { + const result = await http.post(buildUrl(API.GET_STAFF_CODE, { cityCode })); + return Array.isArray(result) && result.length ? result[0] : null; +}