提交 8cb96c9e 作者: 方治民

Merge branch '3.x' of https://gitlab.yiring.com/basic/basic-uniapp-v3 into examples

{ {
"name": "basic-app", "name": "basic-app",
"version": "3.0.3.0", "version": "1.0.0",
"description": "APP 基础工程", "description": "APP 基础工程",
"keywords": [ "keywords": [
"app", "app",
...@@ -66,24 +66,24 @@ ...@@ -66,24 +66,24 @@
} }
}, },
"dependencies": { "dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-app": "3.0.0-3081220230817001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-app-plus": "3.0.0-3081220230817001",
"@dcloudio/uni-components": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-components": "3.0.0-3081220230817001",
"@dcloudio/uni-h5": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-h5": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-alipay": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-baidu": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-jd": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-jd": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-kuaishou": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-lark": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-lark": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-qq": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-qq": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-toutiao": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-mp-weixin": "3.0.0-3081220230817001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-quickapp-webview": "3.0.0-3081220230817001",
"@dcloudio/uni-ui": "^1.4.28", "@dcloudio/uni-ui": "^1.4.28",
"@faker-js/faker": "^8.0.2", "@faker-js/faker": "^8.0.2",
"@vue/runtime-core": "~3.2.47", "@vue/runtime-core": "~3.2.47",
"@vueuse/core": "^10.3.0", "@vueuse/core": "^10.4.1",
"axios": "^1.4.0", "axios": "^1.5.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
...@@ -99,16 +99,16 @@ ...@@ -99,16 +99,16 @@
"vue-types": "^5.1.1" "vue-types": "^5.1.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.40.2", "@antfu/eslint-config": "^0.41.0",
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"@dcloudio/types": "^3.3.3", "@dcloudio/types": "^3.3.3",
"@dcloudio/uni-automator": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-automator": "3.0.0-3081220230817001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-cli-shared": "3.0.0-3081220230817001",
"@dcloudio/uni-helper-json": "^1.0.13", "@dcloudio/uni-helper-json": "^1.0.13",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3081220230802001", "@dcloudio/uni-stacktracey": "3.0.0-3081220230817001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3081220230802001", "@dcloudio/vite-plugin-uni": "3.0.0-3081220230817001",
"@iconify/json": "^2.2.101", "@iconify/json": "^2.2.109",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/lodash-es": "^4.17.8", "@types/lodash-es": "^4.17.8",
"@types/mapbox-gl": "^2.7.13", "@types/mapbox-gl": "^2.7.13",
...@@ -116,17 +116,17 @@ ...@@ -116,17 +116,17 @@
"@types/prettier": "^2.7.3", "@types/prettier": "^2.7.3",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/stompjs": "^2.3.5", "@types/stompjs": "^2.3.5",
"@types/urijs": "^1.19.19", "@types/urijs": "^1.19.20",
"@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.3.0", "@typescript-eslint/parser": "^6.5.0",
"commitizen": "^4.3.0", "commitizen": "^4.3.0",
"conventional-changelog-cli": "^3.0.0", "conventional-changelog-cli": "^4.0.0",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0", "cz-customizable": "^7.0.0",
"cz-git": "^1.7.1", "cz-git": "^1.7.1",
"czg": "^1.7.1", "czg": "^1.7.1",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.47.0", "eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
...@@ -139,23 +139,23 @@ ...@@ -139,23 +139,23 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"pont-engine": "^1.5.12", "pont-engine": "^1.5.12",
"postcss": "^8.4.27", "postcss": "^8.4.29",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"prettier": "^3.0.1", "prettier": "^3.0.3",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"sass": "^1.65.1", "sass": "^1.66.1",
"sort-package-json": "^2.5.1", "sort-package-json": "^2.5.1",
"stylelint": "^15.10.2", "stylelint": "^15.10.3",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0", "stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3", "stylelint-order": "^6.0.3",
"typescript": "~5.1.6", "typescript": "~5.1.6",
"unocss": "^0.54.3", "unocss": "^0.55.4",
"unocss-preset-weapp": "^0.54.0", "unocss-preset-weapp": "^0.55.2",
"unplugin-auto-import": "^0.16.6", "unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.1", "unplugin-vue-components": "^0.25.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vue-eslint-parser": "^9.3.1" "vue-eslint-parser": "^9.3.1"
}, },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<script setup lang="ts"> <script setup lang="ts">
import { checkUpgrade } from '@/utils/upgrade'
// import * as Push from '@/utils/push' // import * as Push from '@/utils/push'
onLaunch(() => { onLaunch(() => {
console.log('App Launch') console.log('App Launch')
// 版本更新检查
checkUpgrade()
// 清除消息角标 // 清除消息角标
// Push.setBadge(0) // Push.setBadge(0)
...@@ -47,11 +42,4 @@ ...@@ -47,11 +42,4 @@
// 全局样式 // 全局样式
@import url('./common/public.less'); @import url('./common/public.less');
/** #ifdef H5 */
uni-page-body,
page {
height: 100%;
}
/** #endif */
</style> </style>
...@@ -4,7 +4,8 @@ page { ...@@ -4,7 +4,8 @@ page {
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: 500;
color: var(--fui-color-title, #181818); color: var(--fui-color-title, #181818);
font-family: -apple-system-font, 'Helvetica Neue', Helvetica, sans-serif; font-family: -apple-system-font, BlinkMacSystemFont, 'Droid Sans', 'Noto Sans', 'PingFang SC', 'Heiti SC',
'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', SimSun, sans-serif;
} }
/* #endif */ /* #endif */
......
...@@ -68,7 +68,7 @@ export const Message = { ...@@ -68,7 +68,7 @@ export const Message = {
// #ifdef APP-PLUS // #ifdef APP-PLUS
plus.nativeUI.confirm(content, ({ index }) => resolve(index === 0), { plus.nativeUI.confirm(content, ({ index }) => resolve(index === 0), {
title, title,
buttons: ['确认', '取消'], buttons: [confirmText, '取消'],
verticalAlign: 'center', verticalAlign: 'center',
}) })
// #endif // #endif
......
import { convertKB } from '@/utils/file'
/**
* 图片 Hash 缓存标识前缀
*/
export const CACHE_PREFIX = 'G_CACHE_IMAGE__'
/**
* 计算缓存大小
* @returns 字节
*/
export function calculateCacheSize() {
const storage = uni.getStorageInfoSync()
return storage.keys
.filter((key) => key.startsWith(CACHE_PREFIX))
.reduce((total, key) => {
try {
const cache = uni.getStorageSync(key)
const result = JSON.parse(cache)
total += result.size
} catch (_) {}
return total
}, 0)
}
/**
* 计算缓存大小格式化
* @returns 格式化后的缓存大小
* @example 1.2MB
* @see {@link calculateCacheSize}
* @see {@link convertKB}
* @see {@link CACHE_PREFIX}
*/
export function calculateCacheSizeFormat() {
const cacheSize = calculateCacheSize()
const { size, unit } = convertKB(cacheSize)
return `${size}${unit}`
}
/**
* 清理缓存
*/
export function cleanCache() {
const storage = uni.getStorageInfoSync()
const promises = storage.keys
.filter((key) => key.startsWith(CACHE_PREFIX))
.map((key) => {
return new Promise((resolve, reject) => {
try {
const cache = uni.getStorageSync(key)
const result = JSON.parse(cache)
uni.removeSavedFile({
filePath: result.url,
complete: resolve,
})
} catch (e) {
console.error(e)
reject(e)
} finally {
uni.removeStorageSync(key)
}
})
})
return Promise.allSettled(promises)
}
<script setup lang="ts"> <script setup lang="ts">
import md5 from 'crypto-js/md5' import md5 from 'crypto-js/md5'
import { CACHE_PREFIX } from './index'
const props = defineProps({ const props = defineProps({
width: { width: {
...@@ -60,27 +61,53 @@ ...@@ -60,27 +61,53 @@
// 如果是网络图片,则缓存到本地 // 如果是网络图片,则缓存到本地
if (url.startsWith('http')) { if (url.startsWith('http')) {
// #ifdef APP-PLUS // #ifdef APP-PLUS
const hash = md5(url).toString() const hash = md5(url).toString().toUpperCase()
hashCacheKey.value = `G_CACHE_IMAGE_${hash}` hashCacheKey.value = `${CACHE_PREFIX}${hash}`
if (uni.getStorageSync(hashCacheKey.value)) { const cache = uni.getStorageSync(hashCacheKey.value)
if (cache) {
log('cache hit', url) log('cache hit', url)
url = uni.getStorageSync(hashCacheKey.value) try {
const result = JSON.parse(cache)
url = result.url
} catch (_) {
url = cache
}
} else { } else {
log('cache miss', url) log('cache miss', url)
try { try {
const res = await uni.downloadFile({ url }) const res = await uni.downloadFile({ url })
if (res.statusCode === 200) { if (res.statusCode === 200) {
const { savedFilePath } = await new Promise<UniApp.SaveFileSuccess>((resolve, reject) => { const { savedFilePath, size } = await new Promise<{ savedFilePath: string; size: number }>(
uni.saveFile({ (resolve, reject) => {
tempFilePath: res.tempFilePath, uni.saveFile({
success: (res) => resolve(res), tempFilePath: res.tempFilePath,
fail: (err) => reject(err), success: (res) => {
}) const filePath = res.savedFilePath
}) uni.getFileInfo({
filePath,
success: (res) =>
resolve({
savedFilePath: filePath,
size: res.size,
}),
fail: (err) => reject(err),
})
},
fail: (err) => reject(err),
})
},
)
url = `${savedFilePath}` url = `${savedFilePath}`
// 缓存图片本地地址 // 缓存图片本地地址
uni.setStorageSync(hashCacheKey.value, url) uni.setStorageSync(
hashCacheKey.value,
JSON.stringify({
url,
size,
timestamp: Date.now(),
}),
)
} else { } else {
log('cache error', url, res) log('cache error', url, res)
url = props.src url = props.src
...@@ -97,6 +124,7 @@ ...@@ -97,6 +124,7 @@
} }
const hasError = ref(false) const hasError = ref(false)
function onError() { function onError() {
if (hasError.value) { if (hasError.value) {
return return
......
...@@ -1036,6 +1036,7 @@ ...@@ -1036,6 +1036,7 @@
}, 80) }, 80)
}, },
pickerChange(e) { pickerChange(e) {
if (!this.isShow) return
//1-年 2-年月 3-年月日 4-年月日 时 5-年月日 时分 6-时分 7-时分秒 8-分秒 //1-年 2-年月 3-年月日 4-年月日 时 5-年月日 时分 6-时分 7-时分秒 8-分秒
let value = e.detail.value; let value = e.detail.value;
let type = Number(this.type) let type = Number(this.type)
......
...@@ -730,6 +730,7 @@ ...@@ -730,6 +730,7 @@
}, 50) }, 50)
}, },
pickerChange(e) { pickerChange(e) {
if (!this.isShow) return
let value = e.detail.value; let value = e.detail.value;
if (!this.isInitShow || value.length != this.layer) return; if (!this.isInitShow || value.length != this.layer) return;
if (this.linkage) { if (this.linkage) {
......
...@@ -385,7 +385,9 @@ ...@@ -385,7 +385,9 @@
}) })
} }
this.itemList = vals; this.itemList = vals;
} } else {
this.itemList = []
}
}, },
itemClick(index) { itemClick(index) {
let vals = [...this.itemList] let vals = [...this.itemList]
......
// token key // token key
export const AREA_CODE = 'AREA__'
export const TOKEN_KEY = 'TOKEN__' export const TOKEN_KEY = 'TOKEN__'
export const LOCALE_KEY = 'LOCALE__' export const LOCALE_KEY = 'LOCALE__'
...@@ -12,6 +14,21 @@ export const ROLES_KEY = 'ROLES__KEY__' ...@@ -12,6 +14,21 @@ export const ROLES_KEY = 'ROLES__KEY__'
// project config key // project config key
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__' export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'
/**
* 推送客户端标识
*/
export const PUSH_CLIENT_KEY = 'PUSH__CLIENT__KEY__'
/**
* APP UUID
*/
export const APP_UUID_KEY = 'APP__UUID__KEY__'
/**
* 用户ID
*/
export const USER_ID_KEY = 'USER__ID__KEY__'
export enum CacheTypeEnum { export enum CacheTypeEnum {
SESSION, SESSION,
LOCAL, LOCAL,
......
...@@ -7,7 +7,7 @@ export function useConcealedExit(max = 10) { ...@@ -7,7 +7,7 @@ export function useConcealedExit(max = 10) {
const maxTapCount = max const maxTapCount = max
const count = ref(0) const count = ref(0)
let loop: NodeJS.Timer let loop: NodeJS.Timeout
// 隐藏退出功能计数 // 隐藏退出功能计数
function exit() { function exit() {
......
import { APP_UUID_KEY } from '/@/enums/cacheEnum'
import { nanoid } from 'nanoid'
import md5 from 'crypto-js/md5'
interface DeviceInfo {
uuid: string
vendor: string
model: string
}
export interface Runtime { export interface Runtime {
app: Ref<PlusRuntimeWidgetInfo> app: Ref<PlusRuntimeWidgetInfo>
getDeviceInfo: () => Promise<DeviceInfo>
} }
export function useRuntime(): Runtime { export function useRuntime(): Runtime {
...@@ -14,7 +25,48 @@ export function useRuntime(): Runtime { ...@@ -14,7 +25,48 @@ export function useRuntime(): Runtime {
// #endif // #endif
}) })
/**
* 获取设备信息
* 注意: 需要在页面的 onReady 后使用
* @returns 设备信息
*/
function getDeviceInfo(): Promise<DeviceInfo> {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
plus.device.getInfo({
success: resolve,
fail: reject,
})
// #endif
// #ifndef APP-PLUS
let uuid = uni.getStorageSync(APP_UUID_KEY)
if (!uuid) {
uuid = md5(nanoid()).toString()
uni.setStorageSync(APP_UUID_KEY, uuid)
}
const result = { uuid, vendor: 'unknown', model: 'unknown' }
const userAgent = navigator.userAgent
const deviceInfo = userAgent.match(/\((.*?)\)/)
if (deviceInfo && deviceInfo.length > 1) {
const info = deviceInfo[1].split(';')
if (/android/i.test(userAgent)) {
if (info.length >= 3) {
result.vendor = info[1].trim()
result.model = info[2].trim()
}
} else {
result.vendor = info[0].trim()
result.model = info[1].trim()
}
}
resolve(result)
// #endif
})
}
return { return {
app, app,
getDeviceInfo,
} }
} }
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
"name" : "Beta App", "name" : "Beta App",
"appid" : "__UNI__2E9441A", "appid" : "__UNI__2E9441A",
"description" : "APP 基础工程", "description" : "APP 基础工程",
"versionName" : "3.0.3.0", "versionName" : "1.0.0",
"versionCode" : 112, "versionCode" : 10000,
"transformPx" : false, "transformPx" : false,
"locale" : "zh-Hans", "locale" : "zh-Hans",
/* 5+App特有相关 */// 配置文件详细说明 /* 5+App特有相关 */// 配置文件详细说明
...@@ -13,10 +13,9 @@ ...@@ -13,10 +13,9 @@
"nvueStyleCompiler" : "uni-app", "nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3, "compilerVersion" : 3,
"splashscreen" : { "splashscreen" : {
"alwaysShowBeforeRender" : true, "alwaysShowBeforeRender" : false,
"waiting" : true, "autoclose" : false,
"autoclose" : true, "waiting" : true
"delay" : 0
}, },
"screenOrientation" : [ "portrait-primary", "landscape-primary" ], "screenOrientation" : [ "portrait-primary", "landscape-primary" ],
"compatible" : { "compatible" : {
...@@ -24,7 +23,10 @@ ...@@ -24,7 +23,10 @@
"ignoreVersion" : true "ignoreVersion" : true
}, },
/* 模块配置 */ /* 模块配置 */
"modules" : {}, "modules" : {
"Geolocation" : {},
"Camera" : {}
},
/* 应用发布信息 */ /* 应用发布信息 */
"distribute" : { "distribute" : {
/* android打包配置 */ /* android打包配置 */
...@@ -45,19 +47,30 @@ ...@@ -45,19 +47,30 @@
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>", "<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
] ],
"permissionPhoneState" : {
// app首次启动关闭权限申请
"request" : "none"
}
}, },
/* ios打包配置 */ /* ios打包配置 */
"ios" : { "ios" : {
"dSYMs" : false "dSYMs" : false,
"privacyDescription" : {
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验",
"NSLocationAlwaysAndWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationAlwaysUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSPhotoLibraryUsageDescription" : "请允许访问您的照片图库,以便能够上传应用异常问题的截图"
}
}, },
/* SDK配置 */ /* SDK配置 */
"sdkConfigs" : { "sdkConfigs" : {
"ad" : {}, "ad" : {},
"push" : { "statics" : {},
"unipush" : { "geolocation" : {
"version" : "2", "system" : {
"offline" : false "__platform__" : [ "ios", "android" ]
} }
} }
}, },
...@@ -92,6 +105,9 @@ ...@@ -92,6 +105,9 @@
"spotlight@3x" : "unpackage/res/icons/120x120.png" "spotlight@3x" : "unpackage/res/icons/120x120.png"
} }
} }
},
"splashscreen" : {
"useOriginalMsgbox" : true
} }
} }
}, },
......
...@@ -28,13 +28,37 @@ ...@@ -28,13 +28,37 @@
} }
}, },
// ================================ 通用页面分割线 ====================================
// === 关于我们 ===
{
"path": "pages/about/index",
"style": {
"navigationBarTitleText": "关于我们",
"backgroundColorTop": "#FFFFFF",
"backgroundColorBottom": "#FFFFFF"
}
},
// === 问题反馈 ===
{
"path": "pages/common/feedback/index",
"style": {
"navigationBarTitleText": "问题反馈",
"backgroundColorTop": "#FFFFFF",
"backgroundColorBottom": "#FFFFFF"
}
},
// === Webview ===
{
"path": "pages/common/webview/index",
"style": {
"navigationBarTitleText": ""
}
},
// === PDF 文件预览 === // === PDF 文件预览 ===
{ {
"path": "pages/common/viewer/pdf", "path": "pages/common/viewer/pdf",
"style": { "style": {
"titleNView": true, "navigationBarTitleText": ""
"navigationBarTitleText": "",
"enablePullDownRefresh": false
} }
}, },
// === 空页面(开发中...) === // === 空页面(开发中...) ===
...@@ -45,13 +69,9 @@ ...@@ -45,13 +69,9 @@
"titleNView": false, "titleNView": false,
// #endif // #endif
"navigationBarTitleText": "开发中", "navigationBarTitleText": "开发中",
"enablePullDownRefresh": false,
"backgroundColorTop": "#FFFFFF", "backgroundColorTop": "#FFFFFF",
"backgroundColorBottom": "#FFFFFF", "backgroundColorBottom": "#FFFFFF",
"backgroundColor": "#FFFFFE", "backgroundColor": "#FFFFFE"
"mp-alipay": {
"allowsBounceVertical": "YES"
}
} }
}, },
// === 版本更新 === // === 版本更新 ===
...@@ -84,7 +104,14 @@ ...@@ -84,7 +104,14 @@
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app", "navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8",
"pageOrientation": "portrait",
"app-plus": {
"scrollIndicator": "none",
"titleNView": {
"titleSize": "20"
}
}
}, },
"resizable": true "resizable": true
} }
<script setup lang="ts">
import dayjs from 'dayjs'
import Link from '@/utils/page/link'
const startYear = ref(2005)
const currentYear = ref(dayjs().year())
</script>
<template>
<view class="wrap">
<view class="logoBox">
<image class="logo" src="/static/logo.png" />
<view class="text-#555 font-bold p-2">{{ $app.name }}</view>
</view>
<fui-footer isFixed>
<template #text>
<view class="links">
<text class="underline" @tap="Link.to(Link.services, '服务协议')">服务协议</text>
<text class="underline" @tap="Link.to(Link.privacy, '隐私政策')">隐私政策</text>
</view>
<view class="mt-1">Copyright © {{ startYear }}-{{ currentYear }} {{ Link.copyright.name }}</view>
<view class="mt-2" v-if="Link.copyright.name !== Link.company.name">
技术支持:
<fui-link underline color="#b2b2b2" size="24" :href="Link.company.link" :text="Link.company.name" />
</view>
</template>
</fui-footer>
</view>
</template>
<style lang="less" scoped>
.wrap {
padding: 0 46rpx;
padding-top: 100rpx;
font-size: 32rpx;
height: 100vh;
background-color: white;
box-sizing: border-box;
}
.logoBox {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
.logo {
width: 160rpx;
height: 160rpx;
box-shadow: 0 0 15rpx #d7d7d7;
border-radius: 20rpx;
}
.logo_text {
width: 126rpx;
height: 40rpx;
margin-top: 20rpx;
}
}
.links {
padding: 10rpx;
* {
padding: 10rpx 20rpx;
}
}
</style>
<script lang="ts" setup>
import { USER_ID_KEY } from '/@/enums/cacheEnum'
const db = uniCloud.database()
const dbCollectionName = 'opendb-feedback'
const model = reactive({
userId: uni.getStorageSync(USER_ID_KEY),
content: '',
mobile: '',
imgs: [],
loading: false,
})
function submit() {
Message.loading('提交中...')
// 使用 clientDB 提交数据
db.collection(dbCollectionName)
.add({
user_id: model.userId,
content: model.content,
mobile: model.mobile,
imgs: model.imgs,
})
.then(() => {
Message.toast('提交成功')
setTimeout(() => uni.navigateBack(), 500)
})
.catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false,
})
})
.finally(() => {
uni.hideLoading()
})
}
</script>
<template>
<view class="feedback-body">
<text class="text-black">问题反馈和意见建议 <text style="color: red">*</text></text>
<textarea
placeholder="请描述您遇到的问题或对本产品的建议..."
v-model="model.content"
class="feedback-textare"
maxlength="-1"
></textarea>
<view class="image-title">
<text class="text-black">上传问题截图<text class="text-grey"> (选填,最多可上传6张) </text> </text>
<view class="text-grey">{{ model.imgs.length }}/6</view>
</view>
<view class="filepicker">
<uni-file-picker file-mediatype="image" :limit="6" return-type="array" v-model="model.imgs" />
</view>
<text class="text-black">联系方式<text class="text-grey">(选填)</text> </text>
<input class="feedback-input" v-model="model.mobile" placeholder="请输入您的手机号" />
<view class="btn">
<fui-button
:disabled="!model.content || model.loading"
:loading="model.loading"
type="primary"
@tap="submit"
>
提 交
</fui-button>
<!-- 安全区 -->
<fui-safe-area />
</view>
</view>
</template>
<style lang="scss" scoped>
.text-black {
color: #303133;
font-size: 32rpx;
}
.text-grey {
color: #bcbcbc;
font-size: 24rpx;
margin-left: 15rpx;
}
.feedback-quick {
padding-right: 10rpx;
color: #606266;
font-size: 32rpx;
}
.feedback-body {
padding: 30rpx;
background-color: white;
min-height: 100vh;
box-sizing: border-box;
}
.feedback-textare {
margin-top: 30rpx;
margin-bottom: 30rpx;
height: 266rpx;
color: #303133;
font-size: 28rpx;
line-height: 2em;
width: 100%;
box-sizing: border-box;
padding: 20rpx 30rpx;
border-radius: 20rpx;
background-color: #f5f6f8;
}
.feedback-input {
font-size: 28rpx;
color: #303133;
background-color: #f5f6f8;
border-radius: 20rpx;
height: 100rpx;
min-height: 100rpx;
padding: 0 30rpx;
margin-top: 30rpx;
margin-bottom: 60rpx;
}
.btn-submit {
border-radius: 20rpx;
color: #ffffff;
margin-top: 100rpx;
background-color: #007aff;
margin-bottom: 70rpx;
}
.image-title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
color: #606266;
}
.filepicker {
margin-top: 30rpx;
margin-bottom: 30rpx;
}
.btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin: 30rpx;
}
</style>
<script lang="ts" setup>
import URI from 'urijs'
const page = reactive({
security: false,
title: '',
link: '',
styles: {
progress: {
color: '#42c02e',
height: '1%',
},
},
})
onLoad(({ title, link, sub }) => {
page.title = title ? decodeURIComponent(title) : ''
page.link = decodeURIComponent(link)
// 嵌入页面进行二级子页面跳转时,参数会被多次 encode
if (sub) {
page.title = decodeURIComponent(page.title)
page.link = decodeURIComponent(page.link)
}
if (!link) {
Message.toast('页面打开失败,参数错误')
uni.navigateBack()
return
}
// 设置标题栏为文件名
if (page.title && page.title !== 'undefined') {
uni.setNavigationBarTitle({
title: page.title,
})
}
// #ifdef APP-PLUS
// 白名单同源检测
if (isSameOrigin(new URI(page.link))) {
page.security = true
} else {
// 对于不安全或不信任的网站地址或可能触发 plus API 的来源,采用手动创建 webview 的方式打开
const { windowHeight, statusBarHeight } = uni.getSystemInfoSync()
const webview = plus.webview.create(page.link, 'no-security-webview', {
plusrequire: 'none',
progress: page.styles.progress,
top: `${statusBarHeight + 44}px`,
height: `${windowHeight}px`,
})
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const currentWebview = currentPage.$getAppWebview()
currentWebview.setStyle({ progress: page.styles.progress })
currentWebview.append(webview)
}
// #endif
// #ifndef APP-PLUS
page.security = true
// #endif
})
// 安全域白名单,防止 APP 环境的意外注入和非法安全调用 plus API 的问题
const allows = ['yiring.com']
// 在安全域白名单中,但仍然需要排除的链接(通常是代理的网站链接)
const denyLinks = []
function isSameOrigin(url: URI) {
if (denyLinks.includes(url.href())) {
return false
}
return allows.some((item) => {
const regex = new RegExp(`^(.*\\.)?${item}$`, 'i')
return regex.test(url.hostname())
})
}
</script>
<template>
<view class="wrap" v-if="page.link">
<template v-if="page.security">
<web-view :src="page.link" :update-title="!page.title" :webview-styles="page.styles" />
</template>
</view>
</template>
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { checkUpgrade } from '@/utils/upgrade' import { checkUpgrade, closeSplashscreenAndChechUpgrade } from '@/utils/upgrade'
import { useRuntime } from '@/hooks/app/useRuntime' import { useRuntime } from '@/hooks/app/useRuntime'
import { useConcealedExit } from '@/hooks/app/useConcealedExit' import { useConcealedExit } from '@/hooks/app/useConcealedExit'
import * as Pages from '@/utils/pages' import * as Pages from '@/utils/pages'
...@@ -11,6 +11,11 @@ ...@@ -11,6 +11,11 @@
const title = ref('Hello World') const title = ref('Hello World')
const version = computed(() => app.value.version) const version = computed(() => app.value.version)
onLoad(() => {
// 关闭启动页并检查更新
closeSplashscreenAndChechUpgrade()
})
function surprise() { function surprise() {
// #ifdef APP-PLUS // #ifdef APP-PLUS
const orientation = plus.navigator.getOrientation() const orientation = plus.navigator.getOrientation()
......
{
"debug": false,
"redis": false,
"cachetime": 604800,
"sessionExpireTime": 1800,
"realtimeStat": true,
"cronMin": false,
"cron": [
{
"type": "stat",
"time": "* * * 0"
},
{
"type": "stat",
"time": "* * 1 10"
},
{
"type": "stat",
"time": "1 * 1 20"
},
{
"type": "stat",
"time": "* 1 3 30"
},
{
"type": "active-device",
"time": "* * 0 10"
},
{
"type": "active-user",
"time": "* * 0 20"
},
{
"type": "page",
"time": "* * 3 20"
},
{
"type": "event",
"time": "* * 4 20"
},
{
"type": "error",
"time": "* * 5 20"
},
{
"type": "loyalty",
"time": "* * 6 20"
},
{
"type": "clean",
"time": "* * 5 30"
},
{
"type": "retention-device",
"time": "* * 2 20"
},
{
"type": "retention-device",
"time": "* 1 4 30"
},
{
"type": "retention-device",
"time": "1 * 2 30"
},
{
"type": "retention-user",
"time": "* * 3 40"
},
{
"type": "retention-user",
"time": "* 1 5 40"
},
{
"type": "retention-user",
"time": "1 * 6 30"
},
{
"type": "pay-result",
"time": "* * * 10",
"dimension": "hour",
"description": "每小时执行统计(会自动统计小时、天、周、月、季度、年度)",
"timely": true
}
],
"batchInsertNum": 5000,
"errorCheck": {
"needCheck": true,
"checkTime": 5
},
"cleanLog": {
"open": true,
"reserveDays": {
"sessionLog": 31,
"userSessionLog": 31,
"pageLog": 7,
"eventLog": 7,
"shareLog": 7,
"errorLog": 7
}
}
}
## 1.0.16(2023-04-25)
- 新增maxTokenLength配置,用于限制数据库用户记录token数组的最大长度
## 1.0.15(2023-04-06)
- 修复部分语言国际化出错的Bug
## 1.0.14(2023-03-07) ## 1.0.14(2023-03-07)
- 修复 admin用户包含其他角色时未包含在token的Bug - 修复 admin用户包含其他角色时未包含在token的Bug
## 1.0.13(2022-07-21) ## 1.0.13(2022-07-21)
......
{ {
"displayName": "uni-id-common", "displayName": "uni-id-common",
"version": "1.0.14", "version": "1.0.16",
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块", "description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
"keywords": [ "keywords": [
"uni-id-common", "uni-id-common",
......
{ {
"name": "uni-id-common", "name": "uni-id-common",
"version": "1.0.14", "version": "1.0.16",
"description": "uni-id token生成、校验、刷新", "description": "uni-id token生成、校验、刷新",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html", "homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html",
"repository": { "repository": {
......
## 0.6.3(2023-08-30)
- 修复 下载 wgt 时如果后缀名不正确,重命名后安装
## 0.6.2(2022-11-21)
- 处理 cloudfunctions 目录
## 0.6.1(2022-08-17) ## 0.6.1(2022-08-17)
- 修复 后台添加应用市场,但都没有启用的情况下报错的Bug (需要 uni-admin 1.9.3+) - 修复 后台添加应用市场,但都没有启用的情况下报错的Bug (需要 uni-admin 1.9.3+)
## 0.6.0(2022-07-19) ## 0.6.0(2022-07-19)
......
{ {
"displayName": "升级中心 uni-upgrade-center - App", "displayName": "升级中心 uni-upgrade-center - App",
"version": "0.6.1", "version": "0.6.3",
"description": "uni升级中心 - 客户端检查更新", "description": "uni升级中心 - 客户端检查更新",
"keywords": [ "keywords": [
"uniCloud", "uniCloud",
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
], ],
"repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center-app", "repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center-app",
"engines": { "engines": {
"HBuilderX": "^3.1.0" "HBuilderX": "^3.2.14"
}, },
"dcloudext": { "dcloudext": {
"sale": { "sale": {
......
...@@ -28,22 +28,20 @@ ...@@ -28,22 +28,20 @@
<template v-else> <template v-else>
<template v-if="!downloadSuccess"> <template v-if="!downloadSuccess">
<view class="progress-box flex-column" v-if="downloading"> <view class="progress-box flex-column" v-if="downloading">
<progress class="progress" border-radius="35" :percent="downLoadPercent" <progress class="progress" border-radius="35" :percent="downLoadPercent" activeColor="#3DA7FF" show-info
activeColor="#3DA7FF" show-info stroke-width="10" /> stroke-width="10" />
<view style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;"> <view style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;">
<text>{{downLoadingText}}</text> <text>{{downLoadingText}}</text>
<text>({{downloadedSize}}/{{packageFileSize}}M)</text> <text>({{downloadedSize}}/{{packageFileSize}}M)</text>
</view> </view>
</view> </view>
<button v-else class="content-button" style="border: none;color: #fff;" plain <button v-else class="content-button" style="border: none;color: #fff;" plain @click="updateApp">
@click="updateApp">
{{downLoadBtnText}} {{downLoadBtnText}}
</button> </button>
</template> </template>
<button v-else-if="downloadSuccess && !installed" class="content-button" <button v-else-if="downloadSuccess && !installed" class="content-button" style="border: none;color: #fff;"
style="border: none;color: #fff;" plain :loading="installing" :disabled="installing" plain :loading="installing" :disabled="installing" @click="installPackage">
@click="installPackage">
{{installing ? '正在安装……' : '下载完成,立即安装'}} {{installing ? '正在安装……' : '下载完成,立即安装'}}
</button> </button>
...@@ -55,8 +53,8 @@ ...@@ -55,8 +53,8 @@
</view> </view>
</view> </view>
<image v-if="!is_mandatory" class="close-img" src="../images/app_update_close.png" <image v-if="!is_mandatory" class="close-img" src="../images/app_update_close.png" @click.stop="closeUpdate">
@click.stop="closeUpdate"></image> </image>
</view> </view>
</view> </view>
</template> </template>
...@@ -282,22 +280,25 @@ ...@@ -282,22 +280,25 @@
success: res => { success: res => {
if (res.statusCode == 200) { if (res.statusCode == 200) {
this.downloadSuccess = true; this.downloadSuccess = true;
this.tempFilePath = res.tempFilePath // fix: wgt 文件下载完成后后缀不是 wgt
if (this.isWGT && res.tempFilePath.split('.').slice(-1) !== 'wgt') {
// 强制更新,直接安装 const failCallback = (e) => {
if (this.is_mandatory) { console.log('[FILE RENAME FAIL]:', JSON.stringify(e));
this.installPackage(); }
plus.io.resolveLocalFileSystemURL(res.tempFilePath, function(entry) {
entry.getParent((parent) => {
const newName = `new_wgt_${Date.now()}.wgt`
entry.copyTo(parent, newName, (res) => {
this.tempFilePath = res.fullPath
this.downLoadComplete()
}, failCallback)
}, failCallback)
}, failCallback);
} else {
this.tempFilePath = res.tempFilePath
this.downLoadComplete()
} }
} }
},
complete: () => {
this.downloading = false;
this.downLoadPercent = 0
this.downloadedSize = 0
this.packageFileSize = 0
downloadTask = null;
} }
}); });
...@@ -307,6 +308,20 @@ ...@@ -307,6 +308,20 @@
this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2); this.packageFileSize = (res.totalBytesExpectedToWrite / Math.pow(1024, 2)).toFixed(2);
}); });
}, },
downLoadComplete() {
this.downloading = false;
this.downLoadPercent = 0
this.downloadedSize = 0
this.packageFileSize = 0
downloadTask = null;
// 强制更新,直接安装
if (this.is_mandatory) {
this.installPackage();
}
},
installPackage() { installPackage() {
// #ifdef APP-PLUS // #ifdef APP-PLUS
// wgt资源包安装 // wgt资源包安装
...@@ -436,7 +451,7 @@ ...@@ -436,7 +451,7 @@
background-color: #fff; background-color: #fff;
box-sizing: border-box; box-sizing: border-box;
padding: 0 50rpx; padding: 0 50rpx;
font-family: -apple-system-font, 'Helvetica Neue', Helvetica, sans-serif; font-family: Source Han Sans CN;
} }
.text { .text {
...@@ -536,4 +551,4 @@ ...@@ -536,4 +551,4 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
</style> </style>
\ No newline at end of file
...@@ -2,13 +2,18 @@ export default function() { ...@@ -2,13 +2,18 @@ export default function() {
// #ifdef APP-PLUS // #ifdef APP-PLUS
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) { plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
const data = { let data = {
action: 'checkVersion', action: 'checkVersion',
appid: plus.runtime.appid, appid: plus.runtime.appid,
appVersion: plus.runtime.version, appVersion: plus.runtime.version,
wgtVersion: widgetInfo.version wgtVersion: widgetInfo.version
} }
console.log("data: ",data); data = {
"action": "checkVersion",
"appid": "__UNI__TEST__",
"appVersion": "1.0",
"wgtVersion": "1.0"
}
uniCloud.callFunction({ uniCloud.callFunction({
name: 'uni-upgrade-center', name: 'uni-upgrade-center',
data, data,
......
...@@ -64,7 +64,7 @@ export default function() { ...@@ -64,7 +64,7 @@ export default function() {
uni.removeStorageSync(PACKAGE_INFO_KEY) uni.removeStorageSync(PACKAGE_INFO_KEY)
} }
}) })
return return
} else if (code < 0) { } else if (code < 0) {
// TODO 云函数报错处理 // TODO 云函数报错处理
......
// 将 KB 转换为其他单位
export function convertKB(kb: number) {
const bytes = kb * 1024
const units = ['B', 'KB', 'MB', 'GB', 'TB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return { size: size.toFixed(2), unit: units[unitIndex] }
}
...@@ -12,13 +12,14 @@ import { useI18n } from '/@/hooks/app/useI18n' ...@@ -12,13 +12,14 @@ import { useI18n } from '/@/hooks/app/useI18n'
import { useMessage } from '/@/hooks/app/useMessage' import { useMessage } from '/@/hooks/app/useMessage'
import { ContentTypeEnum, RequestEnum } from '/@/enums/httpEnum' import { ContentTypeEnum, RequestEnum } from '/@/enums/httpEnum'
import { isString } from '/@/utils/is' import { isString } from '/@/utils/is'
import { clearAuthCache, getToken } from '/@/utils/auth' import { getToken, setAuthCache } from '/@/utils/auth'
import { deepMerge, setObjToUrlParams } from '/@/utils' import { deepMerge, setObjToUrlParams } from '/@/utils'
import { formatRequestDate, joinTimestamp } from './helper' import { formatRequestDate, joinTimestamp } from './helper'
import { AxiosRetry } from '/@/utils/http/axios/axiosRetry' import { AxiosRetry } from '/@/utils/http/axios/axiosRetry'
import * as HTTP from '/@/api/types' import * as HTTP from '/@/api/types'
import { API_URL, API_URL_PREFIX } from '/@/utils/net' import { API_URL, API_URL_PREFIX } from '/@/utils/net'
import { handleResponseResource } from '/@/utils/proxy' import { handleResponseResource } from '/@/utils/proxy'
import { TOKEN_KEY } from '@/enums/cacheEnum'
const globSetting = useGlobSetting() const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix const urlPrefix = globSetting.urlPrefix
...@@ -63,16 +64,25 @@ const transform: AxiosTransform = { ...@@ -63,16 +64,25 @@ const transform: AxiosTransform = {
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
let timeoutMsg = '' let timeoutMsg = ''
switch (status) { switch (status) {
case HTTP.Status.UNAUTHORIZED: case HTTP.Status.UNAUTHORIZED: {
timeoutMsg = t('sys.api.timeoutMessage') timeoutMsg = t('sys.api.timeoutMessage')
clearAuthCache()
// TODO: 跳转到登录页 // 清空 token
// uni.reLaunch({ setAuthCache(TOKEN_KEY, '')
// url: '/pages/login/index',
// }) // 判断当前页面是否为登录页,防止多个请求时重复跳转
const page = getCurrentPages()[0]
const loginPageRoute = `/pages/login/login`
if (page.route !== loginPageRoute) {
// 跳转到登录页
uni.reLaunch({
url: loginPageRoute,
})
return
}
break break
}
default: default:
if (details) { if (details) {
timeoutMsg = details timeoutMsg = details
......
export default {
/**
* 公司信息
*/
company: {
name: '长沙壹润信息科技发展有限公司',
link: 'https://yiring.com',
},
/**
* 版权信息
*/
copyright: {
name: '长沙壹润信息科技发展有限公司',
link: 'https://yiring.com',
},
/**
* 服务协议地址
* @description TODO: 服务协议地址需要根据实际情况进行修改
*/
services: 'https://app.yiring.com/services.html',
/**
* 隐私政策地址
* @description TODO: 隐私政策地址需要根据实际情况进行修改
*/
privacy: 'https://app.yiring.com/privacy.html',
/**
* 从内部打开指定页面
* @param link 链接
* @param title 页面标题
*/
to(link: string, title: string) {
uni.navigateTo({
url: `/pages/common/webview/index?title=${encodeURIComponent(title)}&link=${encodeURIComponent(link)}`,
})
},
/**
* 从外部打开指定页面
* @param link 链接
*/
open(link: string) {
// #ifdef APP-PLUS
if (link?.startsWith('tel:')) {
uni.makePhoneCall({
phoneNumber: link.replace('tel:', ''),
})
} else {
plus.runtime.openURL(link)
}
// #endif
// #ifdef H5
window.open(link)
// #endif
},
}
...@@ -22,3 +22,31 @@ export function checkUpgrade(toast = false) { ...@@ -22,3 +22,31 @@ export function checkUpgrade(toast = false) {
}) })
// #endif // #endif
} }
/**
* 关闭 splashscreen 并检查更新
*/
export function closeSplashscreenAndChechUpgrade() {
return new Promise<void>((resolve) => {
// FIXED: pages 第一路由页面为登录页,加上 splashscreen 配置控制使得用户体验更好
// #ifdef APP-PLUS
nextTick(() => {
// FIX: 延时执行,解决安卓环境下可能意外看到登录页的问题
const { platform } = uni.getSystemInfoSync()
const isAndroid = platform === 'android'
setTimeout(
() => {
plus.navigator.closeSplashscreen()
// 检查更新
checkUpgrade()
resolve()
},
isAndroid ? 1000 : 50,
)
})
// #endif
// #ifndef APP-PLUS
resolve()
// #endif
})
}
...@@ -91,5 +91,5 @@ declare global { ...@@ -91,5 +91,5 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论