[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:
parent
dd5e78a9ff
commit
b0fc0e6520
@ -19,76 +19,7 @@
|
|||||||
</x-page-header>
|
</x-page-header>
|
||||||
|
|
||||||
<form id="create-apk-form"
|
<form id="create-apk-form"
|
||||||
x-data="{
|
x-data="apkVersionForm()"
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
@submit.prevent="submitForm"
|
@submit.prevent="submitForm"
|
||||||
action="{{ route('admin.basic-settings.apk-versions.store') }}"
|
action="{{ route('admin.basic-settings.apk-versions.store') }}"
|
||||||
method="POST"
|
method="POST"
|
||||||
@ -116,7 +47,7 @@
|
|||||||
{{-- 版本名稱 --}}
|
{{-- 版本名稱 --}}
|
||||||
<div>
|
<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>
|
<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>
|
<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>
|
</div>
|
||||||
{{-- 版本代碼 --}}
|
{{-- 版本代碼 --}}
|
||||||
@ -152,7 +83,7 @@
|
|||||||
{{-- S號 / Android 12 / XY主板 × 客戶專案 --}}
|
{{-- 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_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_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>
|
</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>
|
<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>
|
</div>
|
||||||
@ -292,4 +223,121 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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
|
@endsection
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user