[FEAT] 新增 APK 上傳檔名自動解析與優化中國醫風味選項

1. 新增 APK 上傳時的檔名自動解析功能(可從 app-[flavor]-[version_name] 格式自動解析並填入版本名稱與機台風味)。
2. 新增版本代碼的自動數學推導,將 Major_Minor_Patch 轉換為整數(例如 21_01_6_R 轉換為 210106)並自動填入。
3. 將原本「中國醫客製」風味選項由 S_12_XY_U_Cmuh 修正為 S_9_XY_U_Cmuh(S9 主板)。
4. 重構 create.blade.php 表單,將原本位於 HTML x-data 屬性內部的複雜 JS 邏輯抽離至獨立的 <script> 區塊(apkVersionForm 函數),解決 HTML 屬性與 JS 引號/括號衝突導致的渲染問題。
5. 引入 Alpine.js 的 x-model 機制對版本名稱(versionName)與版本代碼進行雙向綁定,確保自動解析賦值的強健性。
This commit is contained in:
sky121113 2026-05-28 09:35:31 +08:00
parent dd5e78a9ff
commit b0fc0e6520

View File

@ -19,76 +19,7 @@
</x-page-header>
<form id="create-apk-form"
x-data="{
uploadMode: 'file',
fileName: '',
isDragging: false,
isForced: false,
versionCode: '{{ old('version_code', '') }}',
adjustVersionCode(delta) {
const current = parseInt(this.versionCode) || 0;
const next = Math.max(1, current + delta);
this.versionCode = next.toString();
},
submitForm() {
const form = this.$el;
const versionName = form.querySelector('[name=version_name]').value.trim();
const versionCode = form.querySelector('[name=version_code]').value.trim();
if (!versionName) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please enter version name') }}', type: 'error' }
}));
return;
}
if (!versionCode || isNaN(versionCode) || parseInt(versionCode) <= 0) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Version code must be a positive integer') }}', type: 'error' }
}));
return;
}
if (this.uploadMode === 'file') {
const fileInput = form.querySelector('[name=apk_file]');
if (!fileInput.files.length) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please select an APK file to upload') }}', type: 'error' }
}));
return;
}
} else {
const urlInput = form.querySelector('[name=download_url]').value.trim();
if (!urlInput) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please provide a download URL') }}', type: 'error' }
}));
return;
}
}
form.submit();
},
handleFileDrop(event) {
this.isDragging = false;
const files = event.dataTransfer.files;
if (files.length > 0) {
const fileInput = this.$refs.apkFileInput;
const dt = new DataTransfer();
dt.items.add(files[0]);
fileInput.files = dt.files;
this.fileName = files[0].name;
}
},
handleFileSelect(event) {
if (event.target.files.length > 0) {
this.fileName = event.target.files[0].name;
}
}
}"
x-data="apkVersionForm()"
@submit.prevent="submitForm"
action="{{ route('admin.basic-settings.apk-versions.store') }}"
method="POST"
@ -116,7 +47,7 @@
{{-- 版本名稱 --}}
<div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Version Name') }} <span class="text-rose-500">*</span></label>
<input type="text" name="version_name" value="{{ old('version_name') }}" required class="luxury-input w-full" placeholder="{{ __('e.g., 10_08_6_R') }}">
<input type="text" name="version_name" x-model="versionName" required class="luxury-input w-full" placeholder="{{ __('e.g., 10_08_6_R') }}">
<p class="mt-1.5 text-[10px] font-bold text-slate-400/70 dark:text-slate-500/70 tracking-wide">{{ __('Format: Major_Minor_Patch_R') }}</p>
</div>
{{-- 版本代碼 --}}
@ -152,7 +83,7 @@
{{-- S號 / Android 12 / XY主板 × 客戶專案 --}}
<option value="S_12_XY_U_Chengwai" data-title="S_12_XY_U_Chengwai 晟崴客製 / S12 / XY主板">S_12_XY_U_Chengwai 晟崴客製 / S12 / XY主板</option>
<option value="S_12_XY_U_General" data-title="S_12_XY_U_General 通用主幹 / S12 / XY主板">S_12_XY_U_General  (通用主幹 / S12 / XY主板</option>
<option value="S_12_XY_U_Cmuh" data-title="S_12_XY_U_Cmuh 中國醫客製 / S12 / XY主板">S_12_XY_U_Cmuh   (中國醫客製 / S12 / XY主板</option>
<option value="S_9_XY_U_Cmuh" data-title="S_9_XY_U_Cmuh 中國醫客製 / S9 / XY主板">S_9_XY_U_Cmuh   (中國醫客製 / S9 / XY主板</option>
</x-searchable-select>
<p class="mt-1.5 text-[10px] font-bold text-slate-400/70 dark:text-slate-500/70 tracking-wide">{{ __('Target hardware flavor for this APK') }}</p>
</div>
@ -292,4 +223,121 @@
</div>
</form>
</div>
<script>
function apkVersionForm() {
return {
uploadMode: 'file',
fileName: '',
isDragging: false,
isForced: false,
versionName: '{{ old('version_name', '') }}',
versionCode: '{{ old('version_code', '') }}',
adjustVersionCode(delta) {
const current = parseInt(this.versionCode) || 0;
const next = Math.max(1, current + delta);
this.versionCode = next.toString();
},
submitForm() {
const form = this.$el;
const versionName = this.versionName.trim();
const versionCode = this.versionCode.trim();
if (!versionName) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please enter version name') }}', type: 'error' }
}));
return;
}
if (!versionCode || isNaN(versionCode) || parseInt(versionCode) <= 0) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Version code must be a positive integer') }}', type: 'error' }
}));
return;
}
if (this.uploadMode === 'file') {
const fileInput = form.querySelector('[name=apk_file]');
if (!fileInput.files.length) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please select an APK file to upload') }}', type: 'error' }
}));
return;
}
} else {
const urlInput = form.querySelector('[name=download_url]').value.trim();
if (!urlInput) {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('Please provide a download URL') }}', type: 'error' }
}));
return;
}
}
form.submit();
},
parseApkName(name) {
const baseName = name.replace(/\.apk$/i, '');
// Match "app-{flavor}-{versionName}" pattern
const pattern = /^app-([^-]+)-(\d+_\d+_\d+_[a-zA-Z0-9]+)/i;
const match = baseName.match(pattern);
if (match) {
const flavor = match[1];
const versionName = match[2];
// 1. Fill Version Name (Alpine.js model binding)
this.versionName = versionName;
// 2. Parse & Calculate Version Code (e.g. 10_01_6_R -> 10 * 10000 + 1 * 100 + 6 = 100106)
const parts = versionName.split('_');
if (parts.length >= 3) {
const major = parseInt(parts[0]) || 0;
const minor = parseInt(parts[1]) || 0;
const patch = parseInt(parts[2]) || 0;
this.versionCode = (major * 10000 + minor * 100 + patch).toString();
}
// 3. Select Flavor (Preline HSSelect integration)
const selectEl = document.getElementById('apk-flavor-select');
if (selectEl) {
selectEl.value = flavor;
const instance = window.HSSelect?.getInstance(selectEl);
if (instance) {
instance.setValue(flavor);
}
}
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: '{{ __('已自動解析 APK 檔名並填入欄位!') }}', type: 'success' }
}));
}
},
handleFileDrop(event) {
this.isDragging = false;
const files = event.dataTransfer.files;
if (files.length > 0) {
const fileInput = this.$refs.apkFileInput;
const dt = new DataTransfer();
dt.items.add(files[0]);
fileInput.files = dt.files;
this.fileName = files[0].name;
this.parseApkName(this.fileName);
}
},
handleFileSelect(event) {
if (event.target.files.length > 0) {
this.fileName = event.target.files[0].name;
this.parseApkName(this.fileName);
}
}
};
}
</script>
@endsection