diff --git a/lib/entities/ExtraFile.d.ts b/lib/entities/ExtraFile.d.ts index 8387b0ab5..9a64206f6 100644 --- a/lib/entities/ExtraFile.d.ts +++ b/lib/entities/ExtraFile.d.ts @@ -1,4 +1,4 @@ -import { String, Text } from 'oak-domain/lib/types/DataType'; +import { String, Int, Text } from 'oak-domain/lib/types/DataType'; import { FileCarrierEntityShape } from 'oak-domain/lib/types/Entity'; export interface Schema extends FileCarrierEntityShape { origin: 'qiniu' | 'unknown'; @@ -12,4 +12,6 @@ export interface Schema extends FileCarrierEntityShape { entity: String<32>; entityId: String<64>; extra1?: Text; + extension: String<16>; + size?: Int<4>; } diff --git a/lib/features/extraFile.js b/lib/features/extraFile.js index 9725e1b96..93709c473 100644 --- a/lib/features/extraFile.js +++ b/lib/features/extraFile.js @@ -15,7 +15,9 @@ class ExtraFile extends oak_frontend_base_1.Feature { } async upload(extraFile, scene) { try { - const { origin, extra1: filePath, filename: fileName } = extraFile; + const { origin, extra1: filePath, filename, objectId, extension, entity, } = extraFile; + // 构造文件上传所需的fileName + const fileName = `${entity}/${objectId}.${extension}`; const uploadInfo = await this.getAspectProxy().getUploadInfo({ origin, fileName, diff --git a/lib/utils/externalUpload/qiniu.d.ts b/lib/utils/externalUpload/qiniu.d.ts index 07a79ce54..fdf5b9a0a 100644 --- a/lib/utils/externalUpload/qiniu.d.ts +++ b/lib/utils/externalUpload/qiniu.d.ts @@ -11,7 +11,7 @@ export default class qiniuInstance { bucket: string; domain: string; }); - getUploadInfo(fileName: string): Promise<{ + getUploadInfo(key: string): Promise<{ key: string; uploadToken: string; uploadHost: string; diff --git a/lib/utils/externalUpload/qiniu.js b/lib/utils/externalUpload/qiniu.js index 619cd7e4c..f5406c8d9 100644 --- a/lib/utils/externalUpload/qiniu.js +++ b/lib/utils/externalUpload/qiniu.js @@ -19,10 +19,9 @@ class qiniuInstance { this.bucket = bucket; this.domain = domain; } - async getUploadInfo(fileName) { + async getUploadInfo(key) { try { const { uploadHost, domain, bucket } = this; - const key = `${Date.now()}/${fileName}`; const scope = `${bucket}:${key}`; const uploadToken = this.getToken(scope); return { diff --git a/lib/utils/extraFile.d.ts b/lib/utils/extraFile.d.ts index 0c54ac062..134afb5fc 100644 --- a/lib/utils/extraFile.d.ts +++ b/lib/utils/extraFile.d.ts @@ -1,3 +1,3 @@ import { OpSchema as ExtraFile } from 'oak-app-domain/ExtraFile/Schema'; -export declare function composeFileUrl(extraFile: Pick): string; +export declare function composeFileUrl(extraFile: Pick): string; export declare function decomposeFileUrl(url: string): Pick; diff --git a/lib/utils/extraFile.js b/lib/utils/extraFile.js index 00455efba..fc07ae100 100644 --- a/lib/utils/extraFile.js +++ b/lib/utils/extraFile.js @@ -2,12 +2,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.decomposeFileUrl = exports.composeFileUrl = void 0; function composeFileUrl(extraFile) { - const { type, bucket, filename, origin, extra1 } = extraFile; + const { type, bucket, filename, origin, extra1, objectId, extension, entity } = extraFile; if (extra1) { // 有extra1就用extra1 return extra1; } - return ''; + // 缺少https和域名 + return `${entity}/${objectId}.${extension}`; } exports.composeFileUrl = composeFileUrl; function decomposeFileUrl(url) { diff --git a/src/entities/ExtraFile.ts b/src/entities/ExtraFile.ts index 9f8fc350e..14af11876 100644 --- a/src/entities/ExtraFile.ts +++ b/src/entities/ExtraFile.ts @@ -13,4 +13,6 @@ export interface Schema extends FileCarrierEntityShape { entity: String<32>; entityId: String<64>; extra1?: Text; + extension: String<16>; + size?: Int<4>; }; diff --git a/src/features/extraFile.ts b/src/features/extraFile.ts index 4877a5d67..9a064655c 100644 --- a/src/features/extraFile.ts +++ b/src/features/extraFile.ts @@ -15,25 +15,36 @@ export class ExtraFile< @Action async upload(extraFile: DeduceCreateOperationData, scene: string) { try { - const { origin, extra1: filePath, filename: fileName } = extraFile; - const uploadInfo = - await this.getAspectProxy().getUploadInfo( - { - origin, - fileName, - }, - scene - ); + const { + origin, + extra1: filePath, + filename, + objectId, + extension, + entity, + } = extraFile; + // 构造文件上传所需的fileName + const fileName = `${entity}/${objectId}.${extension}`; + const uploadInfo = await this.getAspectProxy().getUploadInfo( + { + origin, + fileName, + }, + scene + ); if (process.env.OAK_PLATFORM === 'wechatMp') { // 微信小程序使用wx.uploadFile, 封装upload,上传源为origin - const up = new Upload(); - const result = await up.uploadFile(origin, filePath!, uploadInfo); + const up = new Upload(); + const result = await up.uploadFile( + origin, + filePath!, + uploadInfo + ); return result; - } - else { + } else { throw new Error('not implemented yet'); - } + } } catch (err) { throw err; } diff --git a/src/utils/externalUpload/qiniu.ts b/src/utils/externalUpload/qiniu.ts index b3d69c796..a83ec9f9a 100644 --- a/src/utils/externalUpload/qiniu.ts +++ b/src/utils/externalUpload/qiniu.ts @@ -22,10 +22,9 @@ export default class qiniuInstance { this.domain = domain; } - async getUploadInfo(fileName: string) { + async getUploadInfo(key: string) { try { const { uploadHost, domain, bucket } = this; - const key = `${Date.now()}/${fileName}`; const scope = `${bucket}:${key}`; const uploadToken = this.getToken(scope); return { @@ -47,10 +46,13 @@ export default class qiniuInstance { deadline: 3600 + Math.floor(Date.now() / 1000), }; // 构造凭证 - const encodedFlags = this.urlSafeBase64Encode(JSON.stringify(putPolicy)); + const encodedFlags = this.urlSafeBase64Encode( + JSON.stringify(putPolicy) + ); const encoded = this.hmacSha1(encodedFlags, this.secretKey); const encodedSign = this.base64ToUrlSafe(encoded); - const uploadToken = this.accessKey + ':' + encodedSign + ':' + encodedFlags; + const uploadToken = + this.accessKey + ':' + encodedSign + ':' + encodedFlags; return uploadToken; } diff --git a/src/utils/extraFile.ts b/src/utils/extraFile.ts index 0ba01f9eb..25953cf1e 100644 --- a/src/utils/extraFile.ts +++ b/src/utils/extraFile.ts @@ -1,12 +1,26 @@ import { OpSchema as ExtraFile } from 'oak-app-domain/ExtraFile/Schema'; -export function composeFileUrl(extraFile: Pick) { - const { type, bucket, filename, origin, extra1 } = extraFile; +export function composeFileUrl( + extraFile: Pick< + ExtraFile, + | 'type' + | 'bucket' + | 'filename' + | 'origin' + | 'extra1' + | 'objectId' + | 'extension' + | 'entity' + > +) { + const { type, bucket, filename, origin, extra1, objectId, extension, entity } = + extraFile; if (extra1) { // 有extra1就用extra1 return extra1!; } - return ''; + // 缺少https和域名 + return `${entity}/${objectId}.${extension}`; } export function decomposeFileUrl(url: string): Pick { diff --git a/wechatMp/components/extraFile/gallery/index.ts b/wechatMp/components/extraFile/gallery/index.ts index 18b86cc01..2d82d54f7 100644 --- a/wechatMp/components/extraFile/gallery/index.ts +++ b/wechatMp/components/extraFile/gallery/index.ts @@ -71,6 +71,7 @@ OakComponent({ origin: String, tag1: String, tag2: String, + entity: String, }, methods: { @@ -100,8 +101,16 @@ OakComponent({ return (750 / windowWidth) * px; }, async onPick() { - const { selectCount, mediaType, sourceType, type, origin, tag1, tag2} = - this.data; + const { + selectCount, + mediaType, + sourceType, + type, + origin, + tag1, + tag2, + entity, + } = this.data; try { const { errMsg, tempFiles } = await wx.chooseMedia({ count: selectCount, @@ -116,30 +125,45 @@ OakComponent({ } else { await Promise.all(tempFiles.map( async (tempExtraFile) => { - const { tempFilePath, thumbTempFilePath } = tempExtraFile; + const { tempFilePath, thumbTempFilePath, fileType, size } = tempExtraFile; const filePath = tempFilePath || thumbTempFilePath; - const filename = filePath.match(/[^/]+(?!.*\/)/g)![0]; - - assert(origin === 'qiniu'); // 目前只支持七牛上传 - const ele: Parameters[1] = { - updateData: { - extra1: filePath, - origin, - type, - tag1, - tag2, - objectId: await generateNewId(), - filename: filename, - }, - beforeExecute: async (updateData) => { - const { url, bucket } = await this.features.extraFile.upload( - updateData as DeduceCreateOperationData, "extraFile:gallery:upload"); - Object.assign(updateData, { - bucket, - extra1: url, - }); - }, - }; + const fileFullName = filePath.match(/[^/]+(?!.*\/)/g)![0]; + const extension = fileFullName.substring( + fileFullName.lastIndexOf('.') + 1 + ); + const filename = fileFullName.substring( + 0, fileFullName.lastIndexOf('.') + ); + assert(entity, '必须传入entity'); + assert(origin === 'qiniu', '目前只支持七牛上传'); // 目前只支持七牛上传 + const ele: Parameters[1] = + { + updateData: { + extra1: filePath, + origin, + type: type || fileType, + tag1, + tag2, + objectId: await generateNewId(), + entity, + filename: filename, + size: size, + extension, + }, + beforeExecute: async (updateData) => { + const { url, bucket } = + await this.features.extraFile.upload( + updateData as DeduceCreateOperationData< + EntityDict['extraFile']['Schema'] + >, + 'extraFile:gallery:upload' + ); + Object.assign(updateData, { + bucket, + extra1: url, + }); + }, + }; this.pushNode(undefined, ele); } diff --git a/wechatMp/pages/token/me/index.ts b/wechatMp/pages/token/me/index.ts index 31a6606f8..e6eaa9fdf 100644 --- a/wechatMp/pages/token/me/index.ts +++ b/wechatMp/pages/token/me/index.ts @@ -1,118 +1,124 @@ import { ROOT_ROLE_ID } from '../../../../src/constants'; import { composeFileUrl } from '../../../../src/utils/extraFile'; -OakPage({ - path: 'token:me', - entity: 'token', - isList: true, - projection: { - id: 1, - userId: 1, - playerId: 1, - user: { +OakPage( + { + path: 'token:me', + entity: 'token', + isList: true, + projection: { id: 1, - nickname: 1, - name: 1, - extraFile$entity: { - $entity: 'extraFile', - data: { - id: 1, - tag1: 1, - origin: 1, - bucket: 1, - objectId: 1, - filename: 1, - extra1: 1, - type: 1, + userId: 1, + playerId: 1, + user: { + id: 1, + nickname: 1, + name: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + type: 1, + entity: 1, + extension: 1, + }, + filter: { + tag1: 'avatar', + }, + indexFrom: 0, + count: 1, }, - filter: { - tag1: 'avatar', + mobile$user: { + $entity: 'mobile', + data: { + id: 1, + mobile: 1, + }, }, - indexFrom: 0, - count: 1, }, - mobile$user: { - $entity: 'mobile', - data: { - id: 1, - mobile: 1, + player: { + id: 1, + userRole$user: { + $entity: 'userRole', + data: { + id: 1, + userId: 1, + roleId: 1, + }, }, }, }, - player: { - id: 1, - userRole$user: { - $entity: 'userRole', - data: { - id: 1, - userId: 1, - roleId: 1, - } - }, - } - }, - formData: async ({ data: [ token ] }) => { - const user = token?.user; - const player = token?.player; - const avatarFile = user && user.extraFile$entity && user.extraFile$entity[0]; - const avatar = avatarFile && composeFileUrl(avatarFile); - const nickname = user && user.nickname; - const mobileData = user && user.mobile$user && user.mobile$user[0]; - const { mobile } = mobileData || {}; - const mobileCount = user?.mobile$user?.length || 0; + formData: async ({ data: [token] }) => { + const user = token?.user; + const player = token?.player; + const avatarFile = + user && user.extraFile$entity && user.extraFile$entity[0]; + const avatar = avatarFile && composeFileUrl(avatarFile); + const nickname = user && user.nickname; + const mobileData = user && user.mobile$user && user.mobile$user[0]; + const { mobile } = mobileData || {}; + const mobileCount = user?.mobile$user?.length || 0; - const isLoggedIn = !!token; - const isPlayingAnother = token && token.userId !== token.playerId; - const isRoot = player?.userRole$user && player.userRole$user[0].roleId === ROOT_ROLE_ID; - return { - avatar, - nickname, - mobile, - mobileCount, - isLoggedIn, - isPlayingAnother, - isRoot, - }; + const isLoggedIn = !!token; + const isPlayingAnother = token && token.userId !== token.playerId; + const isRoot = + player?.userRole$user && + player.userRole$user[0].roleId === ROOT_ROLE_ID; + return { + avatar, + nickname, + mobile, + mobileCount, + isLoggedIn, + isPlayingAnother, + isRoot, + }; + }, }, -}, { - methods: { - async onRefresh() { - this.setData({ - refreshing: true, - }); - try { - await this.features.token.syncUserInfoWechatMp('token:me'); - } - catch (err) { - console.error(err); - } - this.setData({ - refreshing: false, - }); + { + methods: { + async onRefresh() { + this.setData({ + refreshing: true, + }); + try { + await this.features.token.syncUserInfoWechatMp('token:me'); + } catch (err) { + console.error(err); + } + this.setData({ + refreshing: false, + }); + }, + async doLogin() { + this.setData({ + refreshing: true, + }); + try { + await this.features.token.loginWechatMp('token:me'); + } catch (err) { + console.error(err); + } + this.setData({ + refreshing: false, + }); + }, + goMyMobile() { + this.navigateTo({ + url: '../../mobile/me/index', + }); + }, + goUserManage() { + this.navigateTo({ + url: '../../user/manage/index', + }); + }, }, - async doLogin() { - this.setData({ - refreshing: true, - }); - try { - await this.features.token.loginWechatMp('token:me'); - } - catch (err) { - console.error(err); - } - this.setData({ - refreshing: false, - }); - }, - goMyMobile() { - this.navigateTo({ - url: '../../mobile/me/index', - }); - }, - goUserManage() { - this.navigateTo({ - url: '../../user/manage/index', - }); - } } -}); \ No newline at end of file +); \ No newline at end of file diff --git a/wechatMp/pages/user/manage/detail/index.ts b/wechatMp/pages/user/manage/detail/index.ts index 722d4882e..4712952a6 100644 --- a/wechatMp/pages/user/manage/detail/index.ts +++ b/wechatMp/pages/user/manage/detail/index.ts @@ -2,150 +2,175 @@ import { composeFileUrl } from "../../../../../src/utils/extraFile"; -OakPage({ - path: 'user:manage:detail', - entity: 'user', - projection: { - id: 1, - nickname: 1, - name: 1, - userState: 1, - idState: 1, - extraFile$entity: { - $entity: 'extraFile', - data: { - id: 1, - tag1: 1, - origin: 1, - bucket: 1, - objectId: 1, - filename: 1, - extra1: 1, - type: 1, +OakPage( + { + path: 'user:manage:detail', + entity: 'user', + projection: { + id: 1, + nickname: 1, + name: 1, + userState: 1, + idState: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + type: 1, + entity: 1, + extension: 1, + }, + filter: { + tag1: 'avatar', + }, + indexFrom: 0, + count: 1, }, - filter: { - tag1: 'avatar', - }, - indexFrom: 0, - count: 1, - }, - mobile$user: { - $entity: 'mobile', - data: { - id: 1, - mobile: 1, + mobile$user: { + $entity: 'mobile', + data: { + id: 1, + mobile: 1, + }, }, }, + isList: false, + formData: async ({ data: user }) => { + const { + id, + nickname, + idState, + userState, + name, + mobile$user, + extraFile$entity, + } = user || {}; + const mobile = mobile$user && mobile$user[0]?.mobile; + const avatar = + extraFile$entity && + extraFile$entity[0] && + composeFileUrl(extraFile$entity[0]); + return { + id, + nickname, + name, + mobile, + avatar, + userState, + idState, + }; + }, + actions: [ + 'accept', + 'activate', + 'disable', + 'enable', + 'remove', + 'update', + 'verify', + 'play', + ], }, - isList: false, - formData: async ({ data: user }) => { - const { id, nickname, idState, userState, name, mobile$user, extraFile$entity } = user || {}; - const mobile = mobile$user && mobile$user[0]?.mobile; - const avatar = extraFile$entity && extraFile$entity[0] && composeFileUrl(extraFile$entity[0]); - return { - id, - nickname, - name, - mobile, - avatar, - userState, - idState, - }; - }, - actions: ['accept', 'activate', 'disable', 'enable', 'remove', 'update', 'verify', 'play'], -}, { - data: { - show: false, - actionDescriptions: { - accept: { - icon: { - name: 'pan_tool', + { + data: { + show: false, + actionDescriptions: { + accept: { + icon: { + name: 'pan_tool', + }, + label: '通过', + }, + activate: { + icon: { + name: 'check', + }, + label: '激活', + }, + disable: { + icon: { + name: 'flash_off', + }, + label: '禁用', + }, + enable: { + icon: { + name: 'flash_on', + }, + label: '启用', + }, + remove: { + icon: { + name: 'clear', + }, + label: '删除', + }, + update: { + icon: { + name: 'edit', + }, + label: '更新', + }, + verify: { + icon: { + name: 'how_to_reg', + }, + label: '验证', + }, + play: { + icon: { + name: 'play_circle', + }, + label: '切换', }, - label: '通过', }, - activate: { - icon: { - name: 'check', - }, - label: '激活', - }, - disable: { - icon: { - name: 'flash_off', - }, - label: '禁用', - }, - enable: { - icon: { - name: 'flash_on', - }, - label: '启用', - }, - remove: { - icon: { - name: 'clear', - }, - label: '删除', - }, - update: { - icon: { - name: 'edit', - }, - label: '更新', - }, - verify: { - icon: { - name: 'how_to_reg', - }, - label: '验证', - }, - play: { - icon: { - name: 'play_circle', - }, - label: '切换', - } - } - }, - methods: { - openDrawer() { - this.setData({ - show: true, - }); }, - closeDrawer() { - this.setData({ - show: false, - }); - }, - async onActionClick({ detail }: WechatMiniprogram.CustomEvent) { - const { action } = detail; - switch (action) { - case 'update': { - this.navigateTo({ - url: '../upsert/index', - oakId: this.data.oakId, - }); - return; - } - case 'enable': - case 'disable': - case 'accept': - case 'verify': - case 'activate': - case 'play': { - await this.execute(action); - break; - } - default: { - console.error(`尚未实现的action: ${action}`) - } - } - if (action === 'play') { - wx.navigateBack({ - delta: 2, + methods: { + openDrawer() { + this.setData({ + show: true, }); - } - } + }, + closeDrawer() { + this.setData({ + show: false, + }); + }, + async onActionClick({ detail }: WechatMiniprogram.CustomEvent) { + const { action } = detail; + switch (action) { + case 'update': { + this.navigateTo({ + url: '../upsert/index', + oakId: this.data.oakId, + }); + return; + } + case 'enable': + case 'disable': + case 'accept': + case 'verify': + case 'activate': + case 'play': { + await this.execute(action); + break; + } + default: { + console.error(`尚未实现的action: ${action}`); + } + } + if (action === 'play') { + wx.navigateBack({ + delta: 2, + }); + } + }, + }, } -}); \ No newline at end of file +); \ No newline at end of file diff --git a/wechatMp/pages/user/manage/index.ts b/wechatMp/pages/user/manage/index.ts index 6836b28a2..2909d389b 100644 --- a/wechatMp/pages/user/manage/index.ts +++ b/wechatMp/pages/user/manage/index.ts @@ -2,47 +2,59 @@ import { composeFileUrl } from "../../../../src/utils/extraFile"; -OakPage({ - path: 'user:manage', - entity: 'user', - projection: { - id: 1, - nickname: 1, - name: 1, - userState: 1, - extraFile$entity: { - $entity: 'extraFile', - data: { - id: 1, - tag1: 1, - origin: 1, - bucket: 1, - objectId: 1, - filename: 1, - extra1: 1, - type: 1, +OakPage( + { + path: 'user:manage', + entity: 'user', + projection: { + id: 1, + nickname: 1, + name: 1, + userState: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + type: 1, + entity: 1, + extension: 1, + }, + filter: { + tag1: 'avatar', + }, + indexFrom: 0, + count: 1, }, - filter: { - tag1: 'avatar', - }, - indexFrom: 0, - count: 1, - }, - mobile$user: { - $entity: 'mobile', - data: { - id: 1, - mobile: 1, + mobile$user: { + $entity: 'mobile', + data: { + id: 1, + mobile: 1, + }, }, }, - }, - isList: true, - formData: async ({ data: users }) => { - const userData = users.map( - (user) => { - const { id, nickname, userState, name, mobile$user, extraFile$entity } = user || {}; + isList: true, + formData: async ({ data: users }) => { + const userData = users.map((user) => { + const { + id, + nickname, + userState, + name, + mobile$user, + extraFile$entity, + } = user || {}; const mobile = mobile$user && mobile$user[0]?.mobile; - const avatar = extraFile$entity && extraFile$entity[0] && composeFileUrl(extraFile$entity[0]); + const avatar = + extraFile$entity && + extraFile$entity[0] && + composeFileUrl(extraFile$entity[0]); return { id, nickname, @@ -51,25 +63,26 @@ OakPage({ avatar, userState, }; - } - ); - return { - userData, - }; - }, -}, { - methods: { - goUserManageDetail(options: WechatMiniprogram.Touch) { - const { id } = options.currentTarget.dataset; - this.navigateTo({ - url: 'detail/index', - oakId: id, }); + return { + userData, + }; }, - goNewUser() { - this.navigateTo({ - url: 'upsert/index', - }); + }, + { + methods: { + goUserManageDetail(options: WechatMiniprogram.Touch) { + const { id } = options.currentTarget.dataset; + this.navigateTo({ + url: 'detail/index', + oakId: id, + }); + }, + goNewUser() { + this.navigateTo({ + url: 'upsert/index', + }); + }, }, } -}); \ No newline at end of file +); \ No newline at end of file diff --git a/wechatMp/pages/userEntityGrant/detail/index.ts b/wechatMp/pages/userEntityGrant/detail/index.ts index 3bf1b4050..8f23734d3 100644 --- a/wechatMp/pages/userEntityGrant/detail/index.ts +++ b/wechatMp/pages/userEntityGrant/detail/index.ts @@ -32,22 +32,20 @@ OakPage({ }, isList: false, formData: async ({ data: userEntityGrant }) => { - let qrcodeUrl; + let qrCodeUrl; const str = userEntityGrant?.wechatQrCode$entity[0]?.buffer; - console.log('str', str); if (str) { const buf = new ArrayBuffer(str.length * 2); const buf2 = new Uint16Array(buf); for (let i = 0; i < str.length; i++) { buf2[i] = str.charCodeAt(i); } - qrcodeUrl = 'data:image/jpeg;base64,' + wx.arrayBufferToBase64(buf2); - console.log('url', qrcodeUrl); + qrCodeUrl = 'data:image/jpeg;base64,' + wx.arrayBufferToBase64(buf2); } return { relation: userEntityGrant?.relation, entity: userEntityGrant?.entity, - url: qrcodeUrl || userEntityGrant?.wechatQrCode$entity[0]?.url + url: qrCodeUrl || userEntityGrant?.wechatQrCode$entity[0]?.url, }; }, }, {