优化更新

This commit is contained in:
2026-02-11 17:13:18 +08:00
parent ada5e027fa
commit e265bcc28d
28 changed files with 1661 additions and 155 deletions

View File

@@ -53,15 +53,21 @@ class DepartmentExport implements FromCollection, WithHeadings, WithMapping, Sho
*/
public function map($department): array
{
$parentName = '';
if ($department->parent_id) {
$parent = Department::find($department->parent_id);
$parentName = $parent ? $parent->name : '';
}
return [
$department->id,
$department->name,
$department->parent_id ? Department::find($department->parent_id)?->name : '',
$parentName,
$department->leader,
$department->phone,
$department->sort,
(int)$department->sort,
$department->status == 1 ? '启用' : '禁用',
$department->created_at ? $department->created_at->toDateTimeString() : '',
$department->created_at ? (string)$department->created_at : '',
];
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Exports;
use App\Models\Auth\Permission;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class PermissionExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $permissionIds;
public function __construct(array $permissionIds = [])
{
$this->permissionIds = $permissionIds;
}
/**
* 获取数据集合
*/
public function collection()
{
$query = Permission::query();
if (!empty($this->permissionIds)) {
$query->whereIn('id', $this->permissionIds);
}
return $query->orderBy('sort')->get();
}
/**
* 设置表头
*/
public function headings(): array
{
return [
'ID',
'权限标题',
'权限编码',
'权限类型',
'父级ID',
'路由路径',
'前端组件',
'排序',
'状态',
'创建时间',
];
}
/**
* 映射数据
*/
public function map($permission): array
{
return [
$permission->id,
$permission->title,
$permission->name,
$this->getTypeName($permission->type),
$permission->parent_id ?: 0,
$permission->path,
$permission->component,
(int)$permission->sort,
$permission->status == 1 ? '启用' : '禁用',
$permission->created_at ? (string)$permission->created_at : '',
];
}
/**
* 获取类型名称
*/
protected function getTypeName($type): string
{
$types = [
'menu' => '菜单',
'api' => 'API接口',
'button' => '按钮',
'url' => '链接',
];
return $types[$type] ?? $type;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Exports;
use App\Models\Auth\Role;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class RoleExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $roleIds;
public function __construct(array $roleIds = [])
{
$this->roleIds = $roleIds;
}
/**
* 获取数据集合
*/
public function collection()
{
$query = Role::with(['permissions']);
if (!empty($this->roleIds)) {
$query->whereIn('id', $this->roleIds);
}
return $query->get();
}
/**
* 设置表头
*/
public function headings(): array
{
return [
'ID',
'角色名称',
'角色编码',
'描述',
'权限',
'排序',
'状态',
'创建时间',
];
}
/**
* 映射数据
*/
public function map($role): array
{
return [
$role->id,
$role->name,
$role->code,
$role->description,
$role->permissions->pluck('title')->implode(','),
(int)$role->sort,
$role->status == 1 ? '启用' : '禁用',
$role->created_at ? (string)$role->created_at : '',
];
}
}

View File

@@ -64,8 +64,8 @@ class UserExport implements FromCollection, WithHeadings, WithMapping, ShouldAut
$user->department ? $user->department->name : '',
$user->roles->pluck('name')->implode(','),
$user->status == 1 ? '启用' : '禁用',
$user->last_login_at ? $user->last_login_at->toDateTimeString() : '',
$user->created_at ? $user->created_at->toDateTimeString() : '',
$user->last_login_at ? (string)$user->last_login_at : '',
$user->created_at ? (string)$user->created_at : '',
];
}
}

View File

@@ -4,15 +4,20 @@ namespace App\Http\Controllers\Auth\Admin;
use App\Http\Controllers\Controller;
use App\Services\Auth\PermissionService;
use App\Services\Auth\ImportExportService;
use Illuminate\Http\Request;
class Permission extends Controller
{
protected $permissionService;
protected $importExportService;
public function __construct(PermissionService $permissionService)
{
public function __construct(
PermissionService $permissionService,
ImportExportService $importExportService
) {
$this->permissionService = $permissionService;
$this->importExportService = $importExportService;
}
/**
@@ -177,4 +182,54 @@ class Permission extends Controller
'data' => ['count' => $count],
]);
}
/**
* 导出权限
*/
public function export(Request $request)
{
$validated = $request->validate([
'ids' => 'nullable|array',
'ids.*' => 'integer',
]);
$filename = $this->importExportService->exportPermissions($validated['ids'] ?? []);
$filePath = $this->importExportService->getExportFilePath($filename);
return response()->download($filePath, $filename)->deleteFileAfterSend();
}
/**
* 导入权限
*/
public function import(Request $request)
{
$validated = $request->validate([
'file' => 'required|file|mimes:xlsx,xls',
]);
$file = $request->file('file');
$realPath = $file->getRealPath();
$filename = $file->getClientOriginalName();
$result = $this->importExportService->importPermissions($filename, $realPath);
return response()->json([
'code' => 200,
'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']}",
'data' => $result,
]);
}
/**
* 下载权限导入模板
*/
public function downloadTemplate()
{
$filename = $this->importExportService->downloadPermissionTemplate();
$filePath = $this->importExportService->getExportFilePath($filename);
return response()->download($filePath, $filename)->deleteFileAfterSend();
}
}

View File

@@ -4,15 +4,20 @@ namespace App\Http\Controllers\Auth\Admin;
use App\Http\Controllers\Controller;
use App\Services\Auth\RoleService;
use App\Services\Auth\ImportExportService;
use Illuminate\Http\Request;
class Role extends Controller
{
protected $roleService;
protected $importExportService;
public function __construct(RoleService $roleService)
{
public function __construct(
RoleService $roleService,
ImportExportService $importExportService
) {
$this->roleService = $roleService;
$this->importExportService = $importExportService;
}
/**
@@ -237,4 +242,54 @@ class Role extends Controller
'data' => $result,
]);
}
/**
* 导出角色
*/
public function export(Request $request)
{
$validated = $request->validate([
'ids' => 'nullable|array',
'ids.*' => 'integer',
]);
$filename = $this->importExportService->exportRoles($validated['ids'] ?? []);
$filePath = $this->importExportService->getExportFilePath($filename);
return response()->download($filePath, $filename)->deleteFileAfterSend();
}
/**
* 导入角色
*/
public function import(Request $request)
{
$validated = $request->validate([
'file' => 'required|file|mimes:xlsx,xls',
]);
$file = $request->file('file');
$realPath = $file->getRealPath();
$filename = $file->getClientOriginalName();
$result = $this->importExportService->importRoles($filename, $realPath);
return response()->json([
'code' => 200,
'message' => "导入完成,成功 {$result['success_count']} 条,失败 {$result['error_count']}",
'data' => $result,
]);
}
/**
* 下载角色导入模板
*/
public function downloadTemplate()
{
$filename = $this->importExportService->downloadRoleTemplate();
$filePath = $this->importExportService->getExportFilePath($filename);
return response()->download($filePath, $filename)->deleteFileAfterSend();
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace App\Imports;
use App\Models\Auth\Permission;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
class PermissionImport implements ToCollection, WithHeadingRow, WithValidation
{
protected $successCount = 0;
protected $errorCount = 0;
protected $errors = [];
/**
* 处理导入数据
*/
public function collection(Collection $rows)
{
foreach ($rows as $index => $row) {
try {
// 跳过空行
if (empty($row['权限标题'])) {
continue;
}
// 检查权限编码是否已存在
$exists = Permission::where('name', $row['权限编码'])->exists();
if ($exists) {
$this->addError($index + 2, '权限编码已存在');
continue;
}
// 查找父级权限
$parentId = null;
if (!empty($row['父级ID']) && $row['父级ID'] != 0) {
$parent = Permission::find($row['父级ID']);
if (!$parent) {
$this->addError($index + 2, '父级权限不存在');
continue;
}
$parentId = $parent->id;
}
// 解析类型
$type = $this->parseType($row['权限类型']);
// 解析元数据
$meta = null;
if ($type === 'menu' && !empty($row['元数据'])) {
// 元数据格式: icon:Setting,hidden:false,keepAlive:false
$meta = $this->parseMeta($row['元数据']);
}
// 创建权限
Permission::create([
'title' => $row['权限标题'],
'name' => $row['权限编码'],
'type' => $type,
'parent_id' => $parentId,
'path' => $row['路由路径'] ?? null,
'component' => $row['前端组件'] ?? null,
'meta' => $meta,
'sort' => $row['排序'] ?? 0,
'status' => 1,
]);
$this->successCount++;
} catch (\Exception $e) {
$this->addError($index + 2, $e->getMessage());
}
}
}
/**
* 验证规则
*/
public function rules(): array
{
return [
'权限标题' => 'required|string|max:50',
'权限编码' => 'required|string|max:100',
'权限类型' => 'required|in:菜单,API接口,按钮,链接,menu,api,button,url',
'父级ID' => 'nullable|integer|min:0',
'路由路径' => 'nullable|string|max:200',
'前端组件' => 'nullable|string|max:200',
'排序' => 'nullable|integer|min:0',
];
}
/**
* 自定义验证消息
*/
public function customValidationMessages(): array
{
return [
'权限标题.required' => '权限标题不能为空',
'权限编码.required' => '权限编码不能为空',
'权限类型.required' => '权限类型不能为空',
];
}
/**
* 解析类型
*/
protected function parseType($type): string
{
$typeMap = [
'菜单' => 'menu',
'API接口' => 'api',
'接口' => 'api',
'按钮' => 'button',
'链接' => 'url',
];
return $typeMap[$type] ?? $type;
}
/**
* 解析元数据
*/
protected function parseMeta($metaString): ?string
{
if (empty($metaString)) {
return null;
}
// 简单解析,实际项目中可能需要更复杂的解析逻辑
return $metaString;
}
/**
* 添加错误
*/
protected function addError(int $row, string $message): void
{
$this->errorCount++;
$this->errors[] = "{$row} 行: {$message}";
}
/**
* 获取成功数量
*/
public function getSuccessCount(): int
{
return $this->successCount;
}
/**
* 获取错误数量
*/
public function getErrorCount(): int
{
return $this->errorCount;
}
/**
* 获取错误信息
*/
public function getErrors(): array
{
return $this->errors;
}
}

129
app/Imports/RoleImport.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
namespace App\Imports;
use App\Models\Auth\Role;
use App\Models\Auth\Permission;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
class RoleImport implements ToCollection, WithHeadingRow, WithValidation
{
protected $successCount = 0;
protected $errorCount = 0;
protected $errors = [];
/**
* 处理导入数据
*/
public function collection(Collection $rows)
{
foreach ($rows as $index => $row) {
try {
// 跳过空行
if (empty($row['角色名称'])) {
continue;
}
// 检查角色编码是否已存在
$exists = Role::where('code', $row['角色编码'])->exists();
if ($exists) {
$this->addError($index + 2, '角色编码已存在');
continue;
}
// 查找权限
$permissionIds = [];
if (!empty($row['权限(多个用逗号分隔)'])) {
$permissionNames = array_map('trim', explode(',', $row['权限(多个用逗号分隔)']));
$permissions = Permission::whereIn('title', $permissionNames)->get();
if ($permissions->count() != count($permissionNames)) {
$existingNames = $permissions->pluck('title')->toArray();
$notFound = array_diff($permissionNames, $existingNames);
$this->addError($index + 2, '权限不存在: ' . implode(', ', $notFound));
continue;
}
$permissionIds = $permissions->pluck('id')->toArray();
}
// 创建角色
$role = Role::create([
'name' => $row['角色名称'],
'code' => $row['角色编码'],
'description' => $row['描述'] ?? null,
'sort' => $row['排序'] ?? 0,
'status' => 1,
]);
// 分配权限
if (!empty($permissionIds)) {
$role->permissions()->attach($permissionIds);
}
$this->successCount++;
} catch (\Exception $e) {
$this->addError($index + 2, $e->getMessage());
}
}
}
/**
* 验证规则
*/
public function rules(): array
{
return [
'角色名称' => 'required|string|max:50',
'角色编码' => 'required|string|max:50',
'描述' => 'nullable|string|max:200',
'排序' => 'nullable|integer|min:0',
];
}
/**
* 自定义验证消息
*/
public function customValidationMessages(): array
{
return [
'角色名称.required' => '角色名称不能为空',
'角色编码.required' => '角色编码不能为空',
];
}
/**
* 添加错误
*/
protected function addError(int $row, string $message): void
{
$this->errorCount++;
$this->errors[] = "{$row} 行: {$message}";
}
/**
* 获取成功数量
*/
public function getSuccessCount(): int
{
return $this->successCount;
}
/**
* 获取错误数量
*/
public function getErrorCount(): int
{
return $this->errorCount;
}
/**
* 获取错误信息
*/
public function getErrors(): array
{
return $this->errors;
}
}

View File

@@ -2,13 +2,14 @@
namespace App\Models\Auth;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Department extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'auth_departments';

View File

@@ -2,13 +2,14 @@
namespace App\Models\Auth;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Permission extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'auth_permissions';

View File

@@ -2,13 +2,14 @@
namespace App\Models\Auth;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Role extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'auth_roles';

View File

@@ -2,6 +2,7 @@
namespace App\Models\Auth;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -11,7 +12,7 @@ use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'auth_users';

View File

@@ -2,11 +2,13 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class City extends Model
{
use ModelTrait;
protected $table = 'system_cities';
protected $fillable = [

View File

@@ -2,12 +2,13 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Config extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'system_configs';

View File

@@ -2,13 +2,14 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Dictionary extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'system_dictionaries';

View File

@@ -2,11 +2,13 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DictionaryItem extends Model
{
use ModelTrait;
protected $table = 'system_dictionary_items';
protected $fillable = [

View File

@@ -2,11 +2,13 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Log extends Model
{
use ModelTrait;
protected $table = 'system_logs';
protected $fillable = [

View File

@@ -2,12 +2,13 @@
namespace App\Models\System;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends Model
{
use SoftDeletes;
use ModelTrait, SoftDeletes;
protected $table = 'system_tasks';

View File

@@ -10,8 +10,12 @@ use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\UserExport;
use App\Exports\DepartmentExport;
use App\Exports\RoleExport;
use App\Exports\PermissionExport;
use App\Imports\UserImport;
use App\Imports\DepartmentImport;
use App\Imports\RoleImport;
use App\Imports\PermissionImport;
class ImportExportService
{
@@ -187,6 +191,166 @@ class ImportExportService
return storage_path('app/exports/' . $filename);
}
/**
* 下载角色导入模板
*/
public function downloadRoleTemplate(): string
{
$filename = 'role_import_template_' . date('YmdHis') . '.xlsx';
// 确保目录存在
if (!is_dir(storage_path('app/exports'))) {
mkdir(storage_path('app/exports'), 0755, true);
}
$templateData = [
[
'角色名称*',
'角色编码*',
'描述',
'权限(多个用逗号分隔)',
'排序',
],
[
'测试角色',
'test_role',
'这是一个测试角色',
'查看用户,编辑用户',
'1',
],
];
Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename);
return $filename;
}
/**
* 导出角色数据
*/
public function exportRoles(array $roleIds = []): string
{
$filename = 'roles_export_' . date('YmdHis') . '.xlsx';
// 确保目录存在
if (!is_dir(storage_path('app/exports'))) {
mkdir(storage_path('app/exports'), 0755, true);
}
Excel::store(new RoleExport($roleIds), 'exports/' . $filename);
return $filename;
}
/**
* 导入角色数据
*/
public function importRoles(string $filePath, string $realPath): array
{
if (!file_exists($realPath)) {
throw ValidationException::withMessages([
'file' => ['文件不存在'],
]);
}
$import = new RoleImport();
Excel::import($import, $realPath);
// 删除临时文件
if (file_exists($realPath)) {
unlink($realPath);
}
return [
'success_count' => $import->getSuccessCount(),
'error_count' => $import->getErrorCount(),
'errors' => $import->getErrors(),
];
}
/**
* 下载权限导入模板
*/
public function downloadPermissionTemplate(): string
{
$filename = 'permission_import_template_' . date('YmdHis') . '.xlsx';
// 确保目录存在
if (!is_dir(storage_path('app/exports'))) {
mkdir(storage_path('app/exports'), 0755, true);
}
$templateData = [
[
'权限标题*',
'权限编码*',
'权限类型*',
'父级ID',
'路由路径',
'前端组件',
'元数据',
'排序',
],
[
'系统管理',
'system',
'菜单',
'0',
'/system',
null,
'icon:Setting',
'1',
],
];
Excel::store(new \App\Exports\GenericExport($templateData), 'exports/' . $filename);
return $filename;
}
/**
* 导出权限数据
*/
public function exportPermissions(array $permissionIds = []): string
{
$filename = 'permissions_export_' . date('YmdHis') . '.xlsx';
// 确保目录存在
if (!is_dir(storage_path('app/exports'))) {
mkdir(storage_path('app/exports'), 0755, true);
}
Excel::store(new PermissionExport($permissionIds), 'exports/' . $filename);
return $filename;
}
/**
* 导入权限数据
*/
public function importPermissions(string $filePath, string $realPath): array
{
if (!file_exists($realPath)) {
throw ValidationException::withMessages([
'file' => ['文件不存在'],
]);
}
$import = new PermissionImport();
Excel::import($import, $realPath);
// 删除临时文件
if (file_exists($realPath)) {
unlink($realPath);
}
return [
'success_count' => $import->getSuccessCount(),
'error_count' => $import->getErrorCount(),
'errors' => $import->getErrors(),
];
}
/**
* 删除导出文件
*/

230
app/Support/Regex.php Normal file
View File

@@ -0,0 +1,230 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace App\Support;
class Regex {
/**
* 验证用户名
*
* @param string $value 验证的值
* @param int $minLen 最小长度
* @param int $maxLen 最大长度
* @param string $type 验证类型默认ALL,EN.验证英文,CN.验证中文ALL.验证中文和英文
* @return bool
*/
public static function isUsername($value, $minLen = 2, $maxLen = 48, $type = 'ALL')
{
if (empty ($value)) {
return false;
}
switch ($type) {
case 'EN' :
$match = '/^[_\w\d]{' . $minLen . ',' . $maxLen . '}$/iu';
break;
case 'CN' :
$match = '/^[_\x{4e00}-\x{9fa5}\d]{' . $minLen . ',' . $maxLen . '}$/iu';
break;
default :
$match = '/^[_\w\d\x{4e00}-\x{9fa5}]{' . $minLen . ',' . $maxLen . '}$/iu';
}
return preg_match($match, $value) !== 0;
}
/**
* 验证密码
*
* @param string $value 验证的值
* @param int $minLen 最小长度
* @param int $maxLen 最大长度
* @return bool
*/
public static function isPassword($value, $minLen = 6, $maxLen = 16)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/^[\\~!@#$%^&*()-_=+|{}\[\],.?\/:;\'\"\d\w]{' . $minLen . ',' . $maxLen . '}$/';
return preg_match($match, $value) !== 0;
}
/**
* 验证eamil
*
* @param string $value 验证的值
* @return bool
*/
public static function isEmail($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/^[\w\d]+[\w\d-.]*@[\w\d-.]+\.[\w\d]{2,10}$/i';
return preg_match($match, $value) !== 0;
}
/**
* 验证电话号码
*
* @param string $value 验证的值
* @return bool
*/
public static function isTelephone($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/^0[0-9]{2,3}[-]?\d{7,8}$/';
return preg_match($match, $value) !== 0;
}
/**
* 验证手机
*
* @param string $value 验证的值
* @return bool
*/
public static function isMobile($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/^[(86)|0]?(1\d{10})$/';
return preg_match($match, $value) !== 0;
}
/**
* 验证邮政编码
*
* @param string $value 验证的值
* @return bool
*/
public static function isPostCode($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/\d{6}/';
return preg_match($match, $value) !== 0;
}
/**
* 验证IP
*
* @param string $value 验证的值
* @return boolean
*/
public static function isIp($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
$match = '/^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])' .
'\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)' .
'\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)' .
'\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$/';
return preg_match($match, $value) !== 0;
}
/**
* 验证身份证号码
*
* @param string $value 验证的值
* @return boolean
*/
public static function isIDCard($value)
{
$value = trim($value);
if (empty ($value)) {
return false;
}
if (strlen($value) > 18) {
return false;
}
$match = '/^\d{6}((1[89])|(2\d))\d{2}((0\d)|(1[0-2]))((3[01])|([0-2]\d))\d{3}(\d|X)$/i';
return preg_match($match, $value) !== 0;
}
/**
* 验证URL
*
* @param string $value 验证的值
* @return boolean
*/
public static function isUrl($value)
{
$value = strtolower(trim($value));
if (empty ($value)) {
return false;
}
$match = '/^(http:\/\/)?(https:\/\/)?([\w\d-]+\.)+[\w-]+(\/[\d\w-.\/?%&=]*)?$/';
return preg_match($match, $value) !== 0;
}
/**
* 是否有数字
* 说明:如果字符串中含有非法字符返回假,没有返回真
*
* @param string $value 验证的值
* @return int
*/
public static function hasNumber($value)
{
return preg_match("/[0-9]/", $value) != false;
}
/**
* 是否含有英文
* 说明:如果字符串中含有非法字符返回假,没有返回真
*
* @param string $value 验证的值
* @return bool
*/
public static function hasEnglish($value)
{
return preg_match("/[a-zA-Z]/", $value) != false;
}
/**
* 是否有中文
* 说明:如果字符串中含有非法字符返回假,没有返回真
*
* @param string $value 验证的值
* @return bool
*/
public static function hasChinese($value)
{
return preg_match("/[\x7f-\xff]/", $value) != false;
}
}

298
app/Support/Time.php Normal file
View File

@@ -0,0 +1,298 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace App\Support;
use Illuminate\Support\Carbon;
class Time {
/**
* 返回今日开始和结束的时间戳
*
* @return array
*/
public static function today($data = '') {
[$y, $m, $d] = explode('-', $data ? $data : date('Y-m-d'));
return [
mktime(0, 0, 0, $m, $d, $y),
mktime(23, 59, 59, $m, $d, $y),
];
}
/**
* 返回昨日开始和结束的时间戳
*
* @return array
*/
public static function yesterday() {
$yesterday = date('d') - 1;
return [
mktime(0, 0, 0, date('m'), $yesterday, date('Y')),
mktime(23, 59, 59, date('m'), $yesterday, date('Y')),
];
}
/**
* 返回本周开始和结束的时间戳
*
* @return array
*/
public static function week() {
[$y, $m, $d, $w] = explode('-', date('Y-m-d-w'));
if ($w == 0) $w = 7; //修正周日的问题
return [
mktime(0, 0, 0, $m, $d - $w + 1, $y), mktime(23, 59, 59, $m, $d - $w + 7, $y),
];
}
/**
* 返回上周开始和结束的时间戳
*
* @return array
*/
public static function lastWeek() {
$timestamp = time();
return [
strtotime(date('Y-m-d', strtotime("last week Monday", $timestamp))),
strtotime(date('Y-m-d', strtotime("last week Sunday", $timestamp))) + 24 * 3600 - 1,
];
}
/**
* 返回本月开始和结束的时间戳
*
* @return array
*/
public static function month($data = '') {
$res = [];
$data = $data ? $data : date('Y-m-d');
$nextMoth = date('Y-m-d', strtotime($data . ' +1 month'));
return [
mktime(0, 0, 0, date('m', strtotime($data)), 1, date('Y', strtotime($data))),
mktime(0, 0, 0, date('m', strtotime($nextMoth)), 1, date('Y', strtotime($nextMoth))),
];
}
/**
* 返回上个月开始和结束的时间戳
*
* @return array
*/
public static function lastMonth() {
$y = date('Y');
$m = date('m');
$begin = mktime(0, 0, 0, $m - 1, 1, $y);
$end = mktime(23, 59, 59, $m - 1, date('t', $begin), $y);
return [$begin, $end];
}
/**
* 返回今年开始和结束的时间戳
*
* @return array
*/
public static function year() {
$y = date('Y');
return [
mktime(0, 0, 0, 1, 1, $y),
mktime(23, 59, 59, 12, 31, $y),
];
}
/**
* 返回去年开始和结束的时间戳
*
* @return array
*/
public static function lastYear() {
$year = date('Y') - 1;
return [
mktime(0, 0, 0, 1, 1, $year),
mktime(23, 59, 59, 12, 31, $year),
];
}
/**
* 获取几天前零点到现在/昨日结束的时间戳
*
* @param int $day 天数
* @param bool $now 返回现在或者昨天结束时间戳
* @return array
*/
public static function dayToNow($day = 1, $now = true) {
$end = time();
if (!$now) {
[$foo, $end] = self::yesterday();
}
return [
mktime(0, 0, 0, date('m'), date('d') - $day, date('Y')),
$end,
];
}
/**
* 返回几天前的时间戳
*
* @param int $day
* @return int
*/
public static function daysAgo($day = 1) {
$nowTime = time();
return $nowTime - self::daysToSecond($day);
}
/**
* 返回几天后的时间戳
*
* @param int $day
* @return int
*/
public static function daysAfter($day = 1) {
$nowTime = time();
return $nowTime + self::daysToSecond($day);
}
/**
* 天数转换成秒数
*
* @param int $day
* @return int
*/
public static function daysToSecond($day = 1) {
return $day * 86400;
}
/**
* 周数转换成秒数
*
* @param int $week
* @return int
*/
public static function weekToSecond($week = 1) {
return self::daysToSecond() * 7 * $week;
}
/**
* 获取毫秒级别的时间戳
*/
public static function getMillisecond() {
$time = explode(" ", microtime());
$time = $time[1] . ($time[0] * 1000);
$time2 = explode(".", $time);
$time = $time2[0];
return $time;
}
/**
* 获取相对时间
*
* @param int $timeStamp
* @return string
*/
public static function formatRelative($timeStamp) {
$currentTime = time();
// 判断传入时间戳是否早于当前时间戳
$isEarly = $timeStamp <= $currentTime;
// 获取两个时间戳差值
$diff = abs($currentTime - $timeStamp);
$dirStr = $isEarly ? '前' : '后';
if ($diff < 60) { // 一分钟之内
$resStr = $diff . '秒' . $dirStr;
} elseif ($diff >= 60 && $diff < 3600) { // 多于59秒少于等于59分钟59秒
$resStr = floor($diff / 60) . '分钟' . $dirStr;
} elseif ($diff >= 3600 && $diff < 86400) { // 多于59分钟59秒少于等于23小时59分钟59秒
$resStr = floor($diff / 3600) . '小时' . $dirStr;
} elseif ($diff >= 86400 && $diff < 2623860) { // 多于23小时59分钟59秒少于等于29天59分钟59秒
$resStr = floor($diff / 86400) . '天' . $dirStr;
} elseif ($diff >= 2623860 && $diff <= 31567860 && $isEarly) { // 多于29天59分钟59秒少于364天23小时59分钟59秒且传入的时间戳早于当前
$resStr = date('m-d H:i', $timeStamp);
} else {
$resStr = date('Y-m-d', $timeStamp);
}
return $resStr;
}
/**
* 范围日期转换时间戳
*
* @param string $rangeDatetime
* @param int $maxRange 最大时间间隔
* @param string $delimiter
* @return array
*/
public static function parseRange($rangeDatetime, $maxRange = 0, $delimiter = ' - ') {
$rangeDatetime = explode($delimiter, $rangeDatetime, 2);
$rangeDatetime[0] = strtotime($rangeDatetime[0]);
$rangeDatetime[1] = isset($rangeDatetime[1]) ? strtotime($rangeDatetime[1]) : time();
$rangeDatetime = [
min($rangeDatetime[0], $rangeDatetime[1]),
max($rangeDatetime[0], $rangeDatetime[1]),
];
// 如果结束时间小于或等于开始时间 直接返回null
// if ($rangeDatetime[1] < $rangeDatetime[0]) {
// return null;
// }
// 如果大于最大时间间隔 则用结束时间减去最大时间间隔获得开始时间
if ($maxRange > 0 && $rangeDatetime[1] - $rangeDatetime[0] > $maxRange) {
$rangeDatetime[0] = $rangeDatetime[1] - $maxRange;
}
return $rangeDatetime;
}
/**
* 获取指定时间范围内的日期数组
* @param int $startTime
* @param int $endTime
* @return \Carbon\CarbonPeriod
*/
public static function daysUntilOfTimestamp($startTime, $endTime) {
$startTime = Carbon::createFromTimestamp($startTime);
$endTime = Carbon::createFromTimestamp($endTime);
return $startTime->daysUntil($endTime);
}
/**
* 时间排序
*
* @param array $times
* @return array
*/
public static function sort($times) {
usort($times, function ($com1, $com2) {
$com1 = strtotime($com1);
$com2 = strtotime($com2);
return $com1 < $com2 ? -1 : 1;
});
return $times;
}
}

43
app/Traits/ModelTrait.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2024 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace App\Traits;
use Illuminate\Support\Facades\Schema;
trait ModelTrait {
protected function casts(): array {
return [
'created_at' => 'datetime:Y-m-d H:i:s',
'updated_at' => 'datetime:Y-m-d H:i:s',
'deleted_at' => 'datetime:Y-m-d H:i:s',
];
}
protected function serializeDate($data){
return $data->timezone('Asia/Shanghai')->format('Y-m-d H:i:s');
}
/**
* 过滤移除非当前表的字段参数
*
* @param array $params
*
* @return array
*/
public function setFilterFields(array $params) : array {
$fields = Schema::getColumnListing($this->getTable());
foreach ($params as $key => $param) {
if ( !in_array($key, $fields) ) unset($params[$key]);
}
// 同时过滤时间戳字段【时间戳只允许自动更改,不允许手动设置】
if ( $this->timestamps === true && isset($params[self::CREATED_AT]) ) unset($params[self::CREATED_AT]);
if ( $this->timestamps === true && isset($params[self::UPDATED_AT]) ) unset($params[self::UPDATED_AT]);
return $params;
}
}

View File

@@ -101,7 +101,7 @@ return [
|
*/
'ttl' => env('JWT_TTL', 60 * 60 * 24 * 7),
'ttl' => (int) env('JWT_TTL', 60 * 60 * 24 * 7),
/*
|--------------------------------------------------------------------------

View File

@@ -178,6 +178,23 @@ export default {
return await request.post('roles/batch-copy', params)
},
},
export: {
post: async function (params) {
return await request.post('roles/export', params, { responseType: 'blob' })
},
},
import: {
post: async function (formData) {
return await request.post('roles/import', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
},
},
downloadTemplate: {
get: async function () {
return await request.get('roles/download-template', { responseType: 'blob' })
},
},
},
// 权限管理
@@ -227,6 +244,23 @@ export default {
return await request.post('permissions/batch-status', params)
},
},
export: {
post: async function (params) {
return await request.post('permissions/export', params, { responseType: 'blob' })
},
},
import: {
post: async function (formData) {
return await request.post('permissions/import', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
},
},
downloadTemplate: {
get: async function () {
return await request.get('permissions/download-template', { responseType: 'blob' })
},
},
},
// 部门管理

View File

@@ -104,34 +104,22 @@
<!-- 新增/编辑/查看部门弹窗 -->
<save-dialog v-if="dialog.save" ref="saveDialogRef" @success="handleSaveSuccess" @closed="dialog.save = false" />
<!-- 导入弹窗 -->
<a-modal v-model:open="dialog.import" title="导入部门" :width="500" :footer="null">
<a-space direction="vertical" style="width: 100%">
<a-alert message="导入说明" description="请先下载模板,按照模板格式填写数据后上传。如果部门名称已存在则跳过。" type="info" show-icon />
<a-button @click="handleDownloadTemplate" style="width: 100%">
<template #icon><download-outlined /></template>
下载导入模板
</a-button>
<a-upload
:file-list="importFileList"
:before-upload="handleImportUpload"
@remove="() => importFileList = []"
accept=".xlsx,.xls"
:max-count="1"
>
<a-button>
<template #icon><upload-outlined /></template>
选择文件
</a-button>
</a-upload>
</a-space>
</a-modal>
<!-- 导入部门弹窗 -->
<sc-import v-model:open="dialog.import" title="导入部门" :api="authApi.departments.import.post"
:template-api="authApi.departments.downloadTemplate.get" filename="部门" @success="handleImportSuccess" />
<!-- 导出部门弹窗 -->
<sc-export v-model:open="dialog.export" title="导出部门" :api="handleExportApi"
:default-filename="`部门列表_${Date.now()}`" :show-options="false" tip="导出当前选中或所有部门数据"
@success="handleExportSuccess" />
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue'
import scTable from '@/components/scTable/index.vue'
import scImport from '@/components/scImport/index.vue'
import scExport from '@/components/scExport/index.vue'
import saveDialog from './components/SaveDialog.vue'
import authApi from '@/api/auth'
import { useTable } from '@/hooks/useTable'
@@ -168,15 +156,13 @@ const {
// 对话框状态
const dialog = reactive({
save: false,
import: false
import: false,
export: false
})
// 弹窗引用
const saveDialogRef = ref(null)
// 导入文件列表
const importFileList = ref([])
// 行key
const rowKey = 'id'
@@ -304,82 +290,47 @@ const handleBatchStatus = (status) => {
}
// 导出部门
const handleExport = async () => {
try {
let ids = []
if (selectedRows.value.length > 0) {
ids = selectedRows.value.map(item => item.id)
}
const res = await authApi.departments.export.post({ ids }, { responseType: 'blob' })
const handleExport = () => {
dialog.export = true
}
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([res]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `部门列表_${new Date().getTime()}.xlsx`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 导出API封装
const handleExportApi = async () => {
const ids = selectedRows.value.map(item => item.id)
return await authApi.departments.export.post({ ids: ids.length > 0 ? ids : undefined })
}
message.success('导出成功')
selectedRows.value = []
} catch (error) {
console.error('导出失败:', error)
message.error('导出失败')
}
// 导出成功回调
const handleExportSuccess = () => {
selectedRows.value = []
}
// 导入部门
const handleImport = () => {
importFileList.value = []
dialog.import = true
}
// 上传导入文件
const handleImportUpload = async (file) => {
const formData = new FormData()
formData.append('file', file)
try {
importFileList.value = [{ uid: file.uid, name: file.name, status: 'uploading' }]
const res = await authApi.departments.import.post(formData)
importFileList.value = []
dialog.import = false
if (res.code === 200) {
message.success(res.message || '导入成功')
refreshTable()
} else {
message.error(res.message || '导入失败')
}
} catch (error) {
console.error('导入失败:', error)
importFileList.value = []
message.error(error.response?.data?.message || '导入失败')
}
return false // 阻止自动上传
// 导入成功回调
const handleImportSuccess = () => {
refreshTable()
}
// 下载导入模板
// 下载模板
const handleDownloadTemplate = async () => {
try {
const res = await authApi.departments.downloadTemplate.get({ responseType: 'blob' })
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([res]))
const blob = await authApi.departments.downloadTemplate.get()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', '部门导入模板.xlsx')
link.download = '部门导入模板.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
message.success('下载成功')
} catch (error) {
console.error('下载模板失败:', error)
message.error('下载模板失败')
message.error('下载失败')
}
}

View File

@@ -12,14 +12,12 @@
</div>
<div class="actions">
<a-space size="small">
<a-tooltip title="展开全部">
<a-button type="text" size="small" @click="handleExpandAll">
<template #icon><UnorderedListOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="折叠全部">
<a-button type="text" size="small" @click="handleCollapseAll">
<template #icon><OrderedListOutlined /></template>
<a-tooltip :title="isAllExpanded ? '折叠全部' : '展开全部'">
<a-button type="text" size="small" @click="handleToggleExpand">
<template #icon>
<UnorderedListOutlined v-if="!isAllExpanded" />
<OrderedListOutlined v-else />
</template>
</a-button>
</a-tooltip>
<a-tooltip title="添加根权限">
@@ -43,7 +41,6 @@
checkable
:check-strictly="false"
:expand-on-click-node="false"
block-node
@select="onMenuSelect"
@check="onMenuCheck">
<template #icon="{ dataRef }">
@@ -81,6 +78,25 @@
<template #icon><DeleteOutlined /></template>
批量删除 ({{ checkedMenuKeys.length }})
</a-button>
<a-dropdown>
<a-button type="link" size="small">
<template #icon><MoreOutlined /></template>
更多
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="handleImport">
<ImportOutlined />导入权限
</a-menu-item>
<a-menu-item @click="handleExport">
<ExportOutlined />导出权限
</a-menu-item>
<a-menu-item @click="handleDownloadTemplate">
<DownloadOutlined />下载模板
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button type="link" size="small" @click="handleRefresh">
<template #icon><ReloadOutlined /></template>
刷新
@@ -96,6 +112,15 @@
</div>
</div>
</div>
<!-- 导入权限弹窗 -->
<sc-import v-model:open="dialog.import" title="导入权限" :api="authApi.permissions.import.post"
:template-api="authApi.permissions.downloadTemplate.get" filename="权限" @success="handleImportSuccess" />
<!-- 导出权限弹窗 -->
<sc-export v-model:open="dialog.export" title="导出权限" :api="handleExportApi"
:default-filename="`权限列表_${Date.now()}`" :show-options="false" tip="导出当前选中或所有权限数据"
@success="handleExportSuccess" />
</template>
<script setup>
@@ -112,9 +137,16 @@ import {
UnorderedListOutlined,
OrderedListOutlined,
DeleteOutlined,
StopOutlined
StopOutlined,
ImportOutlined,
ExportOutlined,
DownloadOutlined,
MoreOutlined
} from '@ant-design/icons-vue'
import { computed } from 'vue'
import saveForm from './components/SaveForm.vue'
import scImport from '@/components/scImport/index.vue'
import scExport from '@/components/scExport/index.vue'
import authApi from '@/api/auth'
defineOptions({
@@ -137,9 +169,30 @@ const parentId = ref(null)
const loading = ref(false)
const detailLoading = ref(false)
// 对话框状态
const dialog = ref({
import: false,
export: false
})
// 树引用
const treeRef = ref()
// 是否全部展开
const isAllExpanded = computed(() => {
const allKeys = getAllKeys(filteredMenuTree.value)
return allKeys.length > 0 && expandedKeys.value.length === allKeys.length
})
// 切换展开/折叠
const handleToggleExpand = () => {
if (isAllExpanded.value) {
handleCollapseAll()
} else {
handleExpandAll()
}
}
// 加载权限树
const loadMenuTree = async () => {
try {
@@ -363,6 +416,52 @@ const handleSaveSuccess = async () => {
message.success('保存成功')
}
// 导出权限
const handleExport = () => {
dialog.value.export = true
}
// 导出API封装
const handleExportApi = async () => {
return await authApi.permissions.export.post({
ids: checkedMenuKeys.value.length > 0 ? checkedMenuKeys.value : undefined
})
}
// 导出成功回调
const handleExportSuccess = () => {
checkedMenuKeys.value = []
}
// 导入权限
const handleImport = () => {
dialog.value.import = true
}
// 导入成功回调
const handleImportSuccess = () => {
loadMenuTree()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const blob = await authApi.permissions.downloadTemplate.get()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '权限导入模板.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
message.success('下载成功')
} catch (error) {
console.error('下载模板失败:', error)
message.error('下载失败')
}
}
// 初始化
onMounted(() => {
loadMenuTree()
@@ -401,8 +500,11 @@ defineExpose({
background: transparent;
.ant-tree-node-content-wrapper {
padding: 4px 0;
padding: 2px 4px;
transition: background-color 0.2s;
display: flex;
align-items: center;
min-height: 28px;
&:hover {
background-color: #f5f5f5;
@@ -411,22 +513,37 @@ defineExpose({
.ant-tree-switcher {
color: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
min-width: 20px;
}
.ant-tree-iconEle {
margin-right: 6px;
margin-right: 4px;
flex-shrink: 0;
display: flex;
align-items: center;
}
.ant-tree-title {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
}
.tree-node-content {
display: inline-flex;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
gap: 4px;
max-width: 100%;
.tree-node-title {
font-size: 14px;
font-size: 13px;
color: #262626;
flex-shrink: 0;
line-height: 1.4;
}
.tree-node-code {
@@ -434,14 +551,17 @@ defineExpose({
font-size: 11px;
background: #f0f0f0;
border: none;
padding: 1px 6px;
padding: 0 4px;
border-radius: 2px;
color: #595959;
flex-shrink: 0;
margin-right: 2px;
}
.tree-node-disabled {
color: #ff4d4f;
margin-left: 4px;
font-size: 12px;
flex-shrink: 0;
}
}

View File

@@ -36,6 +36,25 @@
</a-menu>
</template>
</a-dropdown>
<a-dropdown>
<a-button>
更多
<DownOutlined />
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="handleImport">
<ImportOutlined />导入角色
</a-menu-item>
<a-menu-item @click="handleExport">
<ExportOutlined />导出角色
</a-menu-item>
<a-menu-item @click="handleDownloadTemplate">
<DownloadOutlined />下载模板
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button type="primary" @click="handleAdd">
<template #icon><PlusOutlined /></template>
新增
@@ -72,6 +91,15 @@
<!-- 权限设置弹窗 -->
<permission-dialog v-if="dialog.permission" ref="permissionDialogRef" @success="permissionSuccess"
@closed="dialog.permission = false" />
<!-- 导入角色弹窗 -->
<sc-import v-model:open="dialog.import" title="导入角色" :api="authApi.roles.import.post"
:template-api="authApi.roles.downloadTemplate.get" filename="角色" @success="handleImportSuccess" />
<!-- 导出角色弹窗 -->
<sc-export v-model:open="dialog.export" title="导出角色" :api="handleExportApi"
:default-filename="`角色列表_${Date.now()}`" :show-options="false" tip="导出当前选中或所有角色数据"
@success="handleExportSuccess" />
</template>
<script setup>
@@ -84,9 +112,14 @@ import {
DownOutlined,
CheckCircleOutlined,
CopyOutlined,
DeleteOutlined
DeleteOutlined,
ImportOutlined,
ExportOutlined,
DownloadOutlined
} from '@ant-design/icons-vue'
import scTable from '@/components/scTable/index.vue'
import scImport from '@/components/scImport/index.vue'
import scExport from '@/components/scExport/index.vue'
import saveDialog from './components/SaveDialog.vue'
import permissionDialog from './components/PermissionDialog.vue'
import authApi from '@/api/auth'
@@ -125,7 +158,9 @@ const {
// 对话框状态
const dialog = reactive({
save: false,
permission: false
permission: false,
import: false,
export: false
})
// 弹窗引用
@@ -296,4 +331,49 @@ const handleSaveSuccess = () => {
const permissionSuccess = () => {
refreshTable()
}
// 导出角色
const handleExport = async () => {
dialog.export = true
}
// 导出API封装
const handleExportApi = async () => {
const ids = selectedRows.value.map(item => item.id)
return await authApi.roles.export.post({ ids: ids.length > 0 ? ids : undefined })
}
// 导出成功回调
const handleExportSuccess = () => {
selectedRows.value = []
}
// 导入角色
const handleImport = () => {
dialog.import = true
}
// 导入成功回调
const handleImportSuccess = () => {
refreshTable()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const blob = await authApi.roles.downloadTemplate.get()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '角色导入模板.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
message.success('下载成功')
} catch (error) {
console.error('下载模板失败:', error)
message.error('下载失败')
}
}
</script>

View File

@@ -126,6 +126,15 @@
<!-- 角色设置弹窗 -->
<role-dialog v-if="dialog.role" ref="roleDialogRef" @success="handleRoleSuccess" @closed="dialog.role = false" />
<!-- 导入用户弹窗 -->
<sc-import v-model:open="dialog.import" title="导入用户" :api="authApi.users.import.post"
:template-api="authApi.users.downloadTemplate.get" filename="用户" @success="handleImportSuccess" />
<!-- 导出用户弹窗 -->
<sc-export v-model:open="dialog.export" title="导出用户" :api="handleExportApi"
:default-filename="`用户列表_${Date.now()}`" :show-options="false" tip="导出当前选中或所有用户数据"
@success="handleExportSuccess" />
</template>
<script setup>
@@ -146,6 +155,8 @@ import {
UserOutlined
} from '@ant-design/icons-vue'
import scTable from '@/components/scTable/index.vue'
import scImport from '@/components/scImport/index.vue'
import scExport from '@/components/scExport/index.vue'
import saveDialog from './components/SaveDialog.vue'
import roleDialog from './components/RoleDialog.vue'
import authApi from '@/api/auth'
@@ -188,7 +199,9 @@ const {
// 对话框状态
const dialog = reactive({
save: false,
role: false
role: false,
import: false,
export: false
})
// 弹窗引用
@@ -368,53 +381,50 @@ const handleBatchRoles = () => {
}
// 导出数据
const handleExport = async () => {
try {
const ids = selectedRows.value.map(item => item.id)
const res = await authApi.users.export.post({ ids: ids.length > 0 ? ids : undefined })
if (res) {
// 创建下载链接
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `users_${Date.now()}.xlsx`
link.click()
window.URL.revokeObjectURL(url)
message.success('导出成功')
}
} catch (error) {
console.error('导出失败:', error)
message.error('导出失败')
}
const handleExport = () => {
dialog.export = true
}
// 导出API封装
const handleExportApi = async () => {
const ids = selectedRows.value.map(item => item.id)
return await authApi.users.export.post({ ids: ids.length > 0 ? ids : undefined })
}
// 导出成功回调
const handleExportSuccess = () => {
selectedRows.value = []
}
// 导入用户
const handleImport = () => {
dialog.import = true
}
// 导入成功回调
const handleImportSuccess = () => {
refreshTable()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const res = await authApi.users.downloadTemplate.get()
if (res) {
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'user_template.xlsx'
link.click()
window.URL.revokeObjectURL(url)
message.success('下载成功')
}
const blob = await authApi.users.downloadTemplate.get()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '用户导入模板.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
message.success('下载成功')
} catch (error) {
console.error('下载模板失败:', error)
message.error('下载失败')
}
}
// 导入用户
const handleImport = () => {
// TODO: 实现导入弹窗
message.info('导入功能开发中...')
}
// 重置密码
const handleResetPassword = (record) => {
Modal.confirm({