feat: first commit
This commit is contained in:
46
src/utils/app.ts
Normal file
46
src/utils/app.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { RoleType, PageUrl } from '@/constants/app';
|
||||
import { CollectEventName } from '@/constants/event';
|
||||
import { ANCHOR_TAB_LIST, COMPANY_TAB_LIST } from '@/hooks/use-config';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import store from '@/store';
|
||||
import { changeRoleType, changeHomePage } from '@/store/actions';
|
||||
import { selectRoleType } from '@/store/selector';
|
||||
import { sleep } from '@/utils/common';
|
||||
import { collectEvent } from '@/utils/event';
|
||||
import { switchTab } from '@/utils/route';
|
||||
import Toast from '@/utils/toast';
|
||||
|
||||
const postSwitchRoleType = (appMode: RoleType) => {
|
||||
const data = { roleType: appMode };
|
||||
return http.post(API.APP_MODE_SWITCH, { data });
|
||||
};
|
||||
|
||||
export const getRoleType = () => selectRoleType(store.getState());
|
||||
|
||||
export const isAnchorMode = () => getRoleType() === RoleType.Anchor;
|
||||
|
||||
export const isCompanyMode = () => getRoleType() === RoleType.Company;
|
||||
|
||||
export const switchDefaultTab = async () => {
|
||||
await sleep(1);
|
||||
const mode = getRoleType();
|
||||
const tabList = mode === RoleType.Anchor ? ANCHOR_TAB_LIST : COMPANY_TAB_LIST;
|
||||
const item = tabList[0];
|
||||
store.dispatch(changeHomePage(item.type));
|
||||
switchTab(item.pagePath as PageUrl);
|
||||
};
|
||||
|
||||
export const switchRoleType = async (appMode?: RoleType) => {
|
||||
if (!appMode) {
|
||||
const curMode = getRoleType();
|
||||
appMode = curMode === RoleType.Anchor ? RoleType.Company : RoleType.Anchor;
|
||||
}
|
||||
try {
|
||||
await postSwitchRoleType(appMode);
|
||||
store.dispatch(changeRoleType(appMode));
|
||||
} catch (e) {
|
||||
collectEvent(CollectEventName.SWITCH_APP_MODE_FAILED);
|
||||
Toast.error('切换失败请重试');
|
||||
}
|
||||
};
|
||||
88
src/utils/common.ts
Normal file
88
src/utils/common.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
export const isDev = () => process.env.NODE_ENV === 'development';
|
||||
|
||||
export const isIPhone = (() => {
|
||||
const info = Taro.getSystemInfoSync();
|
||||
return info.platform === 'ios';
|
||||
})();
|
||||
|
||||
export const isDesktop = (() => {
|
||||
const info = Taro.getSystemInfoSync();
|
||||
return info.platform === 'windows' || info.platform === 'mac';
|
||||
})();
|
||||
|
||||
|
||||
export const logWithPrefix = isDev()
|
||||
? (prefix: string) =>
|
||||
(...args: BL.Anything[]) =>
|
||||
console.log(`[${prefix}]`, ...args)
|
||||
: (_prefix: string) =>
|
||||
(..._args: BL.Anything[]) => {};
|
||||
|
||||
export const safeJsonParse = <T = BL.Anything>(str: string, defaultValue: BL.Anything = {}): T => {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
export const string2Number = (str: string, defaultValue: number = 0) => {
|
||||
if (!str) {
|
||||
return defaultValue;
|
||||
}
|
||||
const num = Number(str);
|
||||
return Number.isNaN(num) ? defaultValue : num;
|
||||
};
|
||||
|
||||
export const isUndefined = (v: BL.Anything) => typeof v === 'undefined';
|
||||
|
||||
export const sleep = (timeout: number = 3) => new Promise(resolve => setTimeout(resolve, timeout * 1000));
|
||||
|
||||
export const last = <T = BL.Anything>(list: T[]) => list[list.length - 1];
|
||||
|
||||
export function oncePromise<T extends BL.Anything[], R extends BL.Anything>(
|
||||
func: AsyncFunction<T, R>
|
||||
): AsyncFunction<T, R> {
|
||||
let task: Promise<R> | null = null;
|
||||
return async (...args: T): Promise<R> => {
|
||||
if (task) {
|
||||
console.log('[once promise]: has task pending');
|
||||
return task;
|
||||
}
|
||||
try {
|
||||
task = func(...args);
|
||||
const result = await task;
|
||||
return result;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
task = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const copy = (content: string): Promise<TaroGeneral.CallbackResult> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.setClipboardData({
|
||||
data: content,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const openCustomerServiceChat = (url: string = 'https://work.weixin.qq.com/kfid/kfc291d0b01ecda3088') => {
|
||||
Taro.openCustomerServiceChat({
|
||||
extInfo: { url },
|
||||
corpId: 'ww4f2f2888bf9a4f95',
|
||||
});
|
||||
};
|
||||
|
||||
export const isValidIdCard = (idCard: string) =>
|
||||
/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(idCard);
|
||||
|
||||
export const isValidPhone = (phone: string) => /^1[3-9]\d{9}$/.test(phone);
|
||||
|
||||
export const getScrollItemId = (id?: string) => (id ? `sid-${id}` : id);
|
||||
10
src/utils/company.ts
Normal file
10
src/utils/company.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import { ICertificationRequest, ICertificationResponse } from '@/types/company';
|
||||
import { requestUserInfo } from '@/utils/user';
|
||||
|
||||
export const postCertification = async (data: ICertificationRequest) => {
|
||||
const result = await http.post<ICertificationResponse>(API.CERTIFICATION, { data });
|
||||
await requestUserInfo();
|
||||
return result;
|
||||
};
|
||||
26
src/utils/event.ts
Normal file
26
src/utils/event.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { CollectEventName, ReportEventId } from '@/constants/event';
|
||||
import { logWithPrefix } from '@/utils/common';
|
||||
import { getUserId } from '@/utils/user';
|
||||
|
||||
const log = logWithPrefix('event');
|
||||
const logManager = Taro.getRealtimeLogManager();
|
||||
const deviceInfo = Taro.getDeviceInfo();
|
||||
|
||||
export const collectEvent = (eventName: CollectEventName, params: BL.Anything = {}) => {
|
||||
if (!logManager) return;
|
||||
const collectInfo = {
|
||||
...deviceInfo,
|
||||
...params,
|
||||
eventName,
|
||||
userId: getUserId(),
|
||||
};
|
||||
logManager.info(collectInfo);
|
||||
log('devEvent', collectInfo);
|
||||
};
|
||||
|
||||
export const reportEvent = (eventId: ReportEventId, params: BL.Anything = {}) => {
|
||||
Taro.reportEvent(eventId, params);
|
||||
log('reportEvent', eventId, params);
|
||||
};
|
||||
55
src/utils/group.ts
Normal file
55
src/utils/group.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { GroupType } from '@/constants/group';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import {
|
||||
BatchPublishGroup,
|
||||
GetGroupDetailsRequest,
|
||||
GetGroupsRequest,
|
||||
GetGroupsResponse,
|
||||
GroupDetail,
|
||||
SimpleGroupInfo,
|
||||
} from '@/types/group';
|
||||
import { JobDetails } from '@/types/job';
|
||||
import { getUserId } from '@/utils/user';
|
||||
|
||||
const typeToKeyMap = {
|
||||
[GroupType.All]: 'allGroups',
|
||||
[GroupType.Joined]: 'myJoinedGroups',
|
||||
[GroupType.Created]: 'myCreatedGroups',
|
||||
[GroupType.Followed]: 'myFollowedGroups',
|
||||
};
|
||||
|
||||
export const getInviteGroupText = (groupDetail: GroupDetail) => {
|
||||
return `请邀请我进群:${getUserId()}|${groupDetail.blGroupId}|${groupDetail.imGroupNick}|${groupDetail.robotId}`;
|
||||
};
|
||||
|
||||
export const getConnectCustomerServiceText = (jobDetail: JobDetails, groupDetail: GroupDetail) => {
|
||||
return `请帮我对接该通告:${getUserId()}|${groupDetail.blGroupId}|${groupDetail.robotId}|${jobDetail.publisher}|${jobDetail.id}`;
|
||||
};
|
||||
|
||||
export async function requestGroupList(data: GetGroupsRequest) {
|
||||
const type = data.type;
|
||||
const result = await http.post<GetGroupsResponse>(API.GROUPS, { data });
|
||||
const groupResults = result[typeToKeyMap[type]] || [];
|
||||
return {
|
||||
page: 1,
|
||||
pageSize: groupResults.length,
|
||||
hasMore: false,
|
||||
groupResults,
|
||||
};
|
||||
}
|
||||
|
||||
export async function requestGroupDetail(blGroupId: GetGroupDetailsRequest['blGroupId']) {
|
||||
return await http.post<GroupDetail>(API.GROUP_DETAIL, {
|
||||
data: { blGroupId },
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
}
|
||||
|
||||
export const requestBatchPublishGroups = async () => {
|
||||
return http.post<BatchPublishGroup[]>(API.BATCH_PUBLISH_GROUP_LIST);
|
||||
};
|
||||
|
||||
export const requestSimpleGroupList = async (cityCode?: string) => {
|
||||
return http.post<SimpleGroupInfo[]>(API.SIMPLE_GROUP_LIST, { data: { cityCode } });
|
||||
};
|
||||
133
src/utils/job.ts
Normal file
133
src/utils/job.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { CacheKey } from '@/constants/cache-key';
|
||||
import { CollectEventName } from '@/constants/event';
|
||||
import { EmployType, JobManageStatus, UserJobType } from '@/constants/job';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import store from '@/store';
|
||||
import { selectLocation } from '@/store/selector';
|
||||
import {
|
||||
JobDetails,
|
||||
GetJobsDetailsRequest,
|
||||
GetJobsRequest,
|
||||
GetJobsResponse,
|
||||
JobInfo,
|
||||
GetUserJobRequest,
|
||||
GetUserJobResponse,
|
||||
MyDeclaredJobInfo,
|
||||
MyBrowsedJobInfo,
|
||||
GetMyRecommendJobRequest,
|
||||
GetJobManagesRequest,
|
||||
GetJobManagesResponse,
|
||||
CreateJobInfo,
|
||||
JobManageInfo,
|
||||
} from '@/types/job';
|
||||
import { collectEvent } from '@/utils/event';
|
||||
import { getCityValues } from '@/utils/location';
|
||||
|
||||
export const isFullTimePriceRequired = (employType?: JobDetails['employType']) => {
|
||||
return employType === EmployType.Full || employType === EmployType.All
|
||||
}
|
||||
|
||||
export const isPartTimePriceRequired = (employType?: JobDetails['employType']) => {
|
||||
return employType === EmployType.Part || employType === EmployType.All
|
||||
}
|
||||
|
||||
export const getJobSalary = (data: Partial<JobDetails>) => {
|
||||
const { salary, employType, lowPriceForFullTime, highPriceForFullTime, lowPriceForPartyTime, highPriceForPartyTime } =
|
||||
data;
|
||||
if (salary) {
|
||||
return salary;
|
||||
}
|
||||
const fullSalary =
|
||||
lowPriceForFullTime && highPriceForFullTime
|
||||
? `${lowPriceForFullTime / 1000} - ${highPriceForFullTime / 1000}K/月`
|
||||
: '';
|
||||
const partSalary =
|
||||
lowPriceForPartyTime && highPriceForPartyTime ? `${lowPriceForPartyTime} - ${highPriceForPartyTime}/小时` : '';
|
||||
const salaries: string[] = [];
|
||||
if (employType === EmployType.All) {
|
||||
salaries.push(fullSalary, partSalary);
|
||||
} else if (employType === EmployType.Full) {
|
||||
salaries.push(fullSalary);
|
||||
} else if (employType === EmployType.Part) {
|
||||
salaries.push(partSalary);
|
||||
}
|
||||
return salaries.filter(Boolean).join(',');
|
||||
};
|
||||
|
||||
export const setLastSelectMyJobId = (jobId: string) => Taro.setStorageSync(CacheKey.LAST_SELECT_MY_JOB, jobId);
|
||||
|
||||
export const getLastSelectMyJobId = () => Taro.getStorageSync<string>(CacheKey.LAST_SELECT_MY_JOB);
|
||||
|
||||
export const getJobLocation = (data: JobManageInfo) => {
|
||||
const cityValues = getCityValues([data.provinceCode, data.cityCode, data.countyCode], '-');
|
||||
return cityValues ? cityValues : data.jobLocation;
|
||||
};
|
||||
|
||||
export const getJobTitle = (data: JobInfo) => data.title || (data.sourceText || '').substring(0, 20);
|
||||
|
||||
async function requestMyDeclaredJobList(data: GetUserJobRequest) {
|
||||
const result = await http.post<GetUserJobResponse<MyDeclaredJobInfo>>(API.MY_DECLARED_JOB_LIST, { data });
|
||||
return result;
|
||||
}
|
||||
|
||||
async function requestMyBrowsedJobList(data: GetUserJobRequest) {
|
||||
const result = await http.post<GetUserJobResponse<MyBrowsedJobInfo>>(API.MY_BROWSED_JOB_LIST, { data });
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function requestJobList(data: GetJobsRequest = {}) {
|
||||
const isGetMyJob = data.isOwner || data.isFollow;
|
||||
const url = isGetMyJob ? API.GET_MY_JOB_LIST_V2 : API.GET_JOB_LIST;
|
||||
return await http.post<GetJobsResponse>(url, { data });
|
||||
}
|
||||
|
||||
export async function requestJobDetail(jobId: GetJobsDetailsRequest['jobId']) {
|
||||
const { longitude, latitude } = selectLocation(store.getState());
|
||||
const data: GetJobsDetailsRequest = { jobId, longitude, latitude };
|
||||
const detail = await http.post<JobDetails>(API.GET_JOB_DETAIL, { data });
|
||||
if (!detail.publisher || !detail.sourceText) {
|
||||
collectEvent(CollectEventName.DEBUG, { action: 'invalid_job_detail', response: detail, request: data });
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
export async function requestUserJobList(data: GetUserJobRequest) {
|
||||
const request = data.type === UserJobType.MyDeclared ? requestMyDeclaredJobList : requestMyBrowsedJobList;
|
||||
const result = await request(data);
|
||||
const dataMap = new Map();
|
||||
Object.entries(result.data).forEach(([k, v]) => dataMap.set(k, v));
|
||||
result.dataMap = dataMap;
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function requestMyRecommendJobList(data: GetMyRecommendJobRequest) {
|
||||
return await http.post<GetJobsResponse>(API.MY_RECOMMEND_JOB_LIST, { data });
|
||||
}
|
||||
|
||||
export async function requestJobManageList(data: Partial<GetJobManagesRequest> = {}) {
|
||||
return await http.post<GetJobManagesResponse>(API.GET_JOB_MANAGE_LIST, { data });
|
||||
}
|
||||
|
||||
export async function requestHasPublishedJob() {
|
||||
const data = await requestJobManageList({ status: JobManageStatus.Open });
|
||||
return data.jobResults.length > 0;
|
||||
}
|
||||
|
||||
export function postCreateJob(data: CreateJobInfo) {
|
||||
return http.post(API.CREATE_JOB, { data });
|
||||
}
|
||||
|
||||
export function postUpdateJob(data: CreateJobInfo) {
|
||||
return http.post(API.UPDATE_JOB, { data });
|
||||
}
|
||||
|
||||
export function postPublishJob(jobId: string) {
|
||||
return http.post(API.PUBLISH_JOB, { data: { jobId }, contentType: 'application/x-www-form-urlencoded' });
|
||||
}
|
||||
|
||||
export function postCloseJob(jobId: string) {
|
||||
return http.post(API.CLOSE_JOB, { data: { jobId }, contentType: 'application/x-www-form-urlencoded' });
|
||||
}
|
||||
133
src/utils/location.ts
Normal file
133
src/utils/location.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { isNaN } from 'lodash-es';
|
||||
|
||||
import { CITY_CODE_TO_NAME_MAP, COUNTY_CODE_TO_NAME_MAP, PROVINCE_CODE_TO_NAME_MAP } from '@/constants/city';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import store from '@/store';
|
||||
import { setLocationInfo } from '@/store/actions';
|
||||
import { selectLocation } from '@/store/selector';
|
||||
import { GetCityCodeRequest, LocationInfo } from '@/types/location';
|
||||
|
||||
import { authorize, getWxSetting } from './wx';
|
||||
|
||||
let locationInfo: Taro.getLocation.SuccessCallbackResult | null = null;
|
||||
let waitAuthorizeLocationPromise: ReturnType<typeof authorize> | null = null;
|
||||
const VALID_DISTANCE_KM = 999;
|
||||
|
||||
export const isValidLocation = (location: LocationInfo) => {
|
||||
if (!location) {
|
||||
return false;
|
||||
}
|
||||
return !isNaN(Number(location.latitude)) && !isNaN(Number(location.longitude));
|
||||
};
|
||||
|
||||
export const isValidDistance = (distance: number) => distance < 1000 * VALID_DISTANCE_KM;
|
||||
|
||||
export const isNotNeedAuthorizeLocation = () => getWxSetting('scope.userLocation');
|
||||
|
||||
export const calcDistance = (distance: number, fractionDigits = 2) => {
|
||||
if (isValidDistance(distance)) {
|
||||
return `${(distance / 1000).toFixed(fractionDigits)}km`;
|
||||
}
|
||||
return '999km';
|
||||
};
|
||||
|
||||
export const getCurrentCity = () => {
|
||||
const cityCode = selectLocation(store.getState()).cityCode;
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode) || '';
|
||||
};
|
||||
|
||||
export const getCityValues = (codes: string[] = [], separator: string = '') => {
|
||||
const [province, city, county] = codes;
|
||||
const values: string[] = [];
|
||||
if (province) {
|
||||
const v = PROVINCE_CODE_TO_NAME_MAP.get(province);
|
||||
v && values.push(v);
|
||||
}
|
||||
if (city) {
|
||||
const v = CITY_CODE_TO_NAME_MAP.get(city);
|
||||
v && values.push(v);
|
||||
}
|
||||
if (county) {
|
||||
const v = COUNTY_CODE_TO_NAME_MAP.get(county);
|
||||
v && values.push(v);
|
||||
}
|
||||
return values.join(separator);
|
||||
};
|
||||
|
||||
export async function waitLocationAuthorizeHidden() {
|
||||
if (!waitAuthorizeLocationPromise) {
|
||||
return;
|
||||
}
|
||||
await waitAuthorizeLocationPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取经纬度信息
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
export async function getWxLocation(
|
||||
opts?: Taro.getLocation.Option,
|
||||
force = true
|
||||
): Promise<Taro.getLocation.SuccessCallbackResult | null> {
|
||||
if (locationInfo?.latitude) return Promise.resolve(locationInfo);
|
||||
|
||||
const notNeedAuthorize = await isNotNeedAuthorizeLocation();
|
||||
|
||||
if (!notNeedAuthorize) {
|
||||
waitAuthorizeLocationPromise = authorize('scope.userLocation', '请授权获取位置权限', force);
|
||||
if (!(await waitAuthorizeLocationPromise)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 重新获取位置信息
|
||||
return new Promise(resolve => {
|
||||
try {
|
||||
Taro.getLocation({
|
||||
type: 'wgs84',
|
||||
...opts,
|
||||
success: res => {
|
||||
setTimeout(() => {
|
||||
locationInfo = null;
|
||||
}, 31000);
|
||||
if (res?.latitude) {
|
||||
locationInfo = res;
|
||||
return resolve(res);
|
||||
}
|
||||
resolve(null);
|
||||
},
|
||||
fail: () => resolve(null),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function requestLocation(force: boolean = false) {
|
||||
const data = {} as GetCityCodeRequest;
|
||||
const lgInfo = await getWxLocation({}, force);
|
||||
if (!lgInfo) {
|
||||
return;
|
||||
}
|
||||
if (lgInfo?.latitude && lgInfo?.longitude) {
|
||||
data.latitude = lgInfo.latitude;
|
||||
data.longitude = lgInfo.longitude;
|
||||
}
|
||||
|
||||
const location = await http.post<LocationInfo>(API.LOCATION, {
|
||||
data,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
if (!location.latitude) {
|
||||
location.latitude = data.latitude;
|
||||
}
|
||||
if (!location.longitude) {
|
||||
location.longitude = data.longitude;
|
||||
}
|
||||
store.dispatch(setLocationInfo(location));
|
||||
return location;
|
||||
}
|
||||
165
src/utils/material.ts
Normal file
165
src/utils/material.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { PageUrl } from '@/constants/app';
|
||||
import { CollectEventName } from '@/constants/event';
|
||||
import { GenderType, MaterialViewSource } from '@/constants/material';
|
||||
import { MessageSubscribeIds } from '@/constants/subscribe';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import { RESPONSE_ERROR_INFO } from '@/http/constant';
|
||||
import { HttpError } from '@/http/error';
|
||||
import {
|
||||
GetAnchorListRequest,
|
||||
GetAnchorListResponse,
|
||||
GetReadProfileRequest,
|
||||
GetShareProfileRequest,
|
||||
MaterialProfile,
|
||||
MaterialVideoInfo,
|
||||
UpdateProfileStatusRequest,
|
||||
} from '@/types/material';
|
||||
import { oncePromise } from '@/utils/common';
|
||||
import { collectEvent } from '@/utils/event';
|
||||
import { getJumpUrl } from '@/utils/route';
|
||||
import { postSubscribe, subscribeMessage } from '@/utils/subscribe';
|
||||
import Toast from '@/utils/toast';
|
||||
import { getUserId, getUserInfo, requestUserInfo } from '@/utils/user';
|
||||
|
||||
export const sortVideos = (videos: MaterialVideoInfo[]) => {
|
||||
return [...videos].sort((a, b) => {
|
||||
const num1 = Number(!a.isDefault);
|
||||
const num2 = Number(!b.isDefault);
|
||||
return num1 - num2;
|
||||
});
|
||||
};
|
||||
|
||||
export const isProfileNotChange = (profile: MaterialProfile, newProfile: Partial<MaterialProfile>) => {
|
||||
return Object.keys(newProfile).every(key => newProfile[key] === profile[key]);
|
||||
};
|
||||
|
||||
const updateUserIfNeed = async () => {
|
||||
const user = getUserInfo();
|
||||
if (user.isCreateResume) {
|
||||
return;
|
||||
}
|
||||
await requestUserInfo();
|
||||
};
|
||||
|
||||
export const getBasicInfo = (profile: Pick<MaterialProfile, 'age' | 'height' | 'weight' | 'shoeSize' | 'gender'>) => {
|
||||
const result: string[] = [];
|
||||
if (typeof profile.age !== 'undefined' && profile.age !== null) {
|
||||
result.push(`${profile.age}岁`);
|
||||
}
|
||||
if (typeof profile.height !== 'undefined' && profile.height !== null) {
|
||||
result.push(`${profile.height}cm`);
|
||||
}
|
||||
if (typeof profile.weight !== 'undefined' && profile.weight !== null) {
|
||||
result.push(`${profile.weight}kg`);
|
||||
}
|
||||
if (typeof profile.shoeSize !== 'undefined' && profile.shoeSize !== null) {
|
||||
result.push(`${profile.shoeSize}码`);
|
||||
}
|
||||
result.push(profile.gender === GenderType.MEN ? '男' : '女');
|
||||
return result.join('·');
|
||||
};
|
||||
|
||||
export const chooseMedia = async (option: Taro.chooseMedia.Option = {}) => {
|
||||
try {
|
||||
const result = await Taro.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['mix'],
|
||||
sourceType: ['album'],
|
||||
...option,
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const requestVideoList = async () => {
|
||||
const profile = await requestProfileDetail();
|
||||
return profile?.materialVideoInfoList || [];
|
||||
};
|
||||
|
||||
export const requestProfileDetail = oncePromise(async () => {
|
||||
return http.post<MaterialProfile>(API.GET_PROFILE);
|
||||
});
|
||||
|
||||
export const requestReadProfile = async (data: GetReadProfileRequest) => {
|
||||
return http.post<MaterialProfile>(API.READ_PROFILE, { data });
|
||||
};
|
||||
|
||||
export const requestProfileShareCode = async (resumeId: string) => {
|
||||
return http.post<MaterialProfile>(API.GET_PROFILE_SHARE_CODE, {
|
||||
data: { resumeId },
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
};
|
||||
|
||||
export const requestShareProfile = async (data: GetShareProfileRequest) => {
|
||||
return http.post<MaterialProfile>(API.VIEW_SHARE_PROFILE, { data, contentType: 'application/x-www-form-urlencoded' });
|
||||
};
|
||||
|
||||
export const postVideos = async (materialVideoInfos: MaterialVideoInfo[]) => {
|
||||
await http.post(API.SAVE_VIDEOS, { data: { materialVideoInfos } });
|
||||
updateUserIfNeed();
|
||||
};
|
||||
|
||||
export const postResumeText = async (resumeText: string) => {
|
||||
await http.post(API.CREATE_PROFILE, { data: { resumeText } });
|
||||
updateUserIfNeed();
|
||||
};
|
||||
|
||||
export const updateProfile = async (profile: Partial<MaterialProfile>) => {
|
||||
await http.post(API.UPDATE_PROFILE, { data: profile });
|
||||
updateUserIfNeed();
|
||||
};
|
||||
|
||||
export const updateProfileStatus = async (params: Omit<UpdateProfileStatusRequest, 'userId'>) => {
|
||||
const data = {
|
||||
...params,
|
||||
userId: getUserId(),
|
||||
};
|
||||
return http.post(API.UPDATE_PROFILE_STATUS, { data, contentType: 'application/x-www-form-urlencoded' });
|
||||
};
|
||||
|
||||
export const subscribeMaterialMessage = async () => {
|
||||
try {
|
||||
const tempIds = MessageSubscribeIds;
|
||||
const result = await subscribeMessage(tempIds);
|
||||
const acceptTempIds = tempIds.filter(id => result[id] && result[id] === 'accept');
|
||||
postSubscribe(tempIds, acceptTempIds);
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.error('subscribe message fail', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const requestAnchorList = async (data: GetAnchorListRequest) => {
|
||||
return http.post<GetAnchorListResponse>(API.GET_ANCHOR_LIST, { data });
|
||||
};
|
||||
|
||||
export const getMaterialShareMessage = async (profile?: MaterialProfile | null, needShareCode: boolean = true) => {
|
||||
if (!profile) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const { id, name = '', workedSecCategoryStr } = profile;
|
||||
const shareCode = needShareCode ? await requestProfileShareCode(id) : undefined;
|
||||
const title = `${name} ${workedSecCategoryStr ? `播过 ${workedSecCategoryStr}` : ''}`.trim();
|
||||
return {
|
||||
title,
|
||||
path: getJumpUrl(PageUrl.MaterialView, { shareCode, resumeId: id, source: MaterialViewSource.Share }),
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const e = error as HttpError;
|
||||
const msg = RESPONSE_ERROR_INFO[e.errorCode!];
|
||||
msg && Toast.info(msg);
|
||||
// console.error('fetch share code failed', e.info?.());
|
||||
collectEvent(CollectEventName.REQUEST_MATERIAL_SHARE_CODE_FAILED, {
|
||||
profileId: profile.id,
|
||||
error: e.info?.() || e.message,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
131
src/utils/message.ts
Normal file
131
src/utils/message.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { PluginUrl } from '@/constants/app';
|
||||
import { CollectEventName } from '@/constants/event';
|
||||
import { MessageActionStatus, MessageType } from '@/constants/message';
|
||||
import { MessageSubscribeIds, SubscribeTempId } from '@/constants/subscribe';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import store from '@/store';
|
||||
import { setMessageInfo } from '@/store/actions';
|
||||
import {
|
||||
IChatMessage,
|
||||
MainMessage,
|
||||
IChatInfo,
|
||||
GetNewChatMessagesRequest,
|
||||
PostMessageRequest,
|
||||
PostConfirmActionRequest,
|
||||
IMessageStatus,
|
||||
PostActionDetailRequest,
|
||||
IChatActionDetail,
|
||||
ChatWatchRequest,
|
||||
} from '@/types/message';
|
||||
import { getRoleType } from '@/utils/app';
|
||||
import { logWithPrefix, oncePromise } from '@/utils/common';
|
||||
import { collectEvent } from '@/utils/event';
|
||||
import { navigateTo } from '@/utils/route';
|
||||
import { postSubscribe, subscribeMessage } from '@/utils/subscribe';
|
||||
import { getUserId } from '@/utils/user';
|
||||
|
||||
const log = logWithPrefix('message-utils');
|
||||
|
||||
export const isTextMessage = (message: IChatMessage) => message.type === MessageType.Text;
|
||||
|
||||
export const isTimeMessage = (message: IChatMessage) => message.type === MessageType.Time;
|
||||
|
||||
export const isJobMessage = (message: IChatMessage) => message.type === MessageType.Job;
|
||||
|
||||
export const isMaterialMessage = (message: IChatMessage) => message.type === MessageType.Material;
|
||||
|
||||
export const isExchangeMessage = (message: IChatMessage) =>
|
||||
[MessageType.RequestAnchorContact, MessageType.RequestCompanyContact].includes(message.type);
|
||||
|
||||
export const isLocationMessage = (message: IChatMessage) => message.type === MessageType.Location;
|
||||
|
||||
export const requestRemainPushTime = oncePromise(() => {
|
||||
// if (isDev) {
|
||||
// return Promise.resolve(32);
|
||||
// }
|
||||
return http.post<number>(API.MESSAGE_REMAIN_PUSH_TIMES);
|
||||
});
|
||||
|
||||
export const requestUnreadMessageCount = oncePromise(async () => {
|
||||
const count = await http.post<number>(API.MESSAGE_UNREAD_COUNT);
|
||||
log('request unread message success, count:', count);
|
||||
store.dispatch(setMessageInfo({ count }));
|
||||
return count;
|
||||
});
|
||||
|
||||
export const requestMessageList = oncePromise(async () => {
|
||||
const { data } = await http.post<{ data: MainMessage[] }>(API.MESSAGE_CHAT_LIST, { data: { userId: getUserId() } });
|
||||
return data;
|
||||
});
|
||||
|
||||
export const requestChatDetail = (chatId: string) => {
|
||||
const data = { chatId, roleType: getRoleType() };
|
||||
return http.post<IChatInfo>(API.MESSAGE_CHAT, { data, contentType: 'application/x-www-form-urlencoded' });
|
||||
};
|
||||
|
||||
export const requestNewChatMessages = (params: GetNewChatMessagesRequest) => {
|
||||
const data = { ...params, roleType: getRoleType() };
|
||||
return http.post<IChatMessage[]>(API.MESSAGE_CHAT_NEW, { data });
|
||||
};
|
||||
|
||||
export const requestChatActionStatus = (actionId: string) => {
|
||||
return http.post<MessageActionStatus>(API.MESSAGE_GET_ACTION, {
|
||||
data: { actionId },
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
};
|
||||
|
||||
export const requestActionDetail = (data: PostActionDetailRequest) => {
|
||||
return http.post<IChatActionDetail | null>(API.MESSAGE_GET_ACTION_DETAIL, { data });
|
||||
};
|
||||
|
||||
export const requestMessageStatusList = async (chatId: string) => {
|
||||
return http.post<IMessageStatus[]>(API.MESSAGE_LIST_STATUS, {
|
||||
data: { chatId },
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
};
|
||||
|
||||
export const requestChatWatch = (data: Omit<ChatWatchRequest, 'status'>) => {
|
||||
return http.post<boolean>(API.MESSAGE_CHAT_WATCH_GET, { data });
|
||||
};
|
||||
|
||||
export const postAddMessageTimes = async (source: string) => {
|
||||
const result = await subscribeMessage(MessageSubscribeIds);
|
||||
const successIds: SubscribeTempId[] = [];
|
||||
MessageSubscribeIds.forEach(id => {
|
||||
result[id] === 'accept' && successIds.push(id);
|
||||
});
|
||||
collectEvent(CollectEventName.MESSAGE_DEV_LOG, { action: 'subscribe_new_message_reminder', source, successIds });
|
||||
await postSubscribe(MessageSubscribeIds, successIds);
|
||||
};
|
||||
|
||||
export const postCreateChat = (toUserId: string) => {
|
||||
return http.post<IChatInfo>(API.MESSAGE_CREATE_CHAT, { data: { toUserId } });
|
||||
};
|
||||
|
||||
export const postSendMessage = (data: PostMessageRequest) => {
|
||||
if (data.type === MessageType.Text) {
|
||||
return http.post(API.MESSAGE_SEND_TEXT, { data });
|
||||
}
|
||||
return http.post(API.MESSAGE_SEND_ACTION, { data });
|
||||
};
|
||||
|
||||
export const posConfirmAction = (data: PostConfirmActionRequest) => {
|
||||
return http.post<IChatInfo>(API.MESSAGE_CONFIRM_ACTION, { data });
|
||||
};
|
||||
|
||||
export const postChatRejectWatch = (data: ChatWatchRequest) => {
|
||||
return http.post<IChatInfo>(API.MESSAGE_CHAT_WATCH, { data });
|
||||
};
|
||||
|
||||
export const isChatWithSelf = (toUserId: string) => {
|
||||
return getUserId() === toUserId;
|
||||
};
|
||||
|
||||
export const openLocationSelect = () => {
|
||||
const key = 'UNCBZ-ZCSLZ-HRJX4-7P4XS-76G5H-6WF2Z';
|
||||
const referer = '播络';
|
||||
navigateTo(PluginUrl.LocationSelect, { key, referer });
|
||||
};
|
||||
108
src/utils/product.ts
Normal file
108
src/utils/product.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { ProductType, QrCodeType } from '@/constants/product';
|
||||
import http from '@/http';
|
||||
import { API, DOMAIN } from '@/http/api';
|
||||
import {
|
||||
ProductInfo,
|
||||
GetProductDetailRequest,
|
||||
GetProductIsUnlockRequest,
|
||||
PostUseProductRequest,
|
||||
GetProductIsUnlockResponse,
|
||||
CustomerServiceInfo,
|
||||
CreatePayInfoRequest,
|
||||
CreatePayInfoResponse,
|
||||
CreatePayOrderParams,
|
||||
GetOrderInfoRequest,
|
||||
OrderInfo,
|
||||
} from '@/types/product';
|
||||
import { getUserId } from '@/utils/user';
|
||||
|
||||
export const isCancelPay = err => err?.errMsg === 'requestPayment:fail cancel';
|
||||
|
||||
export const getOrderPrice = (price: number) => {
|
||||
// return 1;
|
||||
return price * 100;
|
||||
};
|
||||
|
||||
export async function requestProductList() {
|
||||
const data = { userId: getUserId() };
|
||||
const list = await http.post<ProductInfo[]>(API.GET_PRODUCT_LIST, { data });
|
||||
return list;
|
||||
}
|
||||
|
||||
// 判断某个产品是否已经解锁
|
||||
export async function requestProductUseRecord(
|
||||
productCode: ProductType,
|
||||
params: Omit<PostUseProductRequest, 'productCode' | 'userId'> = {}
|
||||
) {
|
||||
const data: GetProductIsUnlockRequest = { ...params, productCode, userId: getUserId() };
|
||||
// 返回结果不是空对象则是已经解锁
|
||||
return await http.post<GetProductIsUnlockResponse>(API.PRODUCT_USE_RECORD, {
|
||||
data,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
}
|
||||
|
||||
// 使用某一个产品
|
||||
export async function requestUseProduct(
|
||||
productCode: ProductType,
|
||||
params: Omit<PostUseProductRequest, 'productCode' | 'userId'> = {}
|
||||
) {
|
||||
const data: PostUseProductRequest = { ...params, productCode, userId: getUserId() };
|
||||
return await http.post<ProductInfo>(API.USE_PRODUCT, { data });
|
||||
}
|
||||
|
||||
// 获取某个产品的剩余解锁次数
|
||||
export async function requestProductBalance(productCode: ProductType) {
|
||||
const data: GetProductDetailRequest = { productCode, userId: getUserId() };
|
||||
const { balance } = await http.post<ProductInfo>(API.GET_PRODUCT_DETAIL, {
|
||||
data,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
// 是否可以购买某一个产品
|
||||
export async function requestAllBuyProduct(productCode: ProductType) {
|
||||
const data: GetProductDetailRequest = { productCode, userId: getUserId() };
|
||||
const enable = await http.post<ProductInfo>(API.ALLOW_BUY_PRODUCT, {
|
||||
data,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return enable;
|
||||
}
|
||||
|
||||
export async function requestCsQrCode(_type: QrCodeType) {
|
||||
const result = await http.post<CustomerServiceInfo>(API.CS_QR_CODE);
|
||||
return `${DOMAIN}/${result.vxQrCode}`;
|
||||
}
|
||||
|
||||
export async function requestCreatePayInfo(params: Omit<CreatePayInfoRequest, 'payChannel' | 'payType' | 'userId'>) {
|
||||
const data: CreatePayInfoRequest = { ...params, payChannel: 1, payType: 1, userId: getUserId() };
|
||||
const result = await http.post<CreatePayInfoResponse>(API.CREATE_PAY_ORDER, { data });
|
||||
const createPayInfo = JSON.parse(result.createPayInfo) as CreatePayOrderParams;
|
||||
return { ...result, createPayInfo } as CreatePayInfoResponse & { createPayInfo: CreatePayOrderParams };
|
||||
}
|
||||
|
||||
export async function requestOrderInfo(params: Omit<GetOrderInfoRequest, 'userId'>) {
|
||||
const data: GetOrderInfoRequest = { ...params, userId: getUserId() };
|
||||
const result = await http.post<OrderInfo>(API.GET_PAY_ORDER, {
|
||||
data,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function requestPayment(params: Taro.requestPayment.Option) {
|
||||
return new Promise((resolve, reject) =>
|
||||
Taro.requestPayment({
|
||||
...params,
|
||||
fail: reject,
|
||||
success: res => {
|
||||
params.success?.(res);
|
||||
resolve(res);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
106
src/utils/qiniu-upload.ts
Normal file
106
src/utils/qiniu-upload.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { UploadTask } from '@tarojs/taro';
|
||||
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import { logWithPrefix } from '@/utils/common';
|
||||
|
||||
type RegionCode = 'ECN' | 'NCN' | 'SCN' | 'NA' | 'ASG' | '';
|
||||
|
||||
interface Config {
|
||||
token: string;
|
||||
bucket: string;
|
||||
path: string;
|
||||
uploadUrl?: string;
|
||||
}
|
||||
|
||||
const log = logWithPrefix('qiniu-upload');
|
||||
|
||||
// https://github.com/gpake/qiniu-wxapp-sdk
|
||||
class QiniuUpload {
|
||||
// bucket 所在区域。ECN, SCN, NCN, NA, ASG,分别对应七牛云的:华东,华南,华北,北美,新加坡 5 个区域
|
||||
private qiniuRegion: RegionCode = 'ECN';
|
||||
|
||||
private config: Config | null = null;
|
||||
|
||||
private getUploadUrl() {
|
||||
let uploadURL: string;
|
||||
switch (this.qiniuRegion) {
|
||||
case 'NCN':
|
||||
uploadURL = 'https://up-z1.qiniup.com';
|
||||
break;
|
||||
case 'SCN':
|
||||
uploadURL = 'https://up-z2.qiniup.com';
|
||||
break;
|
||||
case 'NA':
|
||||
uploadURL = 'https://up-na0.qiniup.com';
|
||||
break;
|
||||
case 'ASG':
|
||||
uploadURL = 'https://up-as0.qiniup.com';
|
||||
break;
|
||||
case 'ECN':
|
||||
default:
|
||||
uploadURL = 'https://up-z0.qiniup.com';
|
||||
break;
|
||||
}
|
||||
return uploadURL;
|
||||
}
|
||||
|
||||
private requestUploadConfig = async () => {
|
||||
this.config = await http.post<Config>(API.GET_QI_NIU_TOKEN);
|
||||
return this.config;
|
||||
};
|
||||
|
||||
private getRandomFileName = (length: number = 32) => {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
|
||||
// 结合时间戳
|
||||
const timestamp = Date.now().toString(36); // 将时间戳转换为36进制
|
||||
result += timestamp;
|
||||
|
||||
// 随机补全到指定长度
|
||||
while (result.length < length) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters[randomIndex];
|
||||
}
|
||||
|
||||
// 截取到指定长度
|
||||
return result.substring(0, length);
|
||||
};
|
||||
|
||||
init = () => {
|
||||
this.requestUploadConfig();
|
||||
};
|
||||
|
||||
upload = async (filePath: string, onProgress?: UploadTask.OnProgressUpdateCallback, prefixName?: string) => {
|
||||
let config = this.config;
|
||||
if (!config) {
|
||||
config = await this.requestUploadConfig();
|
||||
}
|
||||
|
||||
const { token, bucket, path: folderPath } = config;
|
||||
const fileName = `${prefixName ? `${prefixName}-` : ''}${this.getRandomFileName()}`;
|
||||
const url = config.uploadUrl || this.getUploadUrl();
|
||||
|
||||
const formData = {
|
||||
token,
|
||||
bucket,
|
||||
key: `${folderPath}${fileName}`,
|
||||
};
|
||||
|
||||
log('upload fileName', fileName);
|
||||
|
||||
const result = await http.upload<{ hash: string; key: string }>(url, {
|
||||
filePath,
|
||||
name: 'file',
|
||||
formData,
|
||||
onProgress,
|
||||
});
|
||||
log('upload result:', result);
|
||||
return result.key;
|
||||
};
|
||||
}
|
||||
|
||||
const qiniuUpload = new QiniuUpload();
|
||||
|
||||
export default qiniuUpload;
|
||||
63
src/utils/route.ts
Normal file
63
src/utils/route.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { PageUrl, PluginUrl } from '@/constants/app';
|
||||
import { logWithPrefix, safeJsonParse } from '@/utils/common';
|
||||
|
||||
type ValidUrl = PageUrl | PluginUrl;
|
||||
|
||||
const log = logWithPrefix('route-utils');
|
||||
|
||||
function completeUrl(path: string) {
|
||||
if (path.startsWith('pages')) {
|
||||
return `/${path}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function getJumpUrl(page: ValidUrl, params: Record<string, BL.Anything> = {}) {
|
||||
const query = Object.entries(params)
|
||||
.filter(([_k, v]) => typeof v !== 'undefined')
|
||||
.map(([k, v]) => `${k}=${typeof v === 'object' ? encodeURIComponent(JSON.stringify(v)) : v}`)
|
||||
.join('&');
|
||||
const url = completeUrl(`${page}${query ? `?${query}` : ''}`);
|
||||
return url;
|
||||
}
|
||||
|
||||
export function switchTab(page: PageUrl, params: Record<string, BL.Anything> = {}) {
|
||||
const url = getJumpUrl(page, params);
|
||||
log('switchTab', url);
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.switchTab({ url, success: resolve, fail: reject });
|
||||
});
|
||||
}
|
||||
|
||||
export function redirectTo(page: PageUrl, params: Record<string, BL.Anything> = {}) {
|
||||
const url = getJumpUrl(page, params);
|
||||
log('redirectTo', url);
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.redirectTo({ url, success: resolve, fail: reject });
|
||||
});
|
||||
}
|
||||
|
||||
export function navigateTo(page: ValidUrl, params: Record<string, BL.Anything> = {}) {
|
||||
const url = getJumpUrl(page, params);
|
||||
log('navigateTo', url);
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.navigateTo({ url, success: resolve, fail: reject });
|
||||
});
|
||||
}
|
||||
|
||||
export function navigateBack(delta: number = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.navigateBack({ delta, success: resolve, fail: reject });
|
||||
});
|
||||
}
|
||||
|
||||
export function getPageQuery<T = Record<string, BL.Anything>>() {
|
||||
const params = (Taro.getCurrentInstance().router?.params || {}) as T;
|
||||
return params;
|
||||
}
|
||||
|
||||
export function parseQuery<T = BL.Anything>(obj: string, defaultValue: BL.Anything = null) {
|
||||
return safeJsonParse<T>(decodeURIComponent(obj), defaultValue);
|
||||
}
|
||||
24
src/utils/share.ts
Normal file
24
src/utils/share.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { ShareAppMessageReturn } from '@tarojs/taro';
|
||||
|
||||
import { PageUrl } from '@/constants/app';
|
||||
|
||||
import { getJumpUrl } from './route';
|
||||
|
||||
const imageUrl = 'https://neighbourhood.cn/share_d.jpg';
|
||||
|
||||
const getRandomCount = () => {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const day = date.getDate();
|
||||
const seed = (year + month + day) * day;
|
||||
return (seed % 300) + 500;
|
||||
};
|
||||
|
||||
export const getCommonShareMessage = (useCapture: boolean = true): ShareAppMessageReturn => {
|
||||
return {
|
||||
title: `昨天新增了${getRandomCount()}条主播通告,宝子快来看看`,
|
||||
path: getJumpUrl(PageUrl.Job),
|
||||
imageUrl: useCapture ? undefined : imageUrl,
|
||||
};
|
||||
};
|
||||
41
src/utils/subscribe.ts
Normal file
41
src/utils/subscribe.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { SubscribeTempId } from '@/constants/subscribe';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import { logWithPrefix } from '@/utils/common';
|
||||
|
||||
const log = logWithPrefix('subscribe-utils');
|
||||
|
||||
export const isSubscribeRefused = async (tempId: SubscribeTempId | SubscribeTempId[]) => {
|
||||
tempId = Array.isArray(tempId) ? tempId : [tempId];
|
||||
const { subscriptionsSetting } = await Taro.getSetting({ withSubscriptions: true });
|
||||
log('isSubscribeRefuse subscriptionsSetting:', subscriptionsSetting);
|
||||
if (!subscriptionsSetting) {
|
||||
return false;
|
||||
}
|
||||
const { mainSwitch, itemSettings = {} } = subscriptionsSetting;
|
||||
if (!mainSwitch) {
|
||||
return true;
|
||||
}
|
||||
return tempId.some(id => {
|
||||
const item = itemSettings[id];
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
return item === 'reject';
|
||||
});
|
||||
};
|
||||
|
||||
export const subscribeMessage = async (tempIds: SubscribeTempId[]) => {
|
||||
return Taro.requestSubscribeMessage({
|
||||
tmplIds: tempIds,
|
||||
entityIds: [],
|
||||
});
|
||||
};
|
||||
|
||||
export const postSubscribe = (tempIds: SubscribeTempId[], acceptTempIds: SubscribeTempId[]) => {
|
||||
const data = { templateIds: tempIds, subscribeIds: acceptTempIds };
|
||||
log('postSubscribe', data);
|
||||
return http.post(API.SUBSCRIBE, { data });
|
||||
};
|
||||
50
src/utils/time.ts
Normal file
50
src/utils/time.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export function formatTime(time: number | string, template = 'YYYY-MM-DD'): string {
|
||||
if (!time) {
|
||||
return '';
|
||||
}
|
||||
time = Number(time);
|
||||
return dayjs(time).format(template);
|
||||
}
|
||||
|
||||
export function formatDate(time: number | string, template = 'YYYY-MM-DD'): string {
|
||||
const now = dayjs();
|
||||
const oneHour = now.subtract(1, 'hour').valueOf();
|
||||
time = Number(time);
|
||||
if (time > oneHour) {
|
||||
return '刚刚';
|
||||
}
|
||||
const todayStart = now.startOf('date').valueOf();
|
||||
if (time > todayStart) {
|
||||
return '今天';
|
||||
}
|
||||
const yesterdayStart = now.subtract(1, 'days').startOf('date').valueOf();
|
||||
if (time > yesterdayStart) {
|
||||
return '昨天';
|
||||
}
|
||||
return dayjs(time).format(template);
|
||||
}
|
||||
|
||||
export function activeDate(time: number | string): string {
|
||||
const now = dayjs();
|
||||
time = Number(time);
|
||||
// const oneHour = now.subtract(1, 'hour').valueOf();
|
||||
// if (time > oneHour) {
|
||||
// return '刚刚活跃';
|
||||
// }
|
||||
// const todayStart = now.startOf('date').valueOf();
|
||||
// if (time > todayStart) {
|
||||
// return '今天活跃';
|
||||
// }
|
||||
const yesterdayStart = now.subtract(3, 'days').startOf('date').valueOf();
|
||||
if (time > yesterdayStart) {
|
||||
return '3日内活跃';
|
||||
}
|
||||
|
||||
const weakStart = now.subtract(1, 'weeks').startOf('date').valueOf();
|
||||
if (time > weakStart) {
|
||||
return '7日内活跃';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
24
src/utils/toast.ts
Normal file
24
src/utils/toast.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { sleep } from '@/utils/common';
|
||||
|
||||
const Toast = {
|
||||
info: async (title: string, duration: number = 1500, wait: boolean = false) => {
|
||||
Taro.showToast({ title, icon: 'none', duration });
|
||||
wait && (await sleep(duration / 1000));
|
||||
},
|
||||
success: async (title: string, duration: number = 1500, wait: boolean = false) => {
|
||||
Taro.showToast({ title, icon: 'success', duration });
|
||||
wait && (await sleep(duration / 1000));
|
||||
},
|
||||
error: async (title: string, duration: number = 1500, wait: boolean = false) => {
|
||||
Taro.showToast({ title, icon: 'error', duration });
|
||||
wait && (await sleep(duration / 1000));
|
||||
},
|
||||
loading: async (title: string, duration: number = 1500, wait: boolean = false) => {
|
||||
Taro.showToast({ title, icon: 'loading', duration });
|
||||
wait && (await sleep(duration / 1000));
|
||||
},
|
||||
};
|
||||
|
||||
export default Toast;
|
||||
148
src/utils/user.ts
Normal file
148
src/utils/user.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
import { CacheKey } from '@/constants/cache-key';
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import store from '@/store';
|
||||
import { setUserInfo, setBindPhone } from '@/store/actions';
|
||||
import { selectUserInfo } from '@/store/selector';
|
||||
import {
|
||||
FollowGroupRequest,
|
||||
SetPhoneRequest,
|
||||
UpdateUserInfoRequest,
|
||||
UpdateUserInfoResponse,
|
||||
UserInfo,
|
||||
} from '@/types/user';
|
||||
import { logWithPrefix } from '@/utils/common';
|
||||
import Toast from '@/utils/toast';
|
||||
|
||||
let lastOpenMiniProgramTime: number | null = null;
|
||||
const SHOW_LOGIN_GUIDE_OFFSET = 1 * 60 * 60 * 1000;
|
||||
// const SHOW_LOGIN_GUIDE_OFFSET = 60 * 1000;
|
||||
// const SHOW_MATERIAL_GUIDE_OFFSET = 1 * 1000;
|
||||
const log = logWithPrefix('user-utils');
|
||||
let showLoginGuide: boolean | null = null;
|
||||
|
||||
export const getUserInfo = () => selectUserInfo(store.getState());
|
||||
|
||||
export const getUserId = () => getUserInfo().userId;
|
||||
|
||||
// 无效则说明还没拉到数据
|
||||
export const isValidUserInfo = (info: UserInfo) => !!info.userId;
|
||||
|
||||
export const isNeedLogin = (info: UserInfo) => !info.isBindPhone;
|
||||
// export const isNeedLogin = (info: UserInfo) => !info.isBindPhone || info.userId === '534740874077898752';
|
||||
|
||||
export const updateLastLoginTime = () => {
|
||||
lastOpenMiniProgramTime = Taro.getStorageSync<number>(CacheKey.LAST_OPEN_MINI_PROGRAM_TIME) ?? null;
|
||||
const now = Date.now();
|
||||
log(`updateLastLoginTime: lastOpenMiniProgramTime=${lastOpenMiniProgramTime}, now=${now}`);
|
||||
Taro.setStorageSync(CacheKey.LAST_OPEN_MINI_PROGRAM_TIME, now);
|
||||
};
|
||||
|
||||
/**
|
||||
* 登录引导
|
||||
* 非首次打开小程序后,如果没有绑定手机号,在打开小程序时自动弹出一次
|
||||
* 指定时间间隔内只出现一次
|
||||
* @param info
|
||||
* @returns
|
||||
*/
|
||||
export const shouldShowLoginGuide = (info: UserInfo) => {
|
||||
// if (1 < 2) {
|
||||
// return true;
|
||||
// }
|
||||
if (!isValidUserInfo(info) || !isNeedLogin(info)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof showLoginGuide === 'boolean') {
|
||||
return showLoginGuide;
|
||||
}
|
||||
if (!lastOpenMiniProgramTime) {
|
||||
// 第一次打开不强制提示
|
||||
showLoginGuide = false;
|
||||
} else {
|
||||
showLoginGuide = Date.now() - lastOpenMiniProgramTime > SHOW_LOGIN_GUIDE_OFFSET;
|
||||
}
|
||||
return showLoginGuide;
|
||||
};
|
||||
|
||||
// export const shouldShowLoginDialog = (info: UserInfo) => {
|
||||
// if (!isValidUserInfo(info) || !isNeedLogin(info)) {
|
||||
// return false;
|
||||
// }
|
||||
// const cache = Taro.getStorageSync<boolean>(CacheKey.SHOW_LOGIN_DIALOG);
|
||||
// return !cache;
|
||||
// };
|
||||
|
||||
// export const setAlreadyShowLoginDialog = () => Taro.setStorageSync(CacheKey.SHOW_LOGIN_DIALOG, '1');
|
||||
|
||||
export const isNeedCreateMaterial = async () => {
|
||||
let info = getUserInfo();
|
||||
if (!isValidUserInfo(info)) {
|
||||
info = await requestUserInfo();
|
||||
}
|
||||
return !info.isCreateResume;
|
||||
};
|
||||
|
||||
export const ensureUserInfo = async (info: UserInfo, toast = true) => {
|
||||
if (!isValidUserInfo(info)) {
|
||||
try {
|
||||
await requestUserInfo();
|
||||
} catch (e) {
|
||||
toast && Toast.error('请稍后再试');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const dispatchUpdateUser = (userInfo: Partial<UserInfo>) => store.dispatch(setUserInfo(userInfo));
|
||||
|
||||
export async function requestUserInfo() {
|
||||
const userInfo = await http.post<UserInfo>(API.USER);
|
||||
dispatchUpdateUser(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
export async function setPhoneNumber(params: SetPhoneRequest) {
|
||||
try {
|
||||
await http.post<string>(API.SET_PHONE, { data: params });
|
||||
store.dispatch(setBindPhone(true));
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: '绑定失败', icon: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUserInfo(params: Omit<UpdateUserInfoRequest, 'userId'>) {
|
||||
try {
|
||||
const data = { ...params, userId: getUserId() };
|
||||
const {
|
||||
avatarUrl,
|
||||
nickName,
|
||||
imAcctNo = params.imAcctNo,
|
||||
} = await http.post<UpdateUserInfoResponse>(API.USER_UPDATE, { data });
|
||||
const userInfoPayload: Partial<UserInfo> = { imAcctNo };
|
||||
if (avatarUrl) {
|
||||
userInfoPayload.avatarUrl = avatarUrl;
|
||||
userInfoPayload.isDefaultAvatar = false;
|
||||
}
|
||||
if (nickName) {
|
||||
userInfoPayload.nickName = nickName;
|
||||
userInfoPayload.isDefaultNickname = false;
|
||||
}
|
||||
store.dispatch(setUserInfo(userInfoPayload));
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: '更新失败', icon: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
export async function followGroup(blGroupId: FollowGroupRequest['blGroupId']) {
|
||||
try {
|
||||
const data: FollowGroupRequest = { blGroupId, userId: getUserId() };
|
||||
await http.post(API.FOLLOW_GROUP, { data });
|
||||
return true;
|
||||
} catch (e) {
|
||||
Taro.showToast({ title: '出错了,请重试', icon: 'error' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
25
src/utils/video.ts
Normal file
25
src/utils/video.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Taro, { UploadTask } from '@tarojs/taro';
|
||||
|
||||
import http from '@/http';
|
||||
import { API } from '@/http/api';
|
||||
import { GetVideoInfoRequest, UploadVideoResult } from '@/types/material';
|
||||
import qiniuUpload from '@/utils/qiniu-upload';
|
||||
|
||||
export const commonUploadProgress: UploadTask.OnProgressUpdateCallback = res => {
|
||||
if (res.progress >= 100) {
|
||||
Taro.hideLoading();
|
||||
return;
|
||||
}
|
||||
Taro.showLoading({ title: `上传${res.progress}%` });
|
||||
};
|
||||
|
||||
export const uploadVideo = async (
|
||||
filePath: string,
|
||||
type: string,
|
||||
onProgress?: UploadTask.OnProgressUpdateCallback,
|
||||
prefixName?: string
|
||||
) => {
|
||||
const qiniuKey = await qiniuUpload.upload(filePath, onProgress, prefixName);
|
||||
const data: GetVideoInfoRequest = { sourcePath: qiniuKey, type: type === 'video' ? 'VIDEO' : 'IMAGE' };
|
||||
return http.post<UploadVideoResult>(API.GET_VIDEO_INFO, { data, contentType: 'application/x-www-form-urlencoded' });
|
||||
};
|
||||
54
src/utils/wx.ts
Normal file
54
src/utils/wx.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
/**
|
||||
* 获取微信配置
|
||||
* @param scope
|
||||
* @param tips
|
||||
* @param force
|
||||
* @returns
|
||||
*/
|
||||
export function getWxSetting(scope: keyof Taro.AuthSetting) {
|
||||
return new Promise<boolean>(resolve => {
|
||||
Taro.getSetting({
|
||||
success(res) {
|
||||
if (!res?.authSetting[scope]) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function authorize(scope: keyof Taro.AuthSetting, tips: string, force = true) {
|
||||
return new Promise<boolean>(resolve => {
|
||||
Taro.authorize({
|
||||
scope,
|
||||
success() {
|
||||
return resolve(true);
|
||||
},
|
||||
fail() {
|
||||
if (!force) return resolve(false);
|
||||
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: tips,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
Taro.openSetting({
|
||||
success(result) {
|
||||
if (!result.authSetting[scope]) resolve(false);
|
||||
else resolve(true);
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: () => resolve(false),
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user