first commit

This commit is contained in:
2026-02-26 09:32:03 +08:00
commit 36a8e4c51b
845 changed files with 116474 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
import type {CustomRequestOptions} from '@/interceptor/request'
import {useConfigStore, useUserStore} from '@/store'
import Config from '@/config'
import {i18n} from "@/locale";
type IRes<T> = T extends ArrayBuffer ? ArrayBuffer : IResData<T>
export const http = <T>(options: CustomRequestOptions) => {
console.log(options, 'options')
// 1. 返回 Promise 对象
return new Promise<IRes<T>>((resolve, reject) => {
uni.request({
...options,
dataType: 'json',
// 响应成功
success(res) {
console.log(res, '111111')
const ErrorMessage = {
401: i18n.global.t('common.prompt.authentication-failed-please-log-in'),
700: i18n.global.t('common.prompt.login-expired-please-log-in-again'),
}
const configStore = useConfigStore()
const userStore = useUserStore()
const data = res.data as IRes<T>
if (!(data instanceof ArrayBuffer) && data.systemTime) {
// @ts-ignore
configStore.serverTime = +data.systemTime
}
if (data instanceof ArrayBuffer || data.code === 200) {
resolve(data)
} else if (data.code === 401 || data.code === 700) {
uni.showToast({
icon: 'none',
title: ErrorMessage[data.code],
})
userStore?.clear?.()
uni.navigateTo({url: Config.loginPath})
reject(data)
} else if (data.code === 711) {
reject(data)
} else {
// 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast &&
uni.showToast({
icon: 'none',
title: (res.data as IResData<T>).msg || i18n.global.t('common.prompt.request-incorrect'),
})
reject(data)
}
},
// 响应失败
fail(err) {
uni.showToast({
icon: 'none',
title: i18n.global.t('common.prompt.request-failed-please-try-again-later'),
})
reject(err)
},
})
})
}
/**
* GET 请求
* @param url 后台地址
* @param query 请求query参数
* @param responseType
* @returns
*/
export const httpGet = <T>(url: string, query?: Record<string, any>, responseType: 'text' | 'arraybuffer' = 'text') => {
return http<T>({
url,
query,
responseType,
method: 'GET',
})
}
/**
* DELETE 请求
* @param url 后台地址
* @param query 请求query参数
* @param data 请求body参数
* @param responseType
* @returns
*/
export const httpDelete = <T>(
url: string,
query?: Record<string, any>,
data?: Record<string, any>,
responseType: 'text' | 'arraybuffer' = 'text',
) => {
return http<T>({
url,
query,
data,
responseType,
method: 'DELETE',
})
}
/**
* POST 请求
* @param url 后台地址
* @param data 请求body参数
* @param query 请求query参数,post请求也支持query,很多微信接口都需要
* @param responseType 数据响应格式
* @returns
*/
export const httpPost = <T>(
url: string,
data?: Record<string, any> | string,
query?: Record<string, any>,
responseType: 'text' | 'arraybuffer' = 'text',
) => {
return http<T>({
url,
query,
data,
responseType,
method: 'POST',
})
}
/**
* PUT 请求
* @param url 后台地址
* @param data 请求body参数
* @param query 请求query参数,post请求也支持query,很多微信接口都需要
* @param responseType 数据响应格式
* @returns
*/
export const httpPut = <T>(
url: string,
data?: Record<string, any> | string,
query?: Record<string, any>,
responseType: 'text' | 'arraybuffer' = 'text',
) => {
return http<T>({
url,
query,
data,
responseType,
method: 'PUT',
})
}
http.get = httpGet
http.post = httpPost
http.put = httpPut
http.delete = httpDelete
+59
View File
@@ -0,0 +1,59 @@
import {dayjs} from '@/plugin/index'
import * as R from 'ramda'
// 店铺营业时间格式化
export function businessTimeFormat(weekNums: string, timePeriod: string) {
if (!weekNums || !timePeriod) {
return ''
}
if (!weekNums.length || !timePeriod.length) {
return ''
}
let timeString = ''
const weeks: string[] = dayjs.weekdays()
console.log('weeks', weeks)
const weekNumsArr = R.map(item => +item, R.split(',', R.trim(weekNums || '')))
const groupWeek = R.groupWith((a, b) => +a + 1 === +b, weekNumsArr)
console.log('weekNums', groupWeek)
R.forEach((weekNumsArr) => {
if (R.gt(weekNumsArr.length, 1)) {
timeString += `${weeks[weekNumsArr[0] - 1]}-${weeks[weekNumsArr[weekNumsArr.length - 1] - 1]} `
} else {
timeString += `${weeks[weekNumsArr[0] - 1]} `
}
}, groupWeek)
const groupTimePeriod = R.map(item => R.map(time => time + ':00', item.split('-')), R.split(',', R.trim(timePeriod || '')))
console.log('timePeriodArr', groupTimePeriod)
R.forEach((timePeriodArr) => {
timeString += `${timePeriodArr[0]}-${timePeriodArr[1]} `
}, groupTimePeriod)
return timeString
}
// 转换手机号码中间四位为*
export function isPhone(mobile: string) {
const regex = /^1[3-9]\d{9}$/;
const regex1 = /^[1-9]\d{7}$/
return regex.test(mobile) || regex1.test(mobile);
}
/**
* 验证邮箱
*/
export function isEmail(email: string) {
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
}
// 转换手机号码中间四位为*
export function conversionMobile(mobile: string) {
if (!mobile) {
return
}
return mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
+263
View File
@@ -0,0 +1,263 @@
let isIos = false;
// #ifdef APP-PLUS
isIos = plus.os.name === "iOS";
// #endif
// 判断安卓主方法
function requestAndroidPermission(permissionID, ifRequest) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions([permissionID], onSuccess, onError);
function onSuccess(res) {
const grantedList = res.granted;
const deniedList = res.deniedPresent;
const deniedAlwaysList = res.deniedAlways;
console.log("res", res, grantedList.includes(permissionID));
if (grantedList.includes(permissionID)) {
resolve(true);
} else {
resolve(false);
ifRequest && gotoAppPermissionSetting();
}
}
function onError(err) {
reject(err);
}
});
}
// 分别判断Ios
function judgeIosPermissionPush(ifRequest) {
return new Promise((resolve) => {
let UIApplication = plus.ios.import("UIApplication");
let app = UIApplication.sharedApplication();
let enabledTypes = 0;
if (app.currentUserNotificationSettings) {
let settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
if (enabledTypes == 0) {
ifRequest && gotoAppPermissionSetting();
resolve(false);
} else {
resolve(true);
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
ifRequest && gotoAppPermissionSetting();
resolve(false);
} else {
resolve(true);
}
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
});
}
// 判断定位权限是否开启
function judgeIosPermissionLocation(ifRequest) {
return new Promise((resolve) => {
let cllocationManger = plus.ios.import("CLLocationManager");
let status = cllocationManger.authorizationStatus();
if (status == 2) {
ifRequest && gotoAppPermissionSetting();
resolve(false);
} else {
resolve(true);
}
plus.ios.deleteObject(cllocationManger);
});
}
// 判断麦克风权限是否开启
function judgeIosPermissionRecord(ifRequest) {
return new Promise((resolve) => {
let avaudiosession = plus.ios.import("AVAudioSession");
let avaudio = avaudiosession.sharedInstance();
let permissionStatus = avaudio.recordPermission();
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
ifRequest && gotoAppPermissionSetting();
resolve(false);
} else {
resolve(true);
}
plus.ios.deleteObject(avaudiosession);
});
}
// 判断相机权限是否开启
function judgeIosPermissionCamera(ifRequest) {
return new Promise((resolve) => {
let AVCaptureDevice = plus.ios.import("AVCaptureDevice");
let authStatus = AVCaptureDevice.authorizationStatusForMediaType("vide");
if (authStatus == 3) {
resolve(true);
} else {
ifRequest && gotoAppPermissionSetting();
resolve(false);
}
plus.ios.deleteObject(AVCaptureDevice);
});
}
// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary(ifRequest) {
return new Promise((resolve) => {
let PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
let authStatus = PHPhotoLibrary.authorizationStatus();
if (authStatus == 3) {
resolve(true);
} else {
ifRequest && gotoAppPermissionSetting();
resolve(false);
}
plus.ios.deleteObject(PHPhotoLibrary);
});
}
// 判断通讯录权限是否开启
function judgeIosPermissionContact(ifRequest) {
return new Promise((resolve) => {
let CNContactStore = plus.ios.import("CNContactStore");
let cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus == 3) {
resolve(true);
} else {
ifRequest && gotoAppPermissionSetting();
resolve(false);
}
plus.ios.deleteObject(CNContactStore);
});
}
// 安卓判断通知权限是否开启
function judgeIosPermissionNotification(ifRequest) {
return new Promise((resolve) => {
let main = plus.android.runtimeMainActivity();
let pkName = main.getPackageName();
let uid = main.getApplicationInfo().plusGetAttribute("uid");
let NotificationManagerCompat = plus.android.importClass("android.support.v4.app.NotificationManagerCompat");
//android.support.v4升级为androidx
if (NotificationManagerCompat == null) {
NotificationManagerCompat = plus.android.importClass("androidx.core.app.NotificationManagerCompat");
}
let areNotificationsEnabled = NotificationManagerCompat.from(main).areNotificationsEnabled();
console.log("areNotificationsEnabled", areNotificationsEnabled);
// 未开通‘允许通知'权限,则弹窗提醒开通,并点击确认后,跳转到系统设置页面进行设置
if (areNotificationsEnabled) {
resolve(true);
} else {
resolve(false);
}
});
}
// 判断所有权限
function getPermission(permission, ifRequest = false) {
switch (permission) {
case "location":
if (isIos) {
return judgeIosPermissionLocation(ifRequest);
} else {
return requestAndroidPermission("android.permission.ACCESS_FINE_LOCATION", ifRequest);
}
break;
case "camera":
if (isIos) {
return judgeIosPermissionCamera(ifRequest);
} else {
return requestAndroidPermission("android.permission.CAMERA", ifRequest);
}
break;
case "photo":
if (isIos) {
return judgeIosPermissionPhotoLibrary(ifRequest);
} else {
return requestAndroidPermission("android.permission.READ_EXTERNAL_STORAGE", ifRequest);
}
break;
case "record":
if (isIos) {
return judgeIosPermissionRecord(ifRequest);
} else {
return requestAndroidPermission("android.permission.RECORD_AUDIO", ifRequest);
}
break;
case "contact":
if (isIos) {
return judgeIosPermissionContact(ifRequest);
} else {
return requestAndroidPermission("android.permission.READ_CONTACTS", ifRequest);
}
break;
case "call":
if (isIos) {
return Promise.resolve(true);
} else {
return requestAndroidPermission("android.permission.CALL_PHONE", ifRequest);
}
break;
case "push":
if (isIos) {
return judgeIosPermissionPush(ifRequest);
} else {
return judgeIosPermissionNotification(ifRequest);
}
break;
default:
break;
}
}
// 去设置
function gotoAppPermissionSetting() {
if (isIos) {
let UIApplication = plus.ios.import("UIApplication");
let application2 = UIApplication.sharedApplication();
let NSURL2 = plus.ios.import("NSURL");
// let setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
let setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
let Intent = plus.android.importClass("android.content.Intent");
let Settings = plus.android.importClass("android.provider.Settings");
let Uri = plus.android.importClass("android.net.Uri");
let mainActivity = plus.android.runtimeMainActivity();
let intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
let uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
// 检查是否开启了定位
function checkSystemEnableLocation() {
if (isIos) {
let result = false;
let cllocationManger = plus.ios.import("CLLocationManager");
result = cllocationManger.locationServicesEnabled();
plus.ios.deleteObject(cllocationManger);
return result;
} else {
let context = plus.android.importClass("android.content.Context");
let locationManager = plus.android.importClass("android.location.LocationManager");
let main = plus.android.runtimeMainActivity();
let mainSvr = main.getSystemService(context.LOCATION_SERVICE);
let result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
return result;
}
}
export default {
getPermission,
checkSystemEnableLocation,
gotoAppPermissionSetting,
};
+11
View File
@@ -0,0 +1,11 @@
export const platform = __UNI_PLATFORM__
export const isH5 = __UNI_PLATFORM__ === 'h5'
export const isApp = __UNI_PLATFORM__ === 'app'
export const isMp = __UNI_PLATFORM__.startsWith('mp-')
const PLATFORM = {
platform,
isH5,
isApp,
isMp,
}
export default PLATFORM
+214
View File
@@ -0,0 +1,214 @@
import {tipView, updateView} from '@/static/app/js/updateView'
import { appVersionIsUpdateVersionPost } from '@/service'
import {dayjs} from '@/plugin/index'
import Config from "@/config";
import {i18n} from "@/locale";
// 比较版本号 v1 > v2 返回1v1 < v2 返回-1v1 = v2 返回0
export function compareVersion(v1: string | undefined, v2: string | undefined) {
if (!v1 || !v2) {
return 0
}
const v1Arr = v1.split('.')
const v2Arr = v2.split('.')
const len = Math.max(v1Arr.length, v2Arr.length)
while (v1Arr.length < len) {
v1Arr.push('0')
}
while (v2Arr.length < len) {
v2Arr.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1Arr[i])
const num2 = parseInt(v2Arr[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
// app版本更新
export function appUpdate() {
if (!plus?.runtime?.appid) {
return
}
plus?.runtime?.getProperty(plus?.runtime?.appid, async (info) => {
const currentVersion = info.version as string
const platform = plus?.os?.name
console.log('info', platform, platform === 'iOS', info)
if (platform === 'Android') {
await updateAndroid(currentVersion, platform)
} else if (platform === 'iOS') {
await updateIos(currentVersion, platform)
}
})
}
async function updateAndroid(currentVersion: string, platform: string) {
try {
const res = await appVersionIsUpdateVersionPost({
body: {
// 1 用户端 2 门店端
port: 1,
// 设备类型:1-安卓(默认) 2-ios
appType: 1,
}
})
console.log('res', res)
const versionInfo: AppVersion = res.data
const newVersion = versionInfo?.versionNo
const result = compareVersion(currentVersion, newVersion)
console.log(result)
// 本地版本最新
if (result >= 0) {
return
}
if (+versionInfo.isForce === 1) {
installApp(versionInfo.url)
} else {
tipView.viewHotFixInit(versionInfo, platform)
}
} catch (e) {
}
}
async function updateIos(currentVersion: string, platform: string) {
const res = await appVersionIsUpdateVersionPost({
body: {
// 1 用户端 2 门店端
port: 1,
// 设备类型:1-安卓(默认) 2-ios
appType: 2,
}
})
console.log('res', res)
const versionInfo: AppVersion = res.data
const newVersion = versionInfo?.versionNo
const result = compareVersion(currentVersion, newVersion)
console.log(result)
// 本地版本最新
if (result >= 0) {
return
}
if (+versionInfo.isForce === 1) {
installApp(versionInfo.url)
} else {
tipView.viewHotFixInit(versionInfo, platform)
}
}
async function updateIosOnline(currentVersion: string, platform: string, versionInfo: AppVersion) {
const res = await uni.request({
method: 'POST',
url: `https://itunes.apple.com/lookup?id=${Config.iosId}`,
})
console.log('iOS', res)
// @ts-ignore
const {currentVersionReleaseDate, version} = res?.data?.results[0] ?? {}
const res1 = await uni.request({
method: 'POST',
url: `https://itunes.apple.com/cn/lookup?id=${Config.iosId}`,
})
console.log('iOS', res1)
const {
currentVersionReleaseDate: currentVersionReleaseDate1,
version: version1
// @ts-ignore
} = res1?.data?.results[0] ?? {}
if (version || version1) {
if (currentVersionReleaseDate && version && currentVersionReleaseDate1 && version1) {
let newVersion
if (dayjs(currentVersionReleaseDate).isSameOrAfter(currentVersionReleaseDate1)) {
newVersion = version
} else {
newVersion = version1
}
const result = compareVersion(currentVersion, newVersion)
// 本地版本最新
if (result >= 0) {
return
}
tipView.viewHotFixInit(versionInfo, platform)
} else {
const result = compareVersion(currentVersion, version || version1)
// 本地版本最新
if (result >= 0) {
return
}
tipView.viewHotFixInit(versionInfo, platform)
}
}
}
// app安装
export function installApp(downloadUrl: string) {
updateView.viewHotFixInit()
uni
.downloadFile({
url: downloadUrl,
complete: (res) => {
console.log('res', downloadUrl, res)
updateView.viewHotFixHide()
if (res && res.statusCode === 200) {
plus.runtime.install(
(res.tempFilePath),
{
force: true
},
function (widgetInfo) {
console.log(widgetInfo)
uni.showToast({
title: i18n.global.t('common.prompt.update-successfully'),
icon: 'none',
position: 'bottom',
duration: 2000,
})
setTimeout(() => {
plus.runtime.restart()
}, 1000)
},
function (error) {
console.log(error)
uni.showToast({
title: i18n.global.t('common.prompt.update-failed'),
icon: 'none',
position: 'bottom',
duration: 2000,
})
},
)
} else {
uni.showToast({
title: i18n.global.t('common.prompt.download-failed'),
icon: 'none',
position: 'bottom',
duration: 2000,
})
}
},
})
.onProgressUpdate((res) => {
// console.log('下载进度' + res.progress);
updateView.viewHotFixUpdate(res.progress)
})
}
+74
View File
@@ -0,0 +1,74 @@
import Crypto from 'crypto-js'
import {Base64} from 'js-base64'
import {i18n} from "@/locale";
const env = {
uploadImageUrl: 'https://wendy123.oss-ap-southeast-1.aliyuncs.com/', // 默认存在根目录,可根据需求改
accessKeySecret: 'TSZOD1jULKPFsNp2zKhAopLe3c3AiH', // accessKeySecret 去你的阿里云上控制台上找
ossAccessKeyId: 'LTAI5tQq1bSBFCfsbvwX6DqQ', // AccessKeyId 去你的阿里云上控制台上找
timeout: 87600, // 这个是上传文件时Policy的失效时间
}
const getPolicyBase64 = function (): string {
const date = new Date()
date.setHours(date.getHours() + env.timeout)
const expirationTime = date.toISOString()
const policyText = {
expiration: expirationTime, // 设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
conditions: [
['content-length-range', 0, 2000 * 1024 * 1024], // 设置上传文件的大小限制,2G
],
}
return Base64.encode(JSON.stringify(policyText))
}
const getSignature = function (policyBase64: string): string {
const bytes = Crypto.HmacSHA1(policyBase64, env.accessKeySecret)
return Crypto.enc.Base64.stringify(bytes)
}
// 上传图片到阿里云oss
const upload = function (filePath: string, suffix = '.png', dir = 'app/image/') {
return new Promise((resolve, reject) => {
if (!filePath) {
return uni.showToast({icon: "none", title: i18n.global.t('common.prompt.picture-wrong-please-try-again')})
}
// 图片名字 可以自行定义,这里是采用当前的时间戳 + 150内的随机数来给图片命名的
const fileKey = dir + new Date().getTime() + Math.floor(Math.random() * 150) + suffix
const aliyunServerURL = env.uploadImageUrl // OSS地址,需要https
const policyBase64 = getPolicyBase64()
const signature = getSignature(policyBase64) // 获取签名
uni.uploadFile({
url: aliyunServerURL, // 开发者服务器 url
filePath: filePath, // 要上传文件资源的路径
name: 'file', // 必须填file
formData: {
key: fileKey,
policy: policyBase64,
OSSAccessKeyId: env.ossAccessKeyId,
signature: signature,
success_action_status: '200',
},
success: function (res) {
console.log(res)
if (res.statusCode !== 200) {
reject(new Error('upload error:' + JSON.stringify(res)))
return
}
resolve(aliyunServerURL + fileKey)
},
fail: function (err) {
console.log(err)
uni.hideLoading()
uni.showToast({icon: "none", title: i18n.global.t('common.prompt.up-failed')})
reject(err)
},
})
})
}
export {upload}
+163
View File
@@ -0,0 +1,163 @@
import CryptoJS from 'crypto-js'
import {Base64} from 'js-base64'
import {i18n} from '@/locale'
// === 👉 请在这里填写你的 AWS 信息 ===
const S3Config = {
bucket: 'cheflink', // 你的存储桶名称
region: 'us-east-1', // 桶所在区域
accessKeyId: 'AKIA3LKQVMAPD3KFPJGS',
secretKey: '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',
timeout: 0.1 // 签名有效时间(小时)
}
// === 签名函数 ===
function getPolicyBase64(): string {
const expiration = new Date(Date.now() + 10 * 60 * 1000).toISOString() // 10分钟有效
const policyText = {
expiration,
conditions: [
{bucket: S3Config.bucket},
['starts-with', '$key', 'cheflink/'],
['starts-with', '$Content-Type', 'image/']
]
}
return Base64.encode(JSON.stringify(policyText))
}
function getSignature(policyBase64: string): string {
const bytes = CryptoJS.HmacSHA1(policyBase64, S3Config.secretKey)
return CryptoJS.enc.Base64.stringify(bytes)
}
// === 图片压缩函数 ===
function compressImageIfNeeded(filePath: string, size: number): Promise<string> {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
if (size > 3 * 1024 * 1024) {
uni.compressImage({
src: filePath,
quality: 70,
success: res => {
uni.getFileInfo({
filePath: res.tempFilePath,
success: info => resolve(res.tempFilePath),
fail: () => resolve(res.tempFilePath),
})
},
fail: err => reject(err),
})
} else {
resolve(filePath)
}
// #endif
// #ifdef H5
if (size > 3 * 1024 * 1024) {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.onload = function () {
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
ctx?.drawImage(img, 0, 0, img.width, img.height)
canvas.toBlob(
blob => {
if (blob) {
const url = URL.createObjectURL(blob)
resolve(url)
} else {
reject(new Error('图片压缩失败'))
}
},
'image/jpeg',
0.7
)
}
img.onerror = () => reject(new Error('图片加载失败'))
img.src = filePath
} else {
resolve(filePath)
}
// #endif
// #ifndef APP-PLUS || H5
resolve(filePath)
// #endif
})
}
// === S3 上传函数 ===
const uploadToS3 = async function (filePath: string, suffix = '.jpg', dir = 'cheflink/'): Promise<string> {
if (!filePath) {
uni.showToast({icon: 'none', title: i18n.global.t('common.prompt.picture-wrong-please-try-again')})
return Promise.reject(new Error('filePath is empty'))
}
// 获取文件信息
let fileInfo: UniApp.GetFileInfoSuccessCallbackResult
try {
fileInfo = await new Promise((resolve, reject) => {
uni.getFileInfo({
filePath,
success: resolve,
fail: reject,
})
})
} catch (err) {
uni.showToast({icon: 'none', title: i18n.global.t('common.prompt.request-incorrect')})
return Promise.reject(err)
}
// 压缩图片(如果大于 3MB
let uploadPath: string
try {
uploadPath = await compressImageIfNeeded(filePath, fileInfo.size)
} catch (err) {
uni.showToast({icon: 'none', title: i18n.global.t('common.prompt.request-incorrect')})
return Promise.reject(err)
}
// 文件名
const filename = Date.now() + Math.floor(Math.random() * 1000) + suffix
const key = dir + filename
const policyBase64 = getPolicyBase64()
const signature = getSignature(policyBase64)
const uploadUrl = `https://${S3Config.bucket}.s3.${S3Config.region}.amazonaws.com/`
return new Promise((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath: uploadPath,
name: 'file',
formData: {
key,
AWSAccessKeyId: S3Config.accessKeyId,
policy: policyBase64,
signature,
'Content-Type': 'image/jpeg',
},
success: res => {
if (res.statusCode === 204) {
uni.showToast({icon: 'none', title: i18n.global.t('common.operation-success')})
resolve(`${uploadUrl}${key}`)
} else {
uni.showToast({icon: 'none', title: i18n.global.t('common.prompt.up-failed')})
reject(new Error(`上传失败,状态码: ${res.statusCode}`))
}
},
fail: err => {
uni.showToast({
icon: 'none',
title: i18n.global.t('common.prompt.request-failed-please-try-again-later')
})
reject(err)
}
})
})
}
export {uploadToS3}
+414
View File
@@ -0,0 +1,414 @@
// 拨打电话
import {i18n} from "@/locale";
import Config from "@/config";
import {dayjs} from "@/plugin";
import {useConfigStore} from "@/store";
// 缩略图
export function thumbnailImg(url: string, width = 400, height = 400) {
if (!url || !url.startsWith("http") || !url.startsWith("https")) {
return;
}
return url + `?x-oss-process=image/resize,m_fill,w_${width},h_${height}`;
}
// 转换webp
export function formatImgWebp(url: string, width = 400, height = 400) {
if (!url || !url.startsWith("http") || !url.startsWith("https")) {
return;
}
return url + `?x-oss-process=image/interlace,1/format,webp`;
}
// 拨打电话
export function callPhone(mobile: string) {
console.log('mobile', mobile)
if (!mobile) {
return uni.showToast({
title: i18n.global.t('common.prompt.phone-number-empty'),
icon: 'none',
})
}
// #ifdef APP-PLUS
const configStore = useConfigStore()
if (configStore.isIos) {
uni.makePhoneCall({
phoneNumber: mobile.trim(),
fail(error) {
console.error(error)
},
})
} else {
// 获取宿主上下文
var main = plus.android.runtimeMainActivity();
var Uri = plus.android.importClass("android.net.Uri");
// 通过反射获取Android的Intent对象
var Intent = plus.android.importClass("android.content.Intent");
// 创建Intent,设置Action为ACTION_DIAL
var intent = new Intent("android.intent.action.DIAL");
// 设置要拨打的电话号码
intent.setData(Uri.parse("tel:" + mobile));
// 启动Intent
main.startActivity(intent);
}
// #endif
// #ifndef APP-PLUS
uni.makePhoneCall({
phoneNumber: mobile.trim(),
fail(error) {
console.error(error)
},
})
// #endif
}
// 复制到剪切板
export function setClipboardData(data: string, showToast = true) {
uni.setClipboardData({
data,
showToast: showToast,
success: function () {
uni.showToast({
title: i18n.global.t('common.prompt.replication-successful'),
icon: 'none',
})
},
})
}
// 微信客服
export function loadWeixinService() {
uni.navigateTo({
url: `/pages/web-view/index?url=${Config.weixinServiceUrl}&title=${Config.weixinServiceName}`
})
}
// 地图导航 latLng 例如:'31.1443439,121.808273'
export function navGoogleMap(latLng: string) {
console.log('latLng', latLng)
// 应用地址
let url = ''
// android 包名
let pname = ''
// ios id
let appid: number
if (plus.os.name == 'Android') {
plus.nativeUI.actionSheet({
title: i18n.global.t('common.select-maps-app'),
cancel: i18n.global.t('common.cancel'),
buttons: [{
title: i18n.global.t('common.google-map')
}]
}, function (e) {
switch (e.index) {
case 1:
url = 'google.navigation:q=' + latLng
pname = 'com.android.vending'
}
if (url) {
plus.runtime.openURL(url, function () {
plus.nativeUI.actionSheet({
title: i18n.global.t('common.app-marketplace'),
cancel: i18n.global.t('common.cancel'),
buttons: [{
title: i18n.global.t('common.app-marketplace')
}]
}, function ({
index
}) {
switch (index) {
case 1:
plus.runtime.openURL('market://details?id=' + pname,
function () {
plus.nativeUI.alert(i18n.global.t('common.prompt.installed-specified-app'));
})
}
})
});
}
});
} else {
plus.nativeUI.actionSheet({
title: i18n.global.t('common.select-maps-app'),
cancel: i18n.global.t('common.cancel'),
buttons: [{
title: i18n.global.t('common.google-map')
}]
}, function (e) {
switch (e.index) {
case 1:
url = 'comgooglemaps://?saddr=&daddr=' + latLng
appid = 585027354
}
if (url) {
plus.runtime.launchApplication({action: url}, function (err) {
plus.nativeUI.actionSheet({
title: i18n.global.t('common.app-marketplace'),
cancel: i18n.global.t('common.cancel'),
buttons: [{
title: i18n.global.t('common.app-marketplace')
}]
}, function ({
index
}) {
switch (index) {
case 1:
plus.runtime.openURL(
`itms-apps://itunes.apple.com/cn/app/id${appid}?mt=8')`,
function () {
plus.nativeUI.alert(i18n.global.t('common.prompt.installed-specified-app'));
})
}
})
});
}
});
}
}
/**
* 将时间戳格式化为 "MMM.DD YYYY" 格式(例如:Mar.06 2025
* @param {number} timestamp - 毫秒级时间戳
* @returns {string} 格式化后的日期字符串
*/
export function formatTimestamp(timestamp: number): string {
return dayjs(Number(timestamp))
.format('MMM.DD YYYY') // 核心格式化指令
.replace(/\b([a-z])/, match => match.toUpperCase()); // 确保月份首字母大写
}
/**
* 将时间戳格式化为 "YYYY.MM.DD HH:mm 星期几" 格式
* 星期几根据当前语言环境显示(中文:周一,英文:Mon)
* 例如:2025.08.22 14:30 Fri 或 2025.08.22 14:30 周五
* @param {number} timestamp - 毫秒级时间戳
* @returns {string} 格式化后的日期时间字符串
*/
export function formatTimestampWithWeekday(timestamp: number): string {
const locale = uni.getLocale()
const date = dayjs(Number(timestamp))
// 设置对应的语言环境
if (locale === 'zh-Hans') {
date.locale('zh-cn')
} else {
date.locale('en')
}
// 获取星期几的简写
const weekdayShort = date.format('ddd')
return `${date.format('YYYY.MM.DD HH:mm')} ${weekdayShort}`
}
/**
* 将时间戳格式化为带月份名称的格式(如:June 3, 2025 12:25
* 根据当前语言环境显示中文或英文月份
* @param {number} timestamp - 时间戳(毫秒)
* @returns {string} 格式化后的时间字符串
*/
export function formatTimestampWithMonthName(timestamp: number): string {
const locale = uni.getLocale()
const date = dayjs(Number(timestamp))
// 设置对应的语言环境
if (locale === 'zh-Hans') {
date.locale('zh-cn')
// 中文格式:2025年6月3日 12:25
return date.format('YYYY年M月D日 HH:mm')
} else {
date.locale('en')
// 英文格式:June 3, 2025 12:25
return date.format('MMMM D, YYYY HH:mm')
}
}
/**
* 将时间戳格式化为简短的月日时间格式(如:Jun 3 19:00)
* 根据当前语言环境显示中文或英文月份
* @param {number} timestamp - 时间戳(毫秒)
* @returns {string} 格式化后的时间字符串
*/
export function formatTimestampShort(timestamp: number): string {
const locale = uni.getLocale()
const date = dayjs(Number(timestamp))
// 设置对应的语言环境
if (locale === 'zh-Hans') {
date.locale('zh-cn')
// 中文格式:6月3日 19:00
return date.format('M月D日 HH:mm')
} else {
date.locale('en')
// 英文格式:Jun 3 19:00
return date.format('MMM D HH:mm')
}
}
/**
* 计算两个经纬度之间的球面距离(单位:英里)
* @param {number} lat1 - 第一个点的纬度(十进制度)
* @param {number} lon1 - 第一个点的经度(十进制度)
* @param {number} lat2 - 第二个点的纬度(十进制度)
* @param {number} lon2 - 第二个点的经度(十进制度)
* @returns {number} 距离(英里)
*/
export function getDistanceInMiles(lat1: number, lon1: number, lat2: number, lon2: number): string {
const toRad = (angle: number) => angle * Math.PI / 180;
const R = 6371000; // 地球半径,单位:米
const metersToMiles = 0.000621371; // 米到英里的转换系数
const φ1 = toRad(lat1);
const φ2 = toRad(lat2);
const Δφ = toRad(lat2 - lat1);
const Δλ = toRad(lon2 - lon1);
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const meters = R * c;
return (meters * metersToMiles).toFixed(2);
}
/**
* 解析商家营业时间字符串
* @param businessHours 营业时间字符串,格式如:"MONDAY/TUESDAY/WEDNESDAY/THURSDAY/FRIDAY/SATURDAY/SUNDAY 05:00-16:00"
* @returns 格式化后的营业时间,如:"周一,周二,周三,周四,周五,周六,周日 05:00-16:00" 或 "Mon,Tue,Wed,Thu,Fri,Sat,Sun 05:00-16:00"
*/
export function parseBusinessHoursUtils(businessHours: string): string {
if (!businessHours || !businessHours.trim()) {
return '';
}
const locale = uni.getLocale()
// 英文星期到中文和英文简写的映射
const weekMapChinese: { [key: string]: string } = {
'MONDAY': '周一',
'TUESDAY': '周二',
'WEDNESDAY': '周三',
'THURSDAY': '周四',
'FRIDAY': '周五',
'SATURDAY': '周六',
'SUNDAY': '周日'
};
const weekMapEnglish: { [key: string]: string } = {
'MONDAY': 'Mon',
'TUESDAY': 'Tue',
'WEDNESDAY': 'Wed',
'THURSDAY': 'Thu',
'FRIDAY': 'Fri',
'SATURDAY': 'Sat',
'SUNDAY': 'Sun'
};
// 根据语言环境选择映射表
const weekMap = locale === 'en' ? weekMapEnglish : weekMapChinese;
const weekOrder = locale === 'en'
? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const separator = locale === 'en' ? '-' : '-';
try {
// 分割星期和时间部分
const parts = businessHours.trim().split(' ');
if (parts.length < 2) {
return businessHours; // 格式不正确,返回原字符串
}
const weekPart = parts[0]; // 星期部分
const timePart = parts.slice(1).join(' '); // 时间部分(可能有多个时间段)
// 解析星期
const weekDays = weekPart.split('/');
const translatedWeeks = weekDays
.map(day => weekMap[day.toUpperCase()])
.filter(day => day); // 过滤掉无效的星期
if (translatedWeeks.length === 0) {
return businessHours; // 没有有效的星期,返回原字符串
}
// 处理连续的星期(如周一到周五)
let weekString = '';
if (translatedWeeks.length > 2) {
// 检查是否为连续的星期
const indices = translatedWeeks.map(week => weekOrder.indexOf(week)).filter(index => index !== -1);
if (indices.length > 1) {
indices.sort((a, b) => a - b);
// 检查是否连续
let isContinuous = true;
for (let i = 1; i < indices.length; i++) {
if (indices[i] !== indices[i-1] + 1) {
isContinuous = false;
break;
}
}
if (isContinuous && indices.length > 2) {
weekString = `${weekOrder[indices[0]]}${separator}${weekOrder[indices[indices.length - 1]]}`;
} else {
weekString = translatedWeeks.join(',');
}
} else {
weekString = translatedWeeks.join(',');
}
} else {
weekString = translatedWeeks.join(',');
}
return `${weekString} ${timePart}`;
} catch (error) {
console.error('解析营业时间出错:', error);
return businessHours; // 出错时返回原字符串
}
}
/**
* 安全获取多语言文本并做占位符替换
* - 不经过 t()
* - 不触发 vue-i18n 插值
* - 全平台稳定
*/
export function tWithParams(
key: string,
params: Record<string, any> = {}
) {
const locale =
typeof i18n.global.locale === 'string'
? i18n.global.locale
: i18n.global.locale.value
const messages = i18n.global.getLocaleMessage(locale)
if (!messages) return key
// 支持 a.b.c 深层 key
let str: any = key.split('.').reduce((obj, k) => obj?.[k], messages)
if (typeof str !== 'string') {
// fallback:交给 i18n 自己处理
return i18n.global.t(key, params)
}
// 使用 split + join(比正则更安全)
Object.keys(params).forEach(k => {
const val = params[k] ?? ''
str = str.split(`{${k}}`).join(String(val))
})
return str
}