6 Commits

Author SHA1 Message Date
6d76c82e96 feat: update 2025-09-03 23:50:03 +08:00
087e05a9a0 feat: update 2025-09-01 18:43:49 +08:00
aea8323d95 feat: 2025-08-31 22:43:00 +08:00
61686950bd feat: update 2025-08-28 14:47:02 +08:00
eea89263f2 Merge branch 'trunk' into feat/share-coupon 2025-08-28 14:45:50 +08:00
285db3f445 feat: wenan update 2025-08-28 14:40:48 +08:00
11 changed files with 174 additions and 60 deletions

View File

@ -1,19 +1,19 @@
import { Button, Canvas } from '@tarojs/components'; import { Button, Canvas } from '@tarojs/components';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { useCallback, useState } from 'react'; import { useCallback } from 'react';
import { openCustomerServiceChat } from '@/utils/common'; import { openCustomerServiceChat } from '@/utils/common';
import { getPartnerQrcode } from '@/utils/partner'; import { getCouponQrCode, generateMembershipCoupon } from '@/utils/coupon';
import './index.less'; import './index.less';
const PREFIX = 'partner-intro'; const PREFIX = 'partner-intro';
export default function PartnerIntro() { export default function PartnerIntro() {
const [posterPath, setPosterPath] = useState('');
const getQrcode = async () => { const getQrcode = async () => {
try { try {
const data = await getPartnerQrcode(); // 假设 getPartnerQrcode 返回 ArrayBuffer const { code } = await generateMembershipCoupon();
const data = await getCouponQrCode(code);
const base64 = Taro.arrayBufferToBase64(data); const base64 = Taro.arrayBufferToBase64(data);
return `data:image/png;base64,${base64}`; return `data:image/png;base64,${base64}`;
} catch (error) { } catch (error) {
@ -39,8 +39,6 @@ export default function PartnerIntro() {
fileType: 'jpg', fileType: 'jpg',
}); });
setPosterPath(tempFilePath.tempFilePath);
resolve(tempFilePath.tempFilePath); resolve(tempFilePath.tempFilePath);
}); });
} catch (error) { } catch (error) {
@ -57,8 +55,8 @@ export default function PartnerIntro() {
const canvas = res[0].node; const canvas = res[0].node;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
canvas.width = 550*2; canvas.width = 550 * 2;
canvas.height = 918*2; canvas.height = 918 * 2;
ctx.scale(2, 2); ctx.scale(2, 2);
// 绘制背景图片 // 绘制背景图片
@ -84,13 +82,10 @@ export default function PartnerIntro() {
}); });
}; };
const savePoster = async () => { const savePoster = async () => {
let filePath = posterPath; Taro.showLoading({ title: '正在生成海报' });
if (!filePath) { const qrCode = await getQrcode();
Taro.showLoading({ title: '正在生成海报' }); const filePath = await drawCanvas(qrCode);
const qrCode = await getQrcode(); Taro.hideLoading();
filePath = await drawCanvas(qrCode);
Taro.hideLoading();
}
const res = await Taro.getSetting(); const res = await Taro.getSetting();
const hasPermission = res.authSetting['scope.writePhotosAlbum']; const hasPermission = res.authSetting['scope.writePhotosAlbum'];
@ -144,7 +139,9 @@ export default function PartnerIntro() {
<div className={`${PREFIX}__block`}> <div className={`${PREFIX}__block`}>
<div className={`${PREFIX}__title`}></div> <div className={`${PREFIX}__title`}></div>
<div className={`${PREFIX}__card`}> <div className={`${PREFIX}__card`}>
<div className={`${PREFIX}__body`}></div> <div className={`${PREFIX}__body`}>
</div>
</div> </div>
</div> </div>
<div className={`${PREFIX}__block`}> <div className={`${PREFIX}__block`}>

View File

@ -36,7 +36,7 @@ interface CityOption extends ISelectOption<CityValue> {
const PREFIX = 'user-batch-publish'; const PREFIX = 'user-batch-publish';
const log = logWithPrefix(PREFIX); const log = logWithPrefix(PREFIX);
const SERVICE_ILLUSTRATE = `服务方式:帮您把招聘需求发到众多同城合作主播群 const SERVICE_ILLUSTRATE = `服务方式:帮您把招聘需求发到众多同城合作主播群
群发次数:每日1次连发3天 群发次数:杭州、广州发3次其他城市1次
内容要求:仅限带货主播招聘需求,其他不发 内容要求:仅限带货主播招聘需求,其他不发
主播联系:内容中留招聘方联系方式,主播直接联系`; 主播联系:内容中留招聘方联系方式,主播直接联系`;
const cityValues: CityValue[] = [ const cityValues: CityValue[] = [

View File

@ -1,4 +1,4 @@
// export const DOMAIN = 'http://192.168.60.191:8082'; // export const DOMAIN = 'http://192.168.60.116:8082';
export const DOMAIN = process.env.BRAND === 'dev' ? 'https://dev.neighbourhood.cn' : 'https://neighbourhood.cn'; export const DOMAIN = process.env.BRAND === 'dev' ? 'https://dev.neighbourhood.cn' : 'https://neighbourhood.cn';
// export const DOMAIN = 'https://dev.neighbourhood.cn'; // export const DOMAIN = 'https://dev.neighbourhood.cn';
export const BASE_URL = `${DOMAIN}/api`; export const BASE_URL = `${DOMAIN}/api`;
@ -85,4 +85,7 @@ export enum API {
GET_PROFIT_STAT = '/user/profits', GET_PROFIT_STAT = '/user/profits',
WITHDRAW = '/user/withdraw', WITHDRAW = '/user/withdraw',
GET_WITHDRAW_LIST = '/user/withdraw/records', GET_WITHDRAW_LIST = '/user/withdraw/records',
GENERATE_MEMBERSHIP_COUPON = '/coupon/membership/generate',
CLAIM_MEMBERSHIP_COUPON = '/coupon/membership/claim',
GET_VIP_QRCODE = '/user/getVipQrCode',
} }

View File

@ -1,50 +1,82 @@
import { Image, Button } from '@tarojs/components'; import { Image, Button } from '@tarojs/components';
import { useLoad } from '@tarojs/taro';
import { Fragment, useCallback } from 'react'; import { Fragment, useCallback, useState } from 'react';
import SafeBottomPadding from '@/components/safe-bottom-padding'; import SafeBottomPadding from '@/components/safe-bottom-padding';
import { PageUrl } from '@/constants/app'; import { PageUrl } from '@/constants/app';
import { logWithPrefix } from '@/utils/common'; import { logWithPrefix } from '@/utils/common';
import { switchTab } from '@/utils/route'; import { claimMembershipCoupon, getCouponCodeFromQuery } from '@/utils/coupon';
import { getPageQuery, switchTab } from '@/utils/route';
import { formatTime } from '@/utils/time';
import './index.less'; import './index.less';
const PREFIX = 'give-vip'; const PREFIX = 'give-vip';
const log = logWithPrefix(PREFIX); const log = logWithPrefix(PREFIX);
enum State {
PENDING,
SUCCESS,
FAILED,
}
export default function GiveVip() { export default function GiveVip() {
const [state, setState] = useState<State>(State.PENDING);
const [expireAt, setExpireAt] = useState('');
const [usedBefore, setUsedBefore] = useState(false);
const handleNavigate = useCallback(() => { const handleNavigate = useCallback(() => {
switchTab(PageUrl.Job); switchTab(PageUrl.Job);
}, []); }, []);
useLoad(() => {
const query = getPageQuery<{ d: string }>();
const code = getCouponCodeFromQuery(query);
claimMembershipCoupon(code!)
.then(res => {
setUsedBefore(res.usedBefore);
setExpireAt(res.usageExpireAt);
setState(Date.now() > new Date(res.usageExpireAt).getTime() ? State.FAILED : State.SUCCESS);
})
.catch(err => {
console.error(err);
setState(State.FAILED);
});
});
return ( return (
<div className={PREFIX}> <div className={PREFIX}>
<Fragment> {state === State.SUCCESS && (
<div className={`${PREFIX}__title`}> <Fragment>
<Image src="https://publiccdn.neighbourhood.com.cn/img/partner-yes.svg" className={`${PREFIX}__icon`} /> <div className={`${PREFIX}__title`}>
<div></div> <Image src="https://publiccdn.neighbourhood.com.cn/img/partner-yes.svg" className={`${PREFIX}__icon`} />
</div> <div>{usedBefore ? '宝子,你已经领过了,下周再来' : '宝子,播络会员领取成功'}</div>
<div className={`${PREFIX}__coupon-info`}>
<Image src="https://publiccdn.neighbourhood.com.cn/img/parnet-share-bg.png" className={`${PREFIX}__bg`} />
<div className={`${PREFIX}__coupon-title`}></div>
<div className={`${PREFIX}__coupon-intro`}>
<div className="highlight">10</div>
</div> </div>
<div className={`${PREFIX}__coupon-valid`}>2025/8/26 22:23:56</div> <div className={`${PREFIX}__coupon-info`}>
</div> <Image src="https://publiccdn.neighbourhood.com.cn/img/parnet-share-bg.png" className={`${PREFIX}__bg`} />
</Fragment> <div className={`${PREFIX}__coupon-title`}></div>
<Fragment> <div className={`${PREFIX}__coupon-intro`}>
<div className={`${PREFIX}__hint`}> <div className="highlight">10</div>
<Image </div>
src="https://publiccdn.neighbourhood.com.cn/img/link-invalid.svg" <div className={`${PREFIX}__coupon-valid`}>
mode="aspectFit" {formatTime(expireAt, 'YYYY/MM/DD HH:mm:ss', false)}
className={`${PREFIX}__icon`} </div>
/> </div>
<div className={`${PREFIX}__hint-title`}></div> </Fragment>
</div> )}
<div className={`${PREFIX}__title`}> {state === State.FAILED && (
<div className="highlight"></div> <Fragment>
</div> <div className={`${PREFIX}__hint`}>
</Fragment> <Image
src="https://publiccdn.neighbourhood.com.cn/img/link-invalid.svg"
mode="aspectFit"
className={`${PREFIX}__icon`}
/>
<div className={`${PREFIX}__hint-title`}></div>
</div>
<div className={`${PREFIX}__title`}>
<div className="highlight"></div>
</div>
</Fragment>
)}
<div className={`${PREFIX}__info-block`}> <div className={`${PREFIX}__info-block`}>
<div className={`${PREFIX}__info-title`}></div> <div className={`${PREFIX}__info-title`}></div>
<div className={`${PREFIX}__info-card`}> <div className={`${PREFIX}__info-card`}>

View File

@ -56,7 +56,27 @@
&__icon { &__icon {
width: 48px; width: 48px;
height: 48px; height: 48px;
}
&__block {
margin-top: 24px; margin-top: 24px;
.flex-row();
gap: 16px;
}
&__status {
font-size: 28px;
line-height: 32px;
font-weight: 400;
color: @blColorG1;
&.open {
color: @blHighlightColor;
}
&.error {
color: #FF5051;
}
} }
} }
} }

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { EventName, OpenSource } from '@/constants/app'; import { EventName, OpenSource } from '@/constants/app';
import { JOB_MANAGE_STATUS_TITLE_MAP, JobManageStatus } from '@/constants/job';
import { JobManageInfo } from '@/types/job'; import { JobManageInfo } from '@/types/job';
import { getJobLocation, requestJobManageList } from '@/utils/job'; import { getJobLocation, requestJobManageList } from '@/utils/job';
import { getPageQuery, navigateBack } from '@/utils/route'; import { getPageQuery, navigateBack } from '@/utils/route';
@ -15,7 +16,14 @@ import Toast from '@/utils/toast';
import './index.less'; import './index.less';
const PREFIX = 'page-job-select-my-publish'; const PREFIX = 'page-job-select-my-publish';
const STATUS_CLASS_MAP = {
[JobManageStatus.WaitVerify]: 'pending',
[JobManageStatus.Open]: 'open',
[JobManageStatus.Pending]: 'pending',
[JobManageStatus.Error]: 'error',
[JobManageStatus.Close]: 'close',
[JobManageStatus.Expire]: 'close',
};
export default function JobSelectMyPublish() { export default function JobSelectMyPublish() {
const [list, setList] = useState<JobManageInfo[]>([]); const [list, setList] = useState<JobManageInfo[]>([]);
const [jobId, setJobId] = useState<string | null>('1'); const [jobId, setJobId] = useState<string | null>('1');
@ -57,13 +65,18 @@ export default function JobSelectMyPublish() {
</div> </div>
<div className={`${PREFIX}__right`}> <div className={`${PREFIX}__right`}>
<div className={`${PREFIX}__right__time`}>{formatTime(item.updated)}</div> <div className={`${PREFIX}__right__time`}>{formatTime(item.updated)}</div>
{item.id === jobId && ( <div className={`${PREFIX}__right__block`}>
<Image <div className={classNames(`${PREFIX}__right__status`, { [STATUS_CLASS_MAP[item.status]]: true })}>
mode="aspectFit" {JOB_MANAGE_STATUS_TITLE_MAP[item.status]}
className={`${PREFIX}__right__icon`} </div>
src={require('@/statics/svg/success.svg')} {item.id === jobId && (
/> <Image
)} mode="aspectFit"
className={`${PREFIX}__right__icon`}
src={require('@/statics/svg/success.svg')}
/>
)}
</div>
</div> </div>
</div> </div>
))} ))}

View File

@ -6,7 +6,9 @@ import { useState } from 'react';
import PartnerIntro from '@/components/partner-intro'; import PartnerIntro from '@/components/partner-intro';
import PartnerInviteList from '@/components/partner-invite-list'; import PartnerInviteList from '@/components/partner-invite-list';
import PartnerProfit from '@/components/partner-profit'; import PartnerProfit from '@/components/partner-profit';
import { PageUrl } from '@/constants/app';
import useInviteCode from '@/hooks/use-invite-code'; import useInviteCode from '@/hooks/use-invite-code';
import { generateMembershipCoupon } from '@/utils/coupon';
import { getCommonShareMessage } from '@/utils/share'; import { getCommonShareMessage } from '@/utils/share';
import './index.less'; import './index.less';
@ -18,12 +20,15 @@ export default function Partner() {
const handleChange = v => { const handleChange = v => {
setTab(v); setTab(v);
}; };
useShareAppMessage(() => { useShareAppMessage(async () => {
console.log('Partner inviteCode', inviteCode); console.log('Partner inviteCode', inviteCode);
const { code } = await generateMembershipCoupon();
return getCommonShareMessage({ return getCommonShareMessage({
useCapture: false, useCapture: false,
inviteCode, inviteCode,
title: '宝子,送你个播络会员,找工作更方便', title: '宝子,送你个播络会员,找工作更方便',
path: PageUrl.GiveVip,
params: { d: code },
imageUrl: 'https://publiccdn.neighbourhood.com.cn/img/share-coupon.png', imageUrl: 'https://publiccdn.neighbourhood.com.cn/img/share-coupon.png',
}); });
}); });

View File

@ -82,7 +82,7 @@ export default function BizService() {
<div className={`${PREFIX}__delegate-h5`}></div> <div className={`${PREFIX}__delegate-h5`}></div>
<div className={`${PREFIX}__delegate-card`}> <div className={`${PREFIX}__delegate-card`}>
<div className={`${PREFIX}__delegate-body`}></div> <div className={`${PREFIX}__delegate-body`}></div>
<div className={`${PREFIX}__delegate-body`}>13</div> <div className={`${PREFIX}__delegate-body`}>广31</div>
<div className={`${PREFIX}__delegate-body`}></div> <div className={`${PREFIX}__delegate-body`}></div>
<div className={`${PREFIX}__delegate-body`}></div> <div className={`${PREFIX}__delegate-body`}></div>
</div> </div>

11
src/types/coupon.ts Normal file
View File

@ -0,0 +1,11 @@
export type ClaimMembershipCouponResponse = {
code: string;
usedBefore: boolean;
usageExpireAt: string;
};
export type GenerateMembershipCouponResponse = {
code: string;
linkExpireAt: string;
shareUrl: string;
};

31
src/utils/coupon.ts Normal file
View File

@ -0,0 +1,31 @@
import http from '@/http';
import { API } from '@/http/api';
import { ClaimMembershipCouponResponse, GenerateMembershipCouponResponse } from '@/types/coupon';
export const generateMembershipCoupon = async () => {
return await http.post<GenerateMembershipCouponResponse>(API.GENERATE_MEMBERSHIP_COUPON);
};
export const claimMembershipCoupon = async (code: string) => {
return await http.post<ClaimMembershipCouponResponse>(API.CLAIM_MEMBERSHIP_COUPON, {
contentType: 'application/x-www-form-urlencoded',
data: { code },
});
};
export const getCouponQrCode = async (code: string) => {
return await http.post(API.GET_VIP_QRCODE, {
contentType: 'application/x-www-form-urlencoded',
responseType: 'arraybuffer',
data: { code },
});
};
export const getCouponCodeFromQuery = (query: Record<string, string>): string | undefined => {
if (query) {
if (query.scene) {
return query.scene.replace('d%3D', '');
}
if (query.d) {
return query.d;
}
}
return undefined;
};

View File

@ -1,10 +1,12 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export function formatTime(time: number | string, template = 'YYYY-MM-DD'): string { export function formatTime(time: number | string, template = 'YYYY-MM-DD', toNum = true): string {
if (!time) { if (!time) {
return ''; return '';
} }
time = Number(time); if (toNum) {
time = Number(time);
}
return dayjs(time).format(template); return dayjs(time).format(template);
} }