feat: 租戶建立自動產生預設網域與管理員帳號
- 修改 TenantController 自動產生預設網域 ({tenant_id}.{TENANT_DEFAULT_DOMAIN})
- 新增 TenantDatabaseSeeder 自動建立 admin 帳號
- 啟用 SeedDatabase Job 在建立租戶時自動執行 seeder
- 新增 TENANT_DEFAULT_DOMAIN 環境變數支援不同環境
- 補充中央資料庫所需的 migrations
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
APP_NAME=KooriERP
|
||||
COMPOSE_PROJECT_NAME=koori-erp
|
||||
APP_NAME=StarERP
|
||||
COMPOSE_PROJECT_NAME=star-erp
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
@@ -7,6 +7,7 @@ APP_URL=http://localhost
|
||||
|
||||
# Multi-tenancy 設定 (用逗號分隔多個中央網域)
|
||||
CENTRAL_DOMAINS=localhost,127.0.0.1
|
||||
TENANT_DEFAULT_DOMAIN=star-erp.test
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
@@ -27,7 +28,7 @@ LOG_LEVEL=debug
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=koori_erp
|
||||
DB_DATABASE=star_erp
|
||||
DB_USERNAME=sail
|
||||
DB_PASSWORD=password
|
||||
FORWARD_DB_PORT=3307
|
||||
|
||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
||||
# Koori ERP
|
||||
# Star ERP
|
||||
|
||||
本專案是一個基於 Laravel 12, Inertia.js (React) 與 Tailwind CSS 開發的 ERP 系統。
|
||||
|
||||
@@ -36,25 +36,25 @@ cp .env.example .env
|
||||
# 背景執行容器
|
||||
docker compose up -d --build
|
||||
|
||||
docker exec -it koori-erp-laravel.test-1 composer install
|
||||
docker exec -it star-erp-laravel composer install
|
||||
|
||||
# 生成 App Key
|
||||
docker exec -it koori-erp-laravel.test-1 php artisan key:generate
|
||||
docker exec -it star-erp-laravel php artisan key:generate
|
||||
```
|
||||
|
||||
### 3. 資料庫遷移與初始化
|
||||
|
||||
```bash
|
||||
# (選填) 如果有種子資料
|
||||
docker exec -it koori-erp-laravel.test-1 php artisan migrate --seed
|
||||
docker exec -it star-erp-laravel php artisan migrate --seed
|
||||
|
||||
```
|
||||
|
||||
### 4. 啟動前端開發伺服器 (Vite)
|
||||
|
||||
```bash
|
||||
docker exec -it koori-erp-laravel npm install
|
||||
docker exec -it koori-erp-laravel npm run dev
|
||||
docker exec -it star-erp-laravel npm install
|
||||
docker exec -it star-erp-laravel npm run dev
|
||||
```
|
||||
|
||||
啟動後,您可以透過以下連結瀏覽專案:
|
||||
|
||||
@@ -58,10 +58,12 @@ class TenantController extends Controller
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// 如果有指定域名,則綁定
|
||||
if (!empty($validated['domain'])) {
|
||||
$tenant->domains()->create(['domain' => $validated['domain']]);
|
||||
}
|
||||
// 綁定網域(如果沒有輸入,使用預設網域)
|
||||
$defaultDomain = env('TENANT_DEFAULT_DOMAIN', 'star-erp.test');
|
||||
$domain = !empty($validated['domain'])
|
||||
? $validated['domain']
|
||||
: $validated['id'] . '.' . $defaultDomain;
|
||||
$tenant->domains()->create(['domain' => $domain]);
|
||||
|
||||
return redirect()->route('landlord.tenants.index')
|
||||
->with('success', "租戶 {$validated['name']} 建立成功!");
|
||||
|
||||
@@ -27,7 +27,7 @@ class TenancyServiceProvider extends ServiceProvider
|
||||
JobPipeline::make([
|
||||
Jobs\CreateDatabase::class,
|
||||
Jobs\MigrateDatabase::class,
|
||||
// Jobs\SeedDatabase::class,
|
||||
Jobs\SeedDatabase::class,
|
||||
|
||||
// Your own jobs to prepare the tenant.
|
||||
// Provision API keys, create S3 buckets, anything you want!
|
||||
|
||||
12
compose.yaml
12
compose.yaml
@@ -6,8 +6,8 @@ services:
|
||||
args:
|
||||
WWWGROUP: '${WWWGROUP}'
|
||||
image: 'sail-8.5/app'
|
||||
container_name: koori-erp-laravel
|
||||
hostname: koori-erp-laravel
|
||||
container_name: star-erp-laravel
|
||||
hostname: star-erp-laravel
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
ports:
|
||||
@@ -29,8 +29,8 @@ services:
|
||||
# - mailpit
|
||||
mysql:
|
||||
image: 'mysql/mysql-server:8.0'
|
||||
container_name: koori-erp-mysql
|
||||
hostname: koori-erp-mysql
|
||||
container_name: star-erp-mysql
|
||||
hostname: star-erp-mysql
|
||||
ports:
|
||||
- '${FORWARD_DB_PORT:-3306}:3306'
|
||||
environment:
|
||||
@@ -56,8 +56,8 @@ services:
|
||||
timeout: 5s
|
||||
redis:
|
||||
image: 'redis:alpine'
|
||||
container_name: koori-erp-redis
|
||||
hostname: koori-erp-redis
|
||||
container_name: star-erp-redis
|
||||
hostname: star-erp-redis
|
||||
# ports:
|
||||
# - '${FORWARD_REDIS_PORT:-6379}:6379'
|
||||
volumes:
|
||||
|
||||
@@ -192,7 +192,7 @@ return [
|
||||
* Parameters used by the tenants:seed command.
|
||||
*/
|
||||
'seeder_parameters' => [
|
||||
'--class' => 'DatabaseSeeder', // root seeder class
|
||||
'--class' => 'TenantDatabaseSeeder', // 租戶專用 seeder
|
||||
// '--force' => true, // This needs to be true to seed tenant databases in production
|
||||
],
|
||||
];
|
||||
|
||||
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('username')->unique()->after('name');
|
||||
$table->string('email')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('username');
|
||||
$table->string('email')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // permission id
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
// $table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::drop($tableNames['role_has_permissions']);
|
||||
Schema::drop($tableNames['model_has_roles']);
|
||||
Schema::drop($tableNames['model_has_permissions']);
|
||||
Schema::drop($tableNames['roles']);
|
||||
Schema::drop($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->string('display_name')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('display_name');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$roles = [
|
||||
'super-admin' => '系統管理員',
|
||||
'admin' => '一般管理員',
|
||||
'warehouse-manager' => '倉庫管理員',
|
||||
'purchaser' => '採購人員',
|
||||
'viewer' => '檢視人員',
|
||||
];
|
||||
|
||||
foreach ($roles as $name => $displayName) {
|
||||
DB::table('roles')
|
||||
->where('name', $name)
|
||||
->update(['display_name' => $displayName]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$roles = [
|
||||
'super-admin',
|
||||
'admin',
|
||||
'warehouse-manager',
|
||||
'purchaser',
|
||||
'viewer',
|
||||
];
|
||||
|
||||
DB::table('roles')
|
||||
->whereIn('name', $roles)
|
||||
->update(['display_name' => null]);
|
||||
}
|
||||
};
|
||||
42
database/seeders/TenantDatabaseSeeder.php
Normal file
42
database/seeders/TenantDatabaseSeeder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\User;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
/**
|
||||
* 租戶資料庫專用 Seeder
|
||||
*
|
||||
* 建立新租戶時會自動執行此 Seeder,負責:
|
||||
* 1. 建立預設的超級管理員帳號
|
||||
* 2. 設定權限與角色
|
||||
*/
|
||||
class TenantDatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// 建立預設管理員帳號
|
||||
$admin = User::firstOrCreate(
|
||||
['username' => 'admin'],
|
||||
[
|
||||
'name' => '系統管理員',
|
||||
'email' => 'admin@example.com',
|
||||
'password' => 'password',
|
||||
]
|
||||
);
|
||||
|
||||
// 呼叫權限 Seeder 設定權限與角色
|
||||
$this->call(PermissionSeeder::class);
|
||||
|
||||
// 確保 admin 擁有 super-admin 角色
|
||||
if (!$admin->hasRole('super-admin')) {
|
||||
$admin->assignRole('super-admin');
|
||||
}
|
||||
}
|
||||
}
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "html",
|
||||
"name": "star-erp",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "star-erp",
|
||||
"dependencies": {
|
||||
"@inertiajs/react": "^2.3.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/package.json",
|
||||
"name": "star-erp",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function LandlordLogin() {
|
||||
<div className="w-full max-w-md p-8 relative z-10">
|
||||
<div className="flex flex-col items-center mb-8">
|
||||
{/* 使用不同風格的 Logo 或純文字 */}
|
||||
<div className="text-white text-3xl font-bold tracking-wider mb-2">Koori ERP</div>
|
||||
<div className="text-white text-3xl font-bold tracking-wider mb-2">Star ERP</div>
|
||||
<div className="text-slate-400 text-sm tracking-widest uppercase">Central Administration</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function Welcome() {
|
||||
<Head title="Welcome" />
|
||||
<div className="p-8 bg-white rounded-lg shadow-lg">
|
||||
<h1 className="text-4xl font-bold text-blue-600">
|
||||
Koori ERP
|
||||
Star ERP
|
||||
</h1>
|
||||
<p className="mt-4 text-gray-600">
|
||||
React + Inertia + Laravel Integration Successful!
|
||||
|
||||
Reference in New Issue
Block a user