提交 85b6307c 作者: 方治民

合并分支 '3.x' 到 'main'

3.x

查看合并请求 !57
......@@ -68,5 +68,8 @@ module.exports = {
],
'vue/multi-word-component-names': 'off',
'no-console': 'off',
'@stylistic/ts/indent': 'off',
'@stylistic/ts/brace-style': 'off',
'@stylistic/js/operator-linebreak': 'off',
},
}
......@@ -66,64 +66,64 @@
}
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3081220230817001",
"@dcloudio/uni-app-plus": "3.0.0-3081220230817001",
"@dcloudio/uni-components": "3.0.0-3081220230817001",
"@dcloudio/uni-h5": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-alipay": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-baidu": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-jd": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-lark": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-qq": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3081220230817001",
"@dcloudio/uni-mp-weixin": "3.0.0-3081220230817001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3081220230817001",
"@dcloudio/uni-app": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-components": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-h5": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-jd": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-lark": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-qq": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-ui": "^1.4.28",
"@faker-js/faker": "^8.0.2",
"@faker-js/faker": "^8.1.0",
"@vue/runtime-core": "~3.2.47",
"@vueuse/core": "^10.4.1",
"axios": "^1.5.0",
"@vueuse/core": "^10.5.0",
"axios": "^1.5.1",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
"dayjs": "^1.11.10",
"lodash-es": "^4.17.21",
"nanoid": "^4.0.2",
"nanoid": "^5.0.1",
"pinia": "~2.0.36",
"qs": "~6.9.7",
"stompjs": "^2.3.3",
"urijs": "^1.19.11",
"vue": "~3.2.47",
"vue-i18n": "^9.2.2",
"vue-i18n": "^9.5.0",
"vue-request": "^2.0.3",
"vue-types": "^5.1.1"
},
"devDependencies": {
"@antfu/eslint-config": "^0.41.0",
"@commitlint/cli": "^17.7.1",
"@antfu/eslint-config": "^0.43.1",
"@commitlint/cli": "^17.7.2",
"@commitlint/config-conventional": "^17.7.0",
"@dcloudio/types": "^3.3.3",
"@dcloudio/uni-automator": "3.0.0-3081220230817001",
"@dcloudio/uni-cli-shared": "3.0.0-3081220230817001",
"@dcloudio/types": "^3.4.0",
"@dcloudio/uni-automator": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3090120230927001",
"@dcloudio/uni-helper-json": "^1.0.13",
"@dcloudio/uni-stacktracey": "3.0.0-3081220230817001",
"@dcloudio/vite-plugin-uni": "3.0.0-3081220230817001",
"@iconify/json": "^2.2.108",
"@types/crypto-js": "^4.1.1",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3090120230927001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3090120230927001",
"@iconify/json": "^2.2.125",
"@types/crypto-js": "^4.1.2",
"@types/lodash-es": "^4.17.9",
"@types/node": "^20.5.7",
"@types/node": "^20.8.3",
"@types/qs": "^6.9.8",
"@types/stompjs": "^2.3.5",
"@types/stompjs": "^2.3.6",
"@types/urijs": "^1.19.20",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"commitizen": "^4.3.0",
"conventional-changelog-cli": "^4.0.0",
"conventional-changelog-cli": "^4.1.0",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0",
"cz-git": "^1.7.1",
"czg": "^1.7.1",
"dotenv": "^16.3.1",
"eslint": "^8.48.0",
"eslint": "^8.51.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0",
......@@ -135,24 +135,24 @@
"npm-run-all": "^4.1.5",
"picocolors": "^1.0.0",
"pont-engine": "^1.5.12",
"postcss": "^8.4.29",
"postcss": "^8.4.31",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
"prettier": "^3.0.3",
"rimraf": "^5.0.1",
"sass": "^1.66.1",
"sort-package-json": "^2.5.1",
"rimraf": "^5.0.5",
"sass": "^1.69.0",
"sort-package-json": "^2.6.0",
"stylelint": "^15.10.3",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^13.0.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
"typescript": "~5.1.6",
"unocss": "^0.55.3",
"unocss-preset-weapp": "^0.55.2",
"unocss": "^0.56.5",
"unocss-preset-weapp": "^0.56.0",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.9",
"vite": "^4.4.11",
"vue-eslint-parser": "^9.3.1"
},
"engines": {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
<script setup lang="ts">
import { checkUpgrade } from '@/utils/upgrade'
// import * as Push from '@/utils/push'
onLaunch(() => {
console.log('App Launch')
// 版本更新检查
checkUpgrade()
// 清除消息角标
// Push.setBadge(0)
......
......@@ -46,8 +46,8 @@ export const Message = {
},
/**
* 消息确认提示
* @param title 标题
* @param content 提示内容
* @param title 标题
* @param confirmText 确认按钮文字
* @param showCancel 是否显示取消按钮
* @returns Promise<boolean> 是否点击了确认按钮
......
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">
import md5 from 'crypto-js/md5'
import { CACHE_PREFIX } from './index'
const props = defineProps({
width: {
......@@ -60,27 +61,53 @@
// 如果是网络图片,则缓存到本地
if (url.startsWith('http')) {
// #ifdef APP-PLUS
const hash = md5(url).toString()
hashCacheKey.value = `G_CACHE_IMAGE_${hash}`
if (uni.getStorageSync(hashCacheKey.value)) {
const hash = md5(url).toString().toUpperCase()
hashCacheKey.value = `${CACHE_PREFIX}${hash}`
const cache = uni.getStorageSync(hashCacheKey.value)
if (cache) {
log('cache hit', url)
url = uni.getStorageSync(hashCacheKey.value)
try {
const result = JSON.parse(cache)
url = result.url
} catch (_) {
url = cache
}
} else {
log('cache miss', url)
try {
const res = await uni.downloadFile({ url })
if (res.statusCode === 200) {
const { savedFilePath } = await new Promise<UniApp.SaveFileSuccess>((resolve, reject) => {
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (res) => resolve(res),
fail: (err) => reject(err),
})
})
const { savedFilePath, size } = await new Promise<{ savedFilePath: string; size: number }>(
(resolve, reject) => {
uni.saveFile({
tempFilePath: res.tempFilePath,
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}`
// 缓存图片本地地址
uni.setStorageSync(hashCacheKey.value, url)
uni.setStorageSync(
hashCacheKey.value,
JSON.stringify({
url,
size,
timestamp: Date.now(),
}),
)
} else {
log('cache error', url, res)
url = props.src
......@@ -97,6 +124,7 @@
}
const hasError = ref(false)
function onError() {
if (hasError.value) {
return
......
// token key
export const AREA_CODE = 'AREA__'
export const TOKEN_KEY = 'TOKEN__'
export const LOCALE_KEY = 'LOCALE__'
......@@ -12,6 +14,21 @@ export const ROLES_KEY = 'ROLES__KEY__'
// project config 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 {
SESSION,
LOCAL,
......
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 {
app: Ref<PlusRuntimeWidgetInfo>
getDeviceInfo: () => Promise<DeviceInfo>
}
export function useRuntime(): Runtime {
......@@ -14,7 +25,48 @@ export function useRuntime(): Runtime {
// #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 {
app,
getDeviceInfo,
}
}
import type { App } from 'vue'
import type { I18n, I18nOptions } from 'vue-i18n'
import { createI18n } from 'vue-i18n'
import dayjs from 'dayjs'
import dayjsLocalizedFormat from 'dayjs/plugin/localizedFormat'
import dayjsZh from 'dayjs/locale/zh-cn'
import langs from './lang'
dayjs.extend(dayjsLocalizedFormat)
dayjs.locale(dayjsZh)
// eslint-disable-next-line import/no-mutable-exports
export let i18n: ReturnType<typeof createI18n>
......
......@@ -2,10 +2,11 @@
"name" : "Beta App",
"appid" : "__UNI__2E9441A",
"description" : "APP 基础工程",
"versionName" : "3.0.3.0",
"versionCode" : 112,
"versionName" : "1.0.0",
"versionCode" : 10000,
"transformPx" : false,
"locale" : "zh-Hans",
"vueVersion" : "3",
/* 5+App特有相关 */// 配置文件详细说明
// https://uniapp.dcloud.net.cn/collocation/manifest-app.html#full-manifest
"app-plus" : {
......@@ -13,10 +14,9 @@
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
"alwaysShowBeforeRender" : false,
"autoclose" : false,
"waiting" : true
},
"screenOrientation" : [ "portrait-primary", "landscape-primary" ],
"compatible" : {
......@@ -24,7 +24,10 @@
"ignoreVersion" : true
},
/* 模块配置 */
"modules" : {},
"modules" : {
"Geolocation" : {},
"Camera" : {}
},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
......@@ -45,19 +48,30 @@
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
],
"permissionPhoneState" : {
// app首次启动关闭权限申请
"request" : "none"
}
},
/* ios打包配置 */
"ios" : {
"dSYMs" : false
"dSYMs" : false,
"privacyDescription" : {
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验",
"NSLocationAlwaysAndWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationAlwaysUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSLocationWhenInUseUsageDescription" : "该应用需要你的地理位置,以便检查你当前位置信息",
"NSPhotoLibraryUsageDescription" : "请允许访问您的照片图库,以便能够上传应用异常问题的截图"
}
},
/* SDK配置 */
"sdkConfigs" : {
"ad" : {},
"push" : {
"unipush" : {
"version" : "2",
"offline" : false
"statics" : {},
"geolocation" : {
"system" : {
"__platform__" : [ "ios", "android" ]
}
}
},
......@@ -92,9 +106,19 @@
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
},
"splashscreen" : {
"useOriginalMsgbox" : true
}
},
"uniStatistics" : {
"enable" : true
}
},
"uniStatistics" : {
"enable" : true,
"version" : "2"
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
......@@ -113,10 +137,5 @@
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : true,
"version" : "2"
},
"vueVersion" : "3"
}
}
......@@ -8,11 +8,29 @@
}
},
// ================================ 通用页面分割线 ====================================
// === 关于我们 ===
{
"path": "pages/common/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": {
"titleNView": true,
"navigationBarTitleText": ""
}
},
......@@ -20,7 +38,6 @@
{
"path": "pages/common/viewer/pdf",
"style": {
"titleNView": true,
"navigationBarTitleText": ""
}
},
......@@ -32,13 +49,8 @@
"titleNView": false,
// #endif
"navigationBarTitleText": "开发中",
"enablePullDownRefresh": false,
"backgroundColorTop": "#FFFFFF",
"backgroundColorBottom": "#FFFFFF",
"backgroundColor": "#FFFFFE",
"mp-alipay": {
"allowsBounceVertical": "YES"
}
"backgroundColorBottom": "#FFFFFF"
}
},
// === 版本更新 ===
......
<script setup lang="ts">
import dayjs from 'dayjs'
import Navigate from '@/utils/page/navigate'
const startYear = ref(2005)
const currentYear = ref(dayjs().year())
const { privacy, services, copyright, company } = Navigate.links
</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="Navigate.webview(services, '服务协议')">服务协议</text>
<text class="underline" @tap="Navigate.webview(privacy, '隐私政策')">隐私政策</text>
</view>
<view class="mt-1">Copyright © {{ startYear }}-{{ currentYear }} {{ copyright.name }}</view>
<view class="mt-2" v-if="copyright.name !== company.name">
技术支持:
<fui-link underline color="#b2b2b2" size="24" :href="company.link" :text="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: {
......@@ -10,10 +13,16 @@
},
})
onLoad(({ title, link }) => {
page.title = title ? decodeURI(title) : ''
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()
......@@ -26,11 +35,52 @@
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">
<web-view :src="page.link" :update-title="!page.title" :webview-styles="page.styles" />
<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">
import dayjs from 'dayjs'
import { checkUpgrade } from '@/utils/upgrade'
import { checkUpgrade, closeSplashscreenAndChechUpgrade } from '@/utils/upgrade'
import { useRuntime } from '@/hooks/app/useRuntime'
import { useConcealedExit } from '@/hooks/app/useConcealedExit'
......@@ -10,7 +10,10 @@
const title = ref('Hello World')
const version = computed(() => app.value.version)
onMounted(() => {
onLoad(() => {
// 关闭启动页并检查更新
closeSplashscreenAndChechUpgrade()
// test API
API.example.hello
.request()
......
import { defineStore } from 'pinia'
import { store } from '/@/store'
import { getAuthCache, setAuthCache } from '@/utils/auth'
import { TOKEN_KEY, USER_INFO_KEY } from '@/enums/cacheEnum'
import type { defs } from '@/api/services'
import Navigate from '@/utils/page/navigate'
export type UserInfo = Partial<defs.UserInfo>
interface UserState {
userInfo: Nullable<UserInfo>
token?: string
loading?: boolean
}
export const useUserStore = defineStore({
id: 'app-user',
state: (): UserState => ({
token: null,
userInfo: null,
loading: false,
}),
getters: {
getToken(): Nullable<string> {
if (this.token) {
return this.token
}
const token = getAuthCache(TOKEN_KEY)
if (token) {
this.token = token
return token
}
},
getUserInfo(): Nullable<UserInfo> {
if (this.userInfo) {
return this.userInfo
}
const info = getAuthCache(USER_INFO_KEY)
if (info) {
const userInfo = JSON.parse(info) as UserInfo
this.userInfo = userInfo
return userInfo
}
return null
},
/**
* @returns 是否为审核账号
*/
isAuditMode(): boolean {
return this.getUserInfo?.mobile === '10000000001'
},
},
actions: {
setToken(token: string) {
this.token = token
setAuthCache(TOKEN_KEY, token)
},
setUserInfo(info: UserInfo) {
this.userInfo = info
setAuthCache(USER_INFO_KEY, info ? JSON.stringify(info) : null)
},
async logout() {
if (this.loading) {
return
}
this.loading = true
try {
await API.auth.logout.request()
} finally {
this.setToken(null)
this.setUserInfo(null)
await Navigate.to('/pages/login/login')
this.loading = false
}
},
async refreshUserInfo() {
if (this.loading) {
return
}
this.loading = true
try {
const userInfo = await API.userView.getUserInfo.request()
this.setUserInfo(userInfo)
} finally {
this.loading = false
}
},
},
})
// Need to be used outside the setup
export function useUserStoreWithOut() {
return useUserStore(store)
}
{
"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)
- 修复 admin用户包含其他角色时未包含在token的Bug
## 1.0.13(2022-07-21)
......
{
"displayName": "uni-id-common",
"version": "1.0.14",
"version": "1.0.16",
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
"keywords": [
"uni-id-common",
......
{
"name": "uni-id-common",
"version": "1.0.14",
"version": "1.0.16",
"description": "uni-id token生成、校验、刷新",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html",
"repository": {
......
## 0.6.4(2023-09-01)
chore: 优化代码结构
## 0.6.3(2023-08-30)
- 修复 下载 wgt 时如果后缀名不正确,重命名后安装
## 0.6.2(2022-11-21)
- 处理 cloudfunctions 目录
## 0.6.1(2022-08-17)
- 修复 后台添加应用市场,但都没有启用的情况下报错的Bug (需要 uni-admin 1.9.3+)
## 0.6.0(2022-07-19)
......
{
"displayName": "升级中心 uni-upgrade-center - App",
"version": "0.6.1",
"version": "0.6.4",
"description": "uni升级中心 - 客户端检查更新",
"keywords": [
"uniCloud",
......@@ -10,7 +10,7 @@
],
"repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center-app",
"engines": {
"HBuilderX": "^3.1.0"
"HBuilderX": "^3.2.14"
},
"dcloudext": {
"sale": {
......
......@@ -28,22 +28,20 @@
<template v-else>
<template v-if="!downloadSuccess">
<view class="progress-box flex-column" v-if="downloading">
<progress class="progress" border-radius="35" :percent="downLoadPercent"
activeColor="#3DA7FF" show-info stroke-width="10" />
<progress class="progress" border-radius="35" :percent="downLoadPercent" activeColor="#3DA7FF" show-info
stroke-width="10" />
<view style="width:100%;font-size: 28rpx;display: flex;justify-content: space-around;">
<text>{{downLoadingText}}</text>
<text>({{downloadedSize}}/{{packageFileSize}}M)</text>
</view>
</view>
<button v-else class="content-button" style="border: none;color: #fff;" plain
@click="updateApp">
<button v-else class="content-button" style="border: none;color: #fff;" plain @click="updateApp">
{{downLoadBtnText}}
</button>
</template>
<button v-else-if="downloadSuccess && !installed" class="content-button"
style="border: none;color: #fff;" plain :loading="installing" :disabled="installing"
@click="installPackage">
<button v-else-if="downloadSuccess && !installed" class="content-button" style="border: none;color: #fff;"
plain :loading="installing" :disabled="installing" @click="installPackage">
{{installing ? '正在安装……' : '下载完成,立即安装'}}
</button>
......@@ -55,8 +53,8 @@
</view>
</view>
<image v-if="!is_mandatory" class="close-img" src="../images/app_update_close.png"
@click.stop="closeUpdate"></image>
<image v-if="!is_mandatory" class="close-img" src="../images/app_update_close.png" @click.stop="closeUpdate">
</image>
</view>
</view>
</template>
......@@ -282,22 +280,26 @@
success: res => {
if (res.statusCode == 200) {
this.downloadSuccess = true;
this.tempFilePath = res.tempFilePath
// 强制更新,直接安装
if (this.is_mandatory) {
this.installPackage();
// fix: wgt 文件下载完成后后缀不是 wgt
if (this.isWGT && !res.tempFilePath.endsWith('.wgt')) {
const failCallback = (e) => {
console.log('[FILE RENAME FAIL]:', JSON.stringify(e));
}
plus.io.resolveLocalFileSystemURL(res.tempFilePath, (entry) => {
const originName = entry.name
entry.getParent((parent) => {
const newName = `new_wgt_${Date.now()}.wgt`
entry.copyTo(parent, newName, (result) => {
this.tempFilePath = res.tempFilePath.replace(originName, newName)
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,12 +309,27 @@
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() {
// #ifdef APP-PLUS
// wgt资源包安装
if (this.isWGT) {
this.installing = true;
}
plus.runtime.install(this.tempFilePath, {
force: false
}, async res => {
......@@ -436,7 +453,8 @@
background-color: #fff;
box-sizing: border-box;
padding: 0 50rpx;
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;
}
.text {
......
......@@ -2,13 +2,12 @@ export default function() {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
const data = {
let data = {
action: 'checkVersion',
appid: plus.runtime.appid,
appVersion: plus.runtime.version,
wgtVersion: widgetInfo.version
}
console.log("data: ",data);
uniCloud.callFunction({
name: 'uni-upgrade-center',
data,
......
......@@ -64,7 +64,7 @@ export default function() {
uni.removeStorageSync(PACKAGE_INFO_KEY)
}
})
return
} else if (code < 0) {
// TODO 云函数报错处理
......
......@@ -5,24 +5,41 @@ export function getToken() {
}
export function getAuthCache(key: string): string | null {
let value: string | null = null
// #ifdef APP-PLUS
value = plus.storage.getItem(key)
// #endif
// #ifndef APP-PLUS
try {
const value = uni.getStorageSync(key)
value = uni.getStorageSync(key)
if (value) {
return value
}
} catch (e) {}
// #endif
return null
return value
}
export function setAuthCache(key: string, value: string) {
// #ifdef APP-PLUS
plus.storage.setItem(key, value)
// #endif
// #ifndef APP-PLUS
try {
uni.setStorageSync(key, value)
} catch (e) {}
// #endif
}
export function clearAuthCache() {
// #ifdef APP-PLUS
plus.storage.clear()
// #endif
// #ifndef APP-PLUS
try {
uni.clearStorageSync()
} catch (e) {}
// #endif
}
......@@ -50,7 +50,7 @@ export function getEnv(): string {
/**
* 获取运行环境中文标识
* @returns
* @returns string
*/
export function getEnvText(): string {
switch (import.meta.env.MODE) {
......
// 将 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,7 +12,7 @@ export function getPendingUrl(config: AxiosRequestConfig) {
export class AxiosCanceler {
/**
* Add request
* @param {Object} config
* @param {object} config
*/
addPending(config: AxiosRequestConfig) {
this.removePending(config)
......@@ -39,7 +39,7 @@ export class AxiosCanceler {
/**
* Removal request
* @param {Object} config
* @param {object} config
*/
removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config)
......
......@@ -12,13 +12,14 @@ import { useI18n } from '/@/hooks/app/useI18n'
import { useMessage } from '/@/hooks/app/useMessage'
import { ContentTypeEnum, RequestEnum } from '/@/enums/httpEnum'
import { isString } from '/@/utils/is'
import { clearAuthCache, getToken } from '/@/utils/auth'
import { getToken, setAuthCache } from '/@/utils/auth'
import { deepMerge, setObjToUrlParams } from '/@/utils'
import { formatRequestDate, joinTimestamp } from './helper'
import { AxiosRetry } from '/@/utils/http/axios/axiosRetry'
import * as HTTP from '/@/api/types'
import { API_URL, API_URL_PREFIX } from '/@/utils/net'
import { handleResponseResource } from '/@/utils/proxy'
import { TOKEN_KEY } from '@/enums/cacheEnum'
const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix
......@@ -63,16 +64,25 @@ const transform: AxiosTransform = {
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
let timeoutMsg = ''
switch (status) {
case HTTP.Status.UNAUTHORIZED:
case HTTP.Status.UNAUTHORIZED: {
timeoutMsg = t('sys.api.timeoutMessage')
clearAuthCache()
// TODO: 跳转到登录页
// uni.reLaunch({
// url: '/pages/login/index',
// })
// 清空 token
setAuthCache(TOKEN_KEY, '')
// 判断当前页面是否为登录页,防止多个请求时重复跳转
const page = getCurrentPages()[0]
const loginPageRoute = `/pages/login/login`
if (page.route !== loginPageRoute) {
// 跳转到登录页
uni.reLaunch({
url: loginPageRoute,
})
return
}
break
}
default:
if (details) {
timeoutMsg = details
......
export default {
links: {
/**
* 公司信息
*/
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 page 页面全路径
*/
async to(page: string, options?: UniNamespace.NavigateToOptions) {
await uni.navigateTo({
url: page,
...options,
})
},
/**
* 从外部打开指定页面
* @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
},
/**
* 从内部打开指定页面
* @param link 链接
* @param title 页面标题
*/
webview(link: string, title: string) {
uni.navigateTo({
url: `/pages/common/webview/index?title=${encodeURIComponent(title)}&link=${encodeURIComponent(link)}`,
})
},
}
......@@ -230,8 +230,8 @@ class StompInstance {
/**
* 向服务器发送消息
* @param {string} destination 主题
* @param {Object | string} message 消息内容
* @param {Object} headers 消息头
* @param {object | string} message 消息内容
* @param {object} headers 消息头
*/
send(destination: string, message: Object | string, headers?: Object) {
if (this.client) {
......
......@@ -101,25 +101,25 @@ class UniWebSocket {
/**
* 连接开启
* @param {Object} _res
* @param {object} _res
*/
onopen(_res: object) {}
/**
* 连接关闭
* @param {Object} _res
* @param {object} _res
*/
onclose(_res: object) {}
/**
* 连接异常
* @param {Object} _res
* @param {object} _res
*/
onerror(_res: object) {}
/**
* 接收消息
* @param {Object} _res
* @param {object} _res
*/
onmessage(_res: object) {}
......
......@@ -22,3 +22,31 @@ export function checkUpgrade(toast = false) {
})
// #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 {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
}
......@@ -2,6 +2,8 @@ import type { AxiosRequestConfig } from 'axios'
declare global {
type Recordable<T = any> = Record<string, T>
type Nullable<T> = T | null
type NonNullable<T> = T extends null | undefined ? never : T
interface ImportMetaEnv extends ViteEnv {
__: unknown
......
......@@ -13,6 +13,7 @@ export default defineConfig({
{
'border-base': 'border border-[#eee]',
'flex-center': 'flex justify-center items-center',
ellipsis: 'text-ellipsis whitespace-nowrap overflow-hidden',
},
],
presets: [
......@@ -22,7 +23,7 @@ export default defineConfig({
presetWeappAttributify(),
// icon
presetIcons({
prefix: 'icon-',
prefix: ['i-', 'icon-'],
extraProperties: {
display: 'inline-flex',
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论