fix(android): 修复PDF/下载/文件选择器,常规设置增加上传日志
This commit is contained in:
parent
426825d475
commit
15dea9c2e5
@ -10,12 +10,12 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.view.View
|
||||
import android.content.Context
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.DownloadListener
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.PermissionRequest
|
||||
import android.webkit.ValueCallback
|
||||
@ -185,10 +185,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
webView.setDownloadListener(DownloadListener { url, _, contentDisposition, _, _ ->
|
||||
startSystemDownload(url, suggestFileName(contentDisposition, url))
|
||||
})
|
||||
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onPermissionRequest(request: PermissionRequest) {
|
||||
// 按需放行媒体权限(例如文件上传触发的媒体访问)
|
||||
@ -203,13 +199,12 @@ class MainActivity : AppCompatActivity() {
|
||||
this@MainActivity.filePathCallback?.onReceiveValue(null)
|
||||
this@MainActivity.filePathCallback = filePathCallback
|
||||
|
||||
// 统一使用 ACTION_GET_CONTENT,避免不同 ROM 对 Photo Picker / 相册的兼容差异
|
||||
// 导致返回的 URI/文件对象在上传时失效。根据 acceptTypes 设置过滤类型,
|
||||
// 图片选择仍只显示图片文件,但走文件管理器而不是系统相册。
|
||||
// 使用 ACTION_OPEN_DOCUMENT 走系统文件管理器(DocumentsUI),
|
||||
// 避免 ACTION_GET_CONTENT 在某些 ROM 上唤起相册/Photo Picker 导致返回 URI 失效。
|
||||
val acceptTypes = fileChooserParams?.acceptTypes?.filter { it.isNotBlank() }?.takeIf { it.isNotEmpty() }
|
||||
?: arrayListOf("*/*")
|
||||
val allowMultiple = fileChooserParams?.mode == FileChooserParams.MODE_OPEN_MULTIPLE
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = acceptTypes.first()
|
||||
if (allowMultiple) {
|
||||
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
@ -770,6 +770,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
}
|
||||
@ -779,6 +780,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.5",
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
@ -788,7 +790,8 @@
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
@ -6964,7 +6967,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
@ -75,6 +75,14 @@
|
||||
:title="displayName"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-else-if="canAndroidPdfPreview" class="sfc-android-pdf-preview" @click="previewPdf">
|
||||
<div class="sfc-android-pdf-placeholder">
|
||||
<span class="sfc-file-icon"></span>
|
||||
<span>PDF 文件(点击预览)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -94,6 +102,9 @@ const props = defineProps<{
|
||||
preview?: string;
|
||||
}>();
|
||||
|
||||
// ---- 环境检测 ----
|
||||
const isAndroidApp = typeof window !== 'undefined' && Boolean((window as any).AndroidPdfBridge);
|
||||
|
||||
// ---- 状态 ----
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
@ -108,9 +119,15 @@ const displayName = computed(() => props.name || props.path.split('/').pop() ||
|
||||
|
||||
const canPreview = computed(() => {
|
||||
if (props.preview === 'off') return false;
|
||||
// Android App 内 PDF 不走 iframe 内嵌预览(WebView 不支持),改用原生 PdfPreviewActivity
|
||||
if (isAndroidApp && fileType.value === 'pdf') return false;
|
||||
return ['text', 'code', 'json', 'csv', 'markdown', 'image', 'pdf'].includes(fileType.value);
|
||||
});
|
||||
|
||||
const canAndroidPdfPreview = computed(() => {
|
||||
return isAndroidApp && fileType.value === 'pdf';
|
||||
});
|
||||
|
||||
const canCopy = computed(() => ['text', 'code', 'json', 'csv', 'markdown'].includes(fileType.value));
|
||||
|
||||
const metaText = computed(() => {
|
||||
@ -275,6 +292,13 @@ function openFullImage() {
|
||||
if (contentUrl.value) window.open(contentUrl.value, '_blank');
|
||||
}
|
||||
|
||||
function previewPdf() {
|
||||
const bridge = (window as any).AndroidPdfBridge;
|
||||
if (bridge && typeof bridge.previewPdf === 'function') {
|
||||
bridge.previewPdf(contentUrl.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onImageError() {
|
||||
error.value = '图片加载失败';
|
||||
}
|
||||
@ -482,4 +506,31 @@ onMounted(() => {
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sfc-android-pdf-preview {
|
||||
border-top: 1px solid var(--border-default);
|
||||
background: var(--surface-base);
|
||||
}
|
||||
|
||||
.sfc-android-pdf-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 24px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--surface-raised);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.sfc-android-pdf-placeholder:hover {
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.sfc-android-pdf-placeholder .sfc-file-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -450,6 +450,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-action-row">
|
||||
<span class="settings-row-copy">
|
||||
<span class="settings-row-title">上传诊断日志</span>
|
||||
<span class="settings-row-desc">复现问题后点击,把当前环境信息上传给开发者排查</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="settings-secondary-button"
|
||||
@click="uploadDiagnosticLog"
|
||||
>
|
||||
上传日志
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
@ -2259,6 +2272,86 @@ const downloadLatestApp = () => {
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
const uploadDiagnosticLog = async () => {
|
||||
try {
|
||||
const w = window as any;
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const screenInfo =
|
||||
typeof window !== 'undefined'
|
||||
? {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
dpr: window.devicePixelRatio
|
||||
}
|
||||
: null;
|
||||
const bridgeProbe = (name: string) => {
|
||||
const b = w?.[name];
|
||||
if (!b) return { exists: false };
|
||||
const methods: Record<string, any> = {};
|
||||
[
|
||||
'getAppVersionCode',
|
||||
'getAppVersionName',
|
||||
'onThemeChanged',
|
||||
'previewPdf',
|
||||
'isPdfPreviewSupported',
|
||||
'downloadFile',
|
||||
'isSupported',
|
||||
'isModelReady',
|
||||
'isModelPartial',
|
||||
'startRecording',
|
||||
'stopRecording'
|
||||
].forEach((m) => {
|
||||
try {
|
||||
methods[m] = typeof b[m] === 'function';
|
||||
} catch {
|
||||
methods[m] = 'error';
|
||||
}
|
||||
});
|
||||
let versionCode = '';
|
||||
let versionName = '';
|
||||
try {
|
||||
versionCode =
|
||||
typeof b.getAppVersionCode === 'function' ? String(b.getAppVersionCode() || '') : '';
|
||||
versionName =
|
||||
typeof b.getAppVersionName === 'function' ? String(b.getAppVersionName() || '') : '';
|
||||
} catch {}
|
||||
return { exists: true, methods, versionCode, versionName };
|
||||
};
|
||||
const payload = {
|
||||
source: 'diagnostic_log',
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
screen: screenInfo,
|
||||
urlParams: {
|
||||
app_vc: params.get('app_vc') || '',
|
||||
app_vn: params.get('app_vn') || '',
|
||||
app_shell: params.get('app_shell') || ''
|
||||
},
|
||||
bridges: {
|
||||
AndroidThemeBridge: bridgeProbe('AndroidThemeBridge'),
|
||||
AndroidPdfBridge: bridgeProbe('AndroidPdfBridge'),
|
||||
AndroidDownloadBridge: bridgeProbe('AndroidDownloadBridge'),
|
||||
AndroidVoiceBridge: bridgeProbe('AndroidVoiceBridge')
|
||||
},
|
||||
appCurrentVersionCode: appCurrentVersionCode.value,
|
||||
appCurrentVersionName: appCurrentVersionName.value,
|
||||
appUpdateInfo: appUpdateInfo.value,
|
||||
appUpdateError: appUpdateError.value,
|
||||
appUpdateCheckedAt: appUpdateCheckedAt.value,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
const resp = await fetch('/api/client_debug_log', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!resp.ok) throw new Error(`status ${resp.status}`);
|
||||
alert('诊断日志已上传');
|
||||
} catch (e: any) {
|
||||
alert('上传诊断日志失败:' + (e?.message || String(e)));
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
hydrateAppVersionFromBridge();
|
||||
try {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user