diff --git a/app/Admin/Controllers/AppVersionController.php b/app/Admin/Controllers/AppVersionController.php
new file mode 100644
index 0000000..86fdd9a
--- /dev/null
+++ b/app/Admin/Controllers/AppVersionController.php
@@ -0,0 +1,132 @@
+baseCRUD()->tableLayout('fixed')
+ ->headerToolbar([
+ $this->createTypeButton('drawer', 'lg')->visible(Admin::user()->can('admin.app_versions.create')),
+ amis('reload')->align('right'),
+ ])
+ ->bulkActions([])
+ ->columns([
+ amis()->TableColumn('name', __('app_version.name')),
+ amis()->TableColumn('version', __('app_version.version')),
+ amis()->TableColumn('title', __('app_version.title')),
+ amis()->TableColumn('update_strategy', __('app_version.update_strategy'))->type('mapping')->map(AppUpdateStrategy::labelMap()),
+ amis()->TableColumn('is_force', __('app_version.is_force'))->type('static-mapping')->map([
+ '0' => '否',
+ '1' => '是',
+ ]),
+ amis()->TableColumn('os', __('app_version.os'))->type('mapping')->map(AppOs::labelMap()),
+ amis()->TableColumn('is_release', __('app_version.is_release'))->type('static-mapping')->map([
+ '0' => '否',
+ '1' => '是',
+ ]),
+ amis()->TableColumn('release_at', __('app_version.release_at')),
+ amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime'),
+ Operation::make()->label(__('admin.actions'))->buttons([
+ $this->rowEditTypeButton('drawer', 'lg')->visible(Admin::user()->can('admin.app_versions.update')),
+ $this->rowDeleteButton()->visible(Admin::user()->can('admin.app_versions.delete')),
+ $this->rowShowButton()->visible(Admin::user()->can('admin.app_versions.view')),
+ ]),
+ ]);
+
+ return $this->baseList($crud);
+ }
+
+ public function form($isEdit = false): Form
+ {
+ return $this->baseForm()->body([
+ amis()->TextControl('name', __('app_version.name'))
+ ->placeholder(__('app_version.name'))
+ ->required(),
+ amis()->NumberControl('version', __('app_version.version'))
+ ->placeholder(__('app_version.version'))
+ ->precision(0)
+ ->required(),
+ amis()->TextControl('title', __('app_version.title'))
+ ->placeholder(__('app_version.title'))
+ ->required(),
+ amis()->TextareaControl('description', __('app_version.description'))
+ ->placeholder(__('app_version.description'))
+ ->minRows(10)
+ ->required(),
+ amis()->RadiosControl('update_strategy', __('app_version.update_strategy'))
+ ->options(AppUpdateStrategy::options())
+ ->value(AppUpdateStrategy::Wgt->value)
+ ->required(),
+ amis()->RadiosControl('is_force', __('app_version.is_force'))
+ ->options([
+ ['label' => '是', 'value' => true],
+ ['label' => '否', 'value' => false],
+ ])
+ ->value(false)
+ ->required(),
+ amis()->RadiosControl('os', __('app_version.os'))->options(AppOs::options())->value(AppOs::Android->value)->required(),
+
+ amis()->FileControl('android[wgt_url]', __('app_version.wgt_url'))
+ ->accept('.wgt')
+ ->useChunk(false)
+ ->visibleOn('${AND(os == "'.AppOs::Android->value.'", update_strategy == "wgt")}'),
+ amis()->FileControl('android[apk_url]', __('app_version.apk_url'))
+ ->accept('.apk')
+ ->useChunk(false)
+ ->visibleOn('${os == "'.AppOs::Android->value.'"}'),
+
+ amis()->FileControl('ios[wgt_url]', __('app_version.wgt_url'))
+ ->accept('.wgt')
+ ->useChunk(false)
+ ->visibleOn('${AND(os == "'.AppOs::Ios->value.'", update_strategy == "wgt")}'),
+ amis()->TextControl('ios[apk_url]', '苹果应用地址')
+ ->placeholder('苹果应用地址')
+ ->visibleOn('${os == "'.AppOs::Ios->value.'"}'),
+
+ amis()->DateTimeControl('release_at', __('app_version.release_at'))
+ ->valueFormat('YYYY-MM-DD HH:mm:ss')
+ ->description('必须设置发布时间,才可更新'),
+ ]);
+ }
+
+ public function detail(): Form
+ {
+ return $this->baseDetail()->body([
+ amis()->Property()->items([
+ ['label' => __('app_version.name'), 'content' => '${name}'],
+ ['label' => __('app_version.version'), 'content' => '${version}'],
+ ['label' => __('app_version.os'), 'content' => amis()->Mapping()->name('os')->map(AppOs::labelMap())],
+ ['label' => __('app_version.title'), 'content' => '${title}', 'span' => 3],
+ ['label' => __('app_version.description'), 'content' => amis()->TextareaControl()->name('description')->static()->labelClassName('hidden'), 'span' => 3],
+ ['label' => __('app_version.update_strategy'), 'content' => amis()->Mapping()->name('update_strategy')->map(AppUpdateStrategy::labelMap())],
+ [
+ 'label' => __('app_version.is_force'),
+ 'content' => amis()->Mapping()->name('${is_force}')->static()->map([
+ '0' => '否',
+ '1' => '是',
+ ]),
+ ],
+ ['label' => __('app_version.apk_url'), 'content' => amis()->Link()->href('${apk_url}')->body('下载')->blank()->visibleOn('${apk_url != null && apk_url != ""}')],
+ ['label' => __('app_version.wgt_url'), 'content' => amis()->Link()->href('${wgt_url}')->body('下载')->blank()->visibleOn('${wgt_url != null && wgt_url != ""}')],
+ ['label' => __('app_version.release_at'), 'content' => '${release_at}'],
+ ['label' => __('app_version.created_at'), 'content' => '${created_at}'],
+ ]),
+ ]);
+ }
+}
diff --git a/app/Admin/Filters/AppVersionFilter.php b/app/Admin/Filters/AppVersionFilter.php
new file mode 100644
index 0000000..f256ffe
--- /dev/null
+++ b/app/Admin/Filters/AppVersionFilter.php
@@ -0,0 +1,9 @@
+os) {
+ case AppOs::Android:
+ $instance->setAttribute('android', [
+ 'apk_url' => $instance->apk_url,
+ 'wgt_url' => $instance->wgt_url,
+ ]);
+ break;
+
+ case AppOs::Ios:
+ $instance->setAttribute('ios', [
+ 'apk_url' => $instance->apk_url,
+ 'wgt_url' => $instance->wgt_url,
+ ]);
+ break;
+ }
+ });
+ }
+
+ public function store($data): bool
+ {
+ Validator::validate(
+ data: $data,
+ rules: [
+ 'name' => ['bail', 'required', 'max:255'],
+ 'version' => ['bail', 'required', 'int'],
+ 'title' => ['bail', 'required', 'max:255'],
+ 'description' => ['bail', 'required'],
+ 'update_strategy' => ['bail', 'required', Rule::enum(AppUpdateStrategy::class)],
+ 'is_force' => ['bail', 'required', 'bool'],
+ 'os' => ['bail', 'required', Rule::enum(AppOs::class)],
+ 'release_at' => ['bail', 'nullable', 'date_format:Y-m-d H:i:s'],
+ ],
+ attributes: [
+ 'name' => __('app_version.name'),
+ 'version' => __('app_version.version'),
+ 'title' => __('app_version.title'),
+ 'description' => __('app_version.description'),
+ 'update_strategy' => __('app_version.update_strategy'),
+ 'is_force' => __('app_version.is_force'),
+ 'os' => __('app_version.os'),
+ 'release_at' => __('app_version.release_at'),
+ ],
+ );
+
+ // 全量包
+ $apkUrl = '';
+ // 增量包
+ $wgtUrl = '';
+ // 更新策略
+ $aus = AppUpdateStrategy::from($data['update_strategy']);
+
+ switch (AppOs::from($data['os'])) {
+ case AppOs::Android:
+ $apkUrl = (string) Arr::get($data, 'android.apk_url');
+ $wgtUrl = (string) Arr::get($data, 'android.wgt_url');
+ if ($aus === AppUpdateStrategy::Apk) {
+ admin_abort_if($apkUrl === '', '请上传全量包');
+ } else {
+ admin_abort_if($wgtUrl === '', '请上传增量包');
+ }
+ break;
+
+ case AppOs::Ios:
+ $apkUrl = (string) Arr::get($data, 'ios.apk_url');
+ $wgtUrl = (string) Arr::get($data, 'ios.wgt_url');
+ if ($aus === AppUpdateStrategy::Apk) {
+ admin_abort_if($apkUrl === '', '苹果应用地址 不能为空。');
+ } else {
+ admin_abort_if($wgtUrl === '', '请上传增量包');
+ }
+ break;
+
+ default:
+ admin_abort('未知操作系统');
+ break;
+ }
+
+ $model = $this->getModel();
+
+ $instance = new $model($data);
+
+ try {
+ $filename = $data['name'].'_'.time();
+ if ($extension = pathinfo($apkUrl, PATHINFO_EXTENSION)) {
+ $filename .= '.'.$extension;
+ }
+ $instance->apk_url = $this->saveUploadedFile($apkUrl, "app-versions/{$filename}");
+ } catch (FileNotFoundException $e) {
+ admin_abort('全量包未找到,请重新上传');
+ }
+
+ try {
+ $filename = $data['name'].'_'.time();
+ if ($extension = pathinfo($wgtUrl, PATHINFO_EXTENSION)) {
+ $filename .= '.'.$extension;
+ }
+ $instance->wgt_url = $this->saveUploadedFile($wgtUrl, "app-versions/{$filename}");
+ } catch (FileNotFoundException $e) {
+ admin_abort('增量包未找到,请重新上传');
+ }
+
+ return $instance->save();
+ }
+
+ public function update($primaryKey, $data): bool
+ {
+ Validator::validate(
+ data: $data,
+ rules: [
+ 'name' => ['bail', 'required', 'max:255'],
+ 'version' => ['bail', 'required', 'int'],
+ 'title' => ['bail', 'required', 'max:255'],
+ 'description' => ['bail', 'required'],
+ 'update_strategy' => ['bail', 'required', Rule::enum(AppUpdateStrategy::class)],
+ 'is_force' => ['bail', 'required', 'bool'],
+ 'os' => ['bail', 'required', Rule::enum(AppOs::class)],
+ 'release_at' => ['bail', 'nullable', 'date_format:Y-m-d H:i:s'],
+ ],
+ attributes: [
+ 'name' => __('app_version.name'),
+ 'version' => __('app_version.version'),
+ 'title' => __('app_version.title'),
+ 'description' => __('app_version.description'),
+ 'update_strategy' => __('app_version.update_strategy'),
+ 'is_force' => __('app_version.is_force'),
+ 'os' => __('app_version.os'),
+ 'release_at' => __('app_version.release_at'),
+ ],
+ );
+
+ // 全量包
+ $apkUrl = '';
+ // 增量包
+ $wgtUrl = '';
+ // 更新策略
+ $aus = AppUpdateStrategy::from($data['update_strategy']);
+
+ switch (AppOs::from($data['os'])) {
+ case AppOs::Android:
+ $apkUrl = (string) Arr::get($data, 'android.apk_url');
+ $wgtUrl = (string) Arr::get($data, 'android.wgt_url');
+ if ($aus === AppUpdateStrategy::Apk) {
+ admin_abort_if($apkUrl === '', '请上传全量包');
+ } else {
+ admin_abort_if($wgtUrl === '', '请上传增量包');
+ }
+ break;
+
+ case AppOs::Ios:
+ $apkUrl = (string) Arr::get($data, 'ios.apk_url');
+ $wgtUrl = (string) Arr::get($data, 'ios.wgt_url');
+ if ($aus === AppUpdateStrategy::Apk) {
+ admin_abort_if($apkUrl === '', '苹果应用地址 不能为空。');
+ } else {
+ admin_abort_if($wgtUrl === '', '请上传增量包');
+ }
+ break;
+
+ default:
+ admin_abort('未知操作系统');
+ break;
+ }
+
+ $instance = $this->query()->whereKey($primaryKey)->firstOrFail();
+ $instance->fill($data);
+
+ try {
+ $filename = $data['name'].'_'.time();
+ if ($extension = pathinfo($apkUrl, PATHINFO_EXTENSION)) {
+ $filename .= '.'.$extension;
+ }
+ $instance->apk_url = $this->saveUploadedFile($apkUrl, "app-versions/{$filename}");
+ } catch (FileNotFoundException $e) {
+ admin_abort('全量包未找到,请重新上传');
+ }
+
+ try {
+ $filename = $data['name'].'_'.time();
+ if ($extension = pathinfo($wgtUrl, PATHINFO_EXTENSION)) {
+ $filename .= '.'.$extension;
+ }
+ $instance->wgt_url = $this->saveUploadedFile($wgtUrl, "app-versions/{$filename}");
+ } catch (FileNotFoundException $e) {
+ admin_abort('增量包未找到,请重新上传');
+ }
+
+ return $instance->save();
+ }
+
+ protected function saveUploadedFile(string $from, string $to): string
+ {
+ if ($from === '' || preg_match('/^https?:\/\//', $from)) {
+ return $from;
+ }
+
+ $disk = Storage::disk(Admin::config('admin.upload.disk'));
+
+ if (! $disk->exists($from)) {
+ throw new FileNotFoundException();
+ }
+
+ $disk->move($from, $to);
+
+ return $disk->url($to);
+ }
+}
diff --git a/app/Admin/routes.php b/app/Admin/routes.php
index 6a110c3..315943f 100644
--- a/app/Admin/routes.php
+++ b/app/Admin/routes.php
@@ -1,6 +1,7 @@
post('agreement/download', [AgreementController::class, 'download'])->name('agreement.download');
$router->resource('agreement', AgreementController::class);
+ $router->resource('app-versions', AppVersionController::class);
+
// 修改上传
$router->post('upload_file', [\App\Admin\Controllers\IndexController::class, 'uploadFile']);
$router->post('upload_image', [\App\Admin\Controllers\IndexController::class, 'uploadImage']);
diff --git a/app/Enums/AppOs.php b/app/Enums/AppOs.php
new file mode 100644
index 0000000..0375e64
--- /dev/null
+++ b/app/Enums/AppOs.php
@@ -0,0 +1,30 @@
+options()[$this->value];
+ }
+
+ public static function options()
+ {
+ return [
+ self::Android->value => '安卓',
+ self::Ios->value => '苹果',
+ ];
+ }
+
+ public static function labelMap(): array
+ {
+ return [
+ self::Android->value => ''.self::Android->text().'',
+ self::Ios->value => ''.self::Ios->text().'',
+ ];
+ }
+}
diff --git a/app/Enums/AppUpdateStrategy.php b/app/Enums/AppUpdateStrategy.php
new file mode 100644
index 0000000..b925a63
--- /dev/null
+++ b/app/Enums/AppUpdateStrategy.php
@@ -0,0 +1,30 @@
+options()[$this->value];
+ }
+
+ public static function options(): array
+ {
+ return [
+ self::Apk->value => '全量包',
+ self::Wgt->value => '热更新',
+ ];
+ }
+
+ public static function labelMap(): array
+ {
+ return [
+ self::Apk->value => ''.self::Apk->text().'',
+ self::Wgt->value => ''.self::Wgt->text().'',
+ ];
+ }
+}
diff --git a/app/Models/AppVersion.php b/app/Models/AppVersion.php
new file mode 100644
index 0000000..031997a
--- /dev/null
+++ b/app/Models/AppVersion.php
@@ -0,0 +1,53 @@
+ false,
+ ];
+
+ protected $casts = [
+ 'os' => AppOs::class,
+ 'update_strategy' => AppUpdateStrategy::class,
+ 'is_force' => 'bool',
+ 'release_at' => 'datetime',
+ ];
+
+ protected $fillable = [
+ 'os',
+ 'name',
+ 'version',
+ 'title',
+ 'description',
+ 'update_strategy',
+ 'is_force',
+ 'apk_url',
+ 'wgt_url',
+ 'release_at',
+ ];
+
+ protected function isRelease(): Attribute
+ {
+ return Attribute::make(
+ get: function (mixed $value, $attributes) {
+ return (bool) $attributes['release_at'];
+ },
+ );
+ }
+}
diff --git a/database/migrations/2024_04_29_152806_create_app_versions_table.php b/database/migrations/2024_04_29_152806_create_app_versions_table.php
new file mode 100644
index 0000000..178075d
--- /dev/null
+++ b/database/migrations/2024_04_29_152806_create_app_versions_table.php
@@ -0,0 +1,37 @@
+id();
+ $table->string('os')->comment('操作系统: android, ios');
+ $table->string('name')->comment('版本名称');
+ $table->integer('version')->comment('版本号');
+ $table->string('title')->nullable()->comment('更新标题');
+ $table->text('description')->nullable()->comment('更新描述');
+ $table->string('apk_url')->nullable()->comment('全量包地址');
+ $table->string('wgt_url')->nullable()->comment('增量包地址');
+ $table->boolean('is_force')->default(false)->comment('是否强制更新');
+ $table->string('update_strategy')->comment('更新策略: apk 全量包, wgt 热更新');
+ $table->timestamp('release_at')->nullable()->comment('发布时间');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('app_versions');
+ }
+};
diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php
index dbb036b..da38bc7 100644
--- a/database/seeders/AdminPermissionSeeder.php
+++ b/database/seeders/AdminPermissionSeeder.php
@@ -15,8 +15,6 @@ class AdminPermissionSeeder extends Seeder
*/
public function run()
{
- // AdminMenu::truncate();
- // AdminPermission::truncate();
$data = [
/*
|--------------------------------------------------------------------------
@@ -359,6 +357,13 @@ class AdminPermissionSeeder extends Seeder
'download' => '打包下载',
]
],
+ 'app_versions' => [
+ 'name' => 'App版本管理',
+ 'icon' => 'flowbite:inbox-full-outline',
+ 'uri' => '/app-versions',
+ 'resource' => true,
+ 'children' => []
+ ],
/*
|--------------------------------------------------------------------------
diff --git a/lang/zh_CN/app_version.php b/lang/zh_CN/app_version.php
new file mode 100644
index 0000000..a2c7295
--- /dev/null
+++ b/lang/zh_CN/app_version.php
@@ -0,0 +1,17 @@
+ 'ID',
+ 'name' => '版本名称',
+ 'version' => '版本号',
+ 'title' => '更新标题',
+ 'description' => '更新描述',
+ 'is_force' => '强制更新',
+ 'update_strategy' => '更新策略',
+ 'os' => '操作系统',
+ 'apk_url' => '全量包',
+ 'wgt_url' => '增量包',
+ 'is_release' => '是否发布',
+ 'release_at' => '发布时间',
+ 'created_at' => '创建时间',
+];