+
+
+ {{ Math.round(imageScale * 100) }}%
+
+ */
+export async function getElementRect(
+ element?: HTMLElement | null,
+ selector?: string,
+ context?: any,
+): Promise {
+ // 优先使用 getBoundingClientRect(H5环境)
+ if (element && typeof element.getBoundingClientRect === 'function') {
+ const rect = element.getBoundingClientRect()
+ return {
+ left: rect.left,
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ width: rect.width,
+ height: rect.height,
+ x: rect.x,
+ y: rect.y,
+ }
+ }
+
+ // 如果element存在但没有getBoundingClientRect,尝试使用offset属性
+ if (element) {
+ // 获取元素位置(需要遍历父元素累加offset)
+ let left = 0
+ let top = 0
+ let current: HTMLElement | null = element
+
+ while (current) {
+ left += current.offsetLeft || 0
+ top += current.offsetTop || 0
+ current = current.offsetParent as HTMLElement | null
+ }
+
+ const width = element.offsetWidth || 0
+ const height = element.offsetHeight || 0
+
+ return {
+ left,
+ top,
+ right: left + width,
+ bottom: top + height,
+ width,
+ height,
+ x: left,
+ y: top,
+ }
+ }
+
+ // uniapp模式:使用 uni.createSelectorQuery
+ if (selector && context) {
+ return new Promise((resolve, reject) => {
+ const query = uni.createSelectorQuery().in(context)
+ query.select(selector).boundingClientRect((rect: any) => {
+ if (rect) {
+ resolve({
+ left: rect.left,
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ width: rect.width,
+ height: rect.height,
+ x: rect.left,
+ y: rect.top,
+ })
+ }
+ else {
+ reject(new Error('Failed to get element rect'))
+ }
+ }).exec()
+ })
+ }
+
+ // 如果都不满足,返回默认值
+ return Promise.reject(new Error('Unable to get element rect: element, selector, or context is required'))
+}
+
+/**
+ * 同步获取元素位置(仅H5环境,使用offset属性)
+ * 在uniapp app模式下不可靠,建议使用异步的 getElementRect
+ */
+export function getElementRectSync(element: HTMLElement | null): ElementRect | null {
+ if (!element) {
+ return null
+ }
+
+ // 优先使用 getBoundingClientRect
+ if (typeof element.getBoundingClientRect === 'function') {
+ const rect = element.getBoundingClientRect()
+ return {
+ left: rect.left,
+ top: rect.top,
+ right: rect.right,
+ bottom: rect.bottom,
+ width: rect.width,
+ height: rect.height,
+ x: rect.x,
+ y: rect.y,
+ }
+ }
+
+ // 备选方案:使用offset属性
+ let left = 0
+ let top = 0
+ let current: HTMLElement | null = element
+
+ while (current) {
+ left += current.offsetLeft || 0
+ top += current.offsetTop || 0
+ current = current.offsetParent as HTMLElement | null
+ }
+
+ const width = element.offsetWidth || 0
+ const height = element.offsetHeight || 0
+
+ return {
+ left,
+ top,
+ right: left + width,
+ bottom: top + height,
+ width,
+ height,
+ x: left,
+ y: top,
+ }
+}
diff --git a/src/utils/image.ts b/src/utils/image.ts
new file mode 100644
index 0000000..0c24bed
--- /dev/null
+++ b/src/utils/image.ts
@@ -0,0 +1,106 @@
+/**
+ * 解析OSS图片URL中的尺寸信息
+ * 支持的URL格式: ?x-oss-process=image/rotate,90/crop,w_1277,h_267,x_204,y_1151
+ */
+export interface OSSImageInfo {
+ /** 图片宽度(考虑旋转后的实际显示宽度) */
+ width: number
+ /** 图片高度(考虑旋转后的实际显示高度) */
+ height: number
+ /** 裁剪信息 */
+ crop?: {
+ x: number
+ y: number
+ width: number
+ height: number
+ }
+}
+
+/**
+ * 从OSS URL中解析图片尺寸信息
+ * @param url OSS图片URL
+ * @returns 图片尺寸信息,如果无法解析则返回null
+ */
+export function parseOSSImageSize(url: string): OSSImageInfo | null {
+ try {
+ // 从URL中提取查询参数部分
+ const queryIndex = url.indexOf('?')
+ if (queryIndex === -1) {
+ return null
+ }
+
+ const queryString = url.substring(queryIndex + 1)
+
+ // 手动解析查询参数
+ const params = new Map()
+ const pairs = queryString.split('&')
+ for (const pair of pairs) {
+ const [key, value] = pair.split('=')
+ if (key && value) {
+ params.set(decodeURIComponent(key), decodeURIComponent(value))
+ }
+ }
+
+ const processParam = params.get('x-oss-process')
+ if (!processParam) {
+ return null
+ }
+
+ // 解析 x-oss-process 参数
+ // 格式: image/rotate,90/crop,w_1277,h_267,x_204,y_1151
+ const parts = processParam.split('/')
+
+ let rotateAngle = 0
+ let cropInfo: { x: number, y: number, width: number, height: number } | undefined
+
+ for (const part of parts) {
+ // 解析旋转角度
+ if (part.startsWith('rotate,')) {
+ const angle = Number.parseInt(part.replace('rotate,', ''), 10)
+ if (!Number.isNaN(angle)) {
+ rotateAngle = angle
+ }
+ }
+
+ // 解析裁剪信息
+ if (part.startsWith('crop,')) {
+ const cropParams = part.replace('crop,', '').split(',')
+ const cropData: Record = {}
+
+ for (const param of cropParams) {
+ const [key, value] = param.split('_')
+ if (key && value) {
+ const numValue = Number.parseInt(value, 10)
+ if (!Number.isNaN(numValue)) {
+ cropData[key] = numValue
+ }
+ }
+ }
+
+ if (cropData.w && cropData.h) {
+ cropInfo = {
+ x: cropData.x || 0,
+ y: cropData.y || 0,
+ width: cropData.w,
+ height: cropData.h,
+ }
+ }
+ }
+ }
+
+ // 如果没有裁剪信息,无法确定尺寸
+ if (!cropInfo) {
+ return null
+ }
+
+ return {
+ width: cropInfo.width,
+ height: cropInfo.height,
+ crop: cropInfo,
+ }
+ }
+ catch (error) {
+ console.warn('Failed to parse OSS image URL:', error)
+ return null
+ }
+}