初始化

This commit is contained in:
2026-08-08 18:27:38 +08:00
parent efc150c937
commit 060eb79c09
336 changed files with 24613 additions and 15 deletions
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
protected $fillable = [
'category_id',
'user_id',
'title',
'slug',
'summary',
'body',
'cover',
'author',
'status',
'template',
'seo_title',
'seo_description',
'published_at',
];
protected $casts = [
'category_id' => 'integer',
'published_at' => 'datetime',
];
public const TEMPLATES = [
'standard' => '标准文章',
'tech' => '技术文档',
'feature' => '专题报道',
];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* 可见性作用域(与 forum 一致):
* - 审核员(articles.moderate):看全部
* - 登录用户:看已审 + 自己投稿(无论状态)
* - 游客:仅看已审
*/
public function scopeVisibleTo($query, ?User $user = null)
{
if ($user && $user->canPerm('articles.moderate')) {
return $query;
}
if ($user) {
return $query->where(function ($q) use ($user) {
$q->where('status', 'approved')->orWhere('user_id', $user->id);
});
}
return $query->where('status', 'approved');
}
public function scopePublished($query)
{
return $query->where('status', 'approved')
->whereNotNull('published_at')
->orderByDesc('published_at');
}
/**
* 前台可见文章:已审核通过(approved) 且已设定发布时间。
* 用户自己的待审(pending)/驳回(rejected) 由控制器单独查询,不进此作用域。
*/
public function scopeVisiblePublic($query)
{
return $query->where('status', 'approved')
->whereNotNull('published_at');
}
/**
* 前台 SEO 标题:优先用文章自定义,否则回退到标题。
*/
public function seoTitle(): string
{
return $this->seo_title ?: $this->title;
}
public function seoDescription(): string
{
return $this->seo_description ?: ($this->summary ?: $this->title);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Category extends Model
{
protected $fillable = [
'name',
'slug',
'type',
'parent_id',
'sort',
];
protected $casts = [
'parent_id' => 'integer',
'sort' => 'integer',
];
public function products(): HasMany
{
return $this->hasMany(Product::class, 'category_id');
}
public function articles(): HasMany
{
return $this->hasMany(Article::class, 'category_id');
}
public function scopeOfType($query, string $type)
{
return $query->where('type', $type)->orderBy('sort')->orderBy('name');
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ContactMessage extends Model
{
protected $fillable = [
'name',
'email',
'phone',
'company',
'subject',
'message',
'ip',
];
protected $hidden = [
'ip',
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class ForumBoard extends Model
{
protected $fillable = [
'name',
'slug',
'description',
'sort',
];
protected $casts = [
'sort' => 'integer',
];
public function threads(): HasMany
{
return $this->hasMany(ForumThread::class, 'board_id');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class ForumReply extends Model
{
use SoftDeletes;
protected $fillable = [
'thread_id',
'user_id',
'body',
'status',
];
protected $casts = [
'thread_id' => 'integer',
'user_id' => 'integer',
];
public const STATUS_PENDING = 'pending';
public const STATUS_APPROVED = 'approved';
public const STATUS_REJECTED = 'rejected';
/**
* 可见性作用域(同 ForumThread):
* - 审核员:全部
* - 登录用户:已审 + 自己发的
* - 游客:仅已审
*/
public function scopeVisible($query, ?User $user = null)
{
if ($user && $user->canPerm('forum.moderate')) {
return $query;
}
if ($user) {
return $query->where(function ($q) use ($user) {
$q->where('status', self::STATUS_APPROVED)
->orWhere('user_id', $user->id);
});
}
return $query->where('status', self::STATUS_APPROVED);
}
public function thread(): BelongsTo
{
return $this->belongsTo(ForumThread::class, 'thread_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class ForumThread extends Model
{
use SoftDeletes;
protected $fillable = [
'board_id',
'user_id',
'title',
'body',
'status',
'is_pinned',
'is_locked',
'views',
'last_reply_at',
];
protected $casts = [
'board_id' => 'integer',
'user_id' => 'integer',
'is_pinned' => 'boolean',
'is_locked' => 'boolean',
'views' => 'integer',
'last_reply_at' => 'datetime',
];
public const STATUS_PENDING = 'pending';
public const STATUS_APPROVED = 'approved';
public const STATUS_REJECTED = 'rejected';
/**
* 可见性作用域:
* - 审核员(forum.moderate):看全部(含待审/驳回)
* - 登录用户:看已审 + 自己发的(无论状态)
* - 游客:仅看已审
*/
public function scopeVisible($query, ?User $user = null)
{
if ($user && $user->canPerm('forum.moderate')) {
return $query;
}
if ($user) {
return $query->where(function ($q) use ($user) {
$q->where('status', self::STATUS_APPROVED)
->orWhere('user_id', $user->id);
});
}
return $query->where('status', self::STATUS_APPROVED);
}
public function board(): BelongsTo
{
return $this->belongsTo(ForumBoard::class, 'board_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function replies(): HasMany
{
return $this->hasMany(ForumReply::class, 'thread_id');
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class GlossaryTerm extends Model
{
protected $fillable = [
'term',
'slug',
'definition',
'aliases',
'category',
];
public function scopeOrdered($query)
{
return $query->orderBy('term');
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class HeroSlide extends Model
{
protected $fillable = [
'location',
'title',
'content',
'cta_text',
'cta_url',
'cta2_text',
'cta2_url',
'is_active',
'sort_order',
];
protected $casts = [
'is_active' => 'boolean',
'sort_order' => 'integer',
];
public function scopeLocation(Builder $query, string $location): Builder
{
return $query->where('location', $location);
}
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeOrdered(Builder $query): Builder
{
return $query->orderBy('sort_order')->orderBy('id');
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class NavigationMenu extends Model
{
protected $fillable = ['label', 'url', 'is_visible', 'sort_order'];
protected $casts = [
'is_visible' => 'boolean',
'sort_order' => 'integer',
];
/**
* 前台可见菜单:按排序返回。
*/
public function scopeVisible(Builder $query): Builder
{
return $query->where('is_visible', true);
}
public function scopeOrdered(Builder $query): Builder
{
return $query->orderBy('sort_order')->orderBy('id');
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class News extends Model
{
use SoftDeletes;
protected $fillable = [
'title',
'slug',
'category',
'summary',
'body',
'cover',
'status',
'published_at',
'seo_title',
'seo_description',
];
protected $casts = [
'published_at' => 'datetime',
];
public const CATEGORIES = [
'industry' => '行业资讯',
'company' => '公司动态',
'event' => '展会活动',
];
public function scopePublished($query)
{
return $query->where('status', 'published')
->whereNotNull('published_at')
->orderByDesc('published_at');
}
public function categoryLabel(): string
{
return self::CATEGORIES[$this->category] ?? $this->category ?? '—';
}
public function seoTitle(): string
{
return $this->seo_title ?: $this->title;
}
public function seoDescription(): string
{
return $this->seo_description ?: ($this->summary ?: $this->title);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
protected $fillable = [
'category_id',
'name',
'slug',
'model',
'summary',
'description',
'specs',
'cover',
'status',
'sort',
'seo_title',
'seo_description',
];
protected $casts = [
'category_id' => 'integer',
'sort' => 'integer',
];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
public function scopePublished($query)
{
return $query->where('status', 'published')->orderBy('sort')->orderBy('name');
}
public function seoTitle(): string
{
return $this->seo_title ?: ($this->name . ' - 深圳市云创芯电子有限公司');
}
public function seoDescription(): string
{
return $this->seo_description ?: ($this->summary ?: $this->name);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = ['key', 'value', 'group'];
/**
* 读取设置值,未设置时返回默认值。
*/
public static function get(string $key, ?string $default = null): ?string
{
$row = static::where('key', $key)->first();
if (!$row) {
return $default;
}
return $row->value;
}
/**
* 写入或更新设置值。
*/
public static function set(string $key, ?string $value, string $group = 'general'): void
{
static::updateOrCreate(
['key' => $key],
['value' => $value, 'group' => $group]
);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Solution extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'slug',
'power_segment',
'application',
'chip_platform',
'power',
'summary',
'advantages',
'description',
'cover',
'status',
'sort',
'seo_title',
'seo_description',
];
public const POWER_SEGMENTS = [
'lt30' => '30W 以下',
'30-65' => '3065W',
'65-100' => '65100W',
'gt100' => '100W 以上',
];
public const APPLICATIONS = [
'charger' => '充电器',
'powerbank' => '移动电源',
'car' => '车载充电',
'dock' => '扩展坞 / 多口排插',
];
public const CHIP_PLATFORMS = [
'protocol' => '协议芯片',
'soc' => '高集成 SoC',
'rectifier' => '同步整流',
];
public function scopePublished($query)
{
return $query->where('status', 'published')->orderBy('sort')->orderBy('name');
}
public function powerSegmentLabel(): string
{
return self::POWER_SEGMENTS[$this->power_segment] ?? $this->power_segment ?? '—';
}
public function applicationLabel(): string
{
return self::APPLICATIONS[$this->application] ?? $this->application ?? '—';
}
public function chipPlatformLabel(): string
{
return self::CHIP_PLATFORMS[$this->chip_platform] ?? $this->chip_platform ?? '—';
}
public function advantagesList(): array
{
if (! $this->advantages) {
return [];
}
return collect(explode("\n", $this->advantages))
->map(fn ($line) => trim($line))
->filter()
->all();
}
public function seoTitle(): string
{
return $this->seo_title ?: ($this->name . ' - 深圳市云创芯电子有限公司');
}
public function seoDescription(): string
{
return $this->seo_description ?: ($this->summary ?: $this->name);
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'is_admin',
'role',
'permissions',
'points',
'real_name',
'phone',
'company',
'industry',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_admin' => 'boolean',
'role' => 'string',
'permissions' => 'array',
'points' => 'integer',
];
}
/**
* 等级阈值(积分下限),键名即等级 L1..L5
*/
public const LEVELS = [
1 => 0,
2 => 50,
3 => 200,
4 => 600,
5 => 1500,
];
public const LEVEL_NAMES = [
1 => '新芯用户',
2 => '活跃用户',
3 => '资深用户',
4 => '核心用户',
5 => '云创芯达人',
];
/**
* 兼容旧逻辑:有后台角色即视为「管理员」(用于前台「后台」入口判断)。
* 新逻辑请使用 role() / canPerm()。
*/
public function isAdmin(): bool
{
return $this->role !== 'user';
}
/**
* 角色等级(1-5),用于层级判断。
*/
public function roleLevel(): int
{
return (int) config("permissions.roles.{$this->role}.level", 1);
}
public function roleLabel(): string
{
return config("permissions.roles.{$this->role}.label", '注册用户');
}
public function isSuperAdmin(): bool
{
return $this->role === 'super_admin';
}
public function isStaff(): bool
{
return $this->role === 'staff';
}
/**
* 细粒度权限判定。
* - 超级管理员:恒 true
* - 内部员工:查 users.permissions(管理员勾选分配)
* - 其他角色:查 config/permissions.php 中该角色的默认权限集
*/
public function canPerm(string $key): bool
{
if ($this->role === 'super_admin') {
return true;
}
if ($this->role === 'staff') {
$perms = is_array($this->permissions) ? $this->permissions : [];
return in_array($key, $perms, true);
}
$def = config("permissions.roles.{$this->role}.permissions", []);
if (in_array('*', $def, true)) {
return true;
}
return in_array($key, $def, true);
}
public function threads()
{
return $this->hasMany(ForumThread::class);
}
public function replies()
{
return $this->hasMany(ForumReply::class);
}
/**
* 增加积分(用于注册、发帖、回帖等动作)。
*/
public function awardPoints(int $amount): void
{
$this->increment('points', max(0, $amount));
}
/**
* 当前等级(1-5)。
*/
public function level(): int
{
$points = $this->points ?? 0;
$level = 1;
foreach (self::LEVELS as $lv => $threshold) {
if ($points >= $threshold) {
$level = $lv;
}
}
return $level;
}
public function levelName(): string
{
return self::LEVEL_NAMES[$this->level()] ?? '用户';
}
/**
* 当前等级进度(用于前台进度条展示)。
*/
public function levelProgress(): array
{
$level = $this->level();
$current = self::LEVELS[$level] ?? 0;
$next = self::LEVELS[$level + 1] ?? null;
if ($next === null) {
return ['level' => $level, 'current' => $this->points, 'next' => null, 'percent' => 100];
}
$span = $next - $current;
$done = $this->points - $current;
$percent = $span > 0 ? (int) min(100, round($done / $span * 100)) : 100;
return ['level' => $level, 'current' => $this->points, 'next' => $next, 'percent' => $percent];
}
}