24 changed files with 5069 additions and 323 deletions
@ -0,0 +1,98 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\api\controller\apiController; |
|||
|
|||
use app\Request; |
|||
use app\service\api\apiService\StudentService; |
|||
use core\base\BaseApiService; |
|||
|
|||
/** |
|||
* 学员管理相关接口 |
|||
* Class StudentManager |
|||
* @package app\api\controller\apiController |
|||
*/ |
|||
class StudentManager extends BaseApiService |
|||
{ |
|||
/** |
|||
* 添加学员 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function add(Request $request) |
|||
{ |
|||
try { |
|||
$data = $this->request->params([ |
|||
["name", ""], |
|||
["gender", 0], |
|||
["age", 0.00], |
|||
["birthday", ""], |
|||
["user_id", 0], |
|||
["campus_id", 0], |
|||
["class_id", 0], |
|||
["note", ""], |
|||
["status", 1], |
|||
["emergency_contact", ""], |
|||
["contact_phone", ""], |
|||
["member_label", ""], |
|||
["consultant_id", ""], |
|||
["coach_id", ""], |
|||
["trial_class_count", 2] |
|||
]); |
|||
|
|||
// 表单验证 |
|||
if (empty($data['name'])) { |
|||
return fail('学员姓名不能为空'); |
|||
} |
|||
if ($data['gender'] == 0) { |
|||
return fail('请选择学员性别'); |
|||
} |
|||
if (empty($data['birthday'])) { |
|||
return fail('请选择学员出生日期'); |
|||
} |
|||
|
|||
$result = (new StudentService())->add($data); |
|||
if ($result['code'] === 1) { |
|||
return success('添加成功', $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} catch (\Exception $e) { |
|||
return fail('添加学员失败:' . $e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取学员列表 |
|||
* @param Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function list(Request $request) |
|||
{ |
|||
try { |
|||
$data = $this->request->params([ |
|||
["parent_resource_id", 0], |
|||
["user_id", 0], |
|||
["campus_id", 0], |
|||
["status", 0] |
|||
]); |
|||
|
|||
$result = (new StudentService())->getList($data); |
|||
if ($result['code'] === 1) { |
|||
return success('获取成功', $result['data']); |
|||
} else { |
|||
return fail($result['msg']); |
|||
} |
|||
} catch (\Exception $e) { |
|||
return fail('获取学员列表失败:' . $e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\api\controller\login; |
|||
|
|||
use app\service\api\login\LoginService; |
|||
use app\service\api\login\UnifiedLoginService; |
|||
use core\base\BaseController; |
|||
use think\Response; |
|||
|
|||
/** |
|||
* 统一登录控制器 |
|||
* 支持员工端和会员端两种登录方式 |
|||
*/ |
|||
class UnifiedLogin extends BaseController |
|||
{ |
|||
/** |
|||
* 统一登录接口 |
|||
* @return Response |
|||
*/ |
|||
public function login() |
|||
{ |
|||
$data = $this->request->params([ |
|||
['username', ''], // 用户名/手机号 |
|||
['password', ''], // 密码 |
|||
['login_type', ''], // 登录类型: staff=员工端, member=会员端 |
|||
]); |
|||
|
|||
// 参数验证 |
|||
$this->validate($data, [ |
|||
'username' => 'require', |
|||
'password' => 'require', |
|||
'login_type' => 'require|in:staff,member' |
|||
]); |
|||
|
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$result = $service->unifiedLogin($data); |
|||
|
|||
if (!$result) { |
|||
return fail('登录失败,请检查用户名和密码'); |
|||
} |
|||
|
|||
return success($result, '登录成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 员工端登录(兼容旧接口) |
|||
* @return Response |
|||
*/ |
|||
public function staffLogin() |
|||
{ |
|||
$data = $this->request->params([ |
|||
['phone', ''], |
|||
['password', ''], |
|||
['user_type', ''], // 1=市场, 2=教练, 3=销售 |
|||
]); |
|||
|
|||
// 参数验证 |
|||
$this->validate($data, [ |
|||
'phone' => 'require|mobile', |
|||
'password' => 'require', |
|||
'user_type' => 'require|in:1,2,3' |
|||
]); |
|||
|
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$result = $service->staffLogin($data); |
|||
|
|||
return success($result, '员工登录成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 会员端登录(兼容旧接口) |
|||
* @return Response |
|||
*/ |
|||
public function memberLogin() |
|||
{ |
|||
$data = $this->request->params([ |
|||
['username', ''], |
|||
['password', ''], |
|||
['mobile', ''], |
|||
]); |
|||
|
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$result = $service->memberLogin($data); |
|||
|
|||
return success($result, '会员登录成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 登出 |
|||
* @return Response |
|||
*/ |
|||
public function logout() |
|||
{ |
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$service->logout(); |
|||
|
|||
return success([], '登出成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取用户信息 |
|||
* @return Response |
|||
*/ |
|||
public function getUserInfo() |
|||
{ |
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$result = $service->getUserInfo(); |
|||
|
|||
return success($result, '获取用户信息成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 刷新Token |
|||
* @return Response |
|||
*/ |
|||
public function refreshToken() |
|||
{ |
|||
try { |
|||
$service = new UnifiedLoginService(); |
|||
$result = $service->refreshToken(); |
|||
|
|||
return success($result, 'Token刷新成功'); |
|||
|
|||
} catch (\Exception $e) { |
|||
return fail($e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,163 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\service\api\apiService; |
|||
|
|||
use core\base\BaseApiService; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* 学员服务层 |
|||
* Class StudentService |
|||
* @package app\service\api\apiService |
|||
*/ |
|||
class StudentService extends BaseApiService |
|||
{ |
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
} |
|||
|
|||
/** |
|||
* 添加学员 |
|||
* @param array $data |
|||
* @return array |
|||
*/ |
|||
public function add(array $data) |
|||
{ |
|||
$res = [ |
|||
'code' => 0, |
|||
'msg' => '操作失败', |
|||
'data' => [] |
|||
]; |
|||
|
|||
try { |
|||
// 准备插入数据 |
|||
$insertData = [ |
|||
'name' => $data['name'], |
|||
'gender' => $data['gender'], |
|||
'age' => $data['age'], |
|||
'birthday' => $data['birthday'], |
|||
'user_id' => $data['user_id'], |
|||
'campus_id' => $data['campus_id'], |
|||
'class_id' => $data['class_id'], |
|||
'note' => $data['note'], |
|||
'status' => $data['status'], |
|||
'emergency_contact' => $data['emergency_contact'], |
|||
'contact_phone' => $data['contact_phone'], |
|||
'member_label' => $data['member_label'], |
|||
'consultant_id' => $data['consultant_id'], |
|||
'coach_id' => $data['coach_id'], |
|||
'trial_class_count' => $data['trial_class_count'], |
|||
'created_at' => date('Y-m-d H:i:s'), |
|||
'updated_at' => date('Y-m-d H:i:s'), |
|||
'deleted_at' => 0 |
|||
]; |
|||
|
|||
// 插入数据库 |
|||
$id = Db::table('school_student')->insertGetId($insertData); |
|||
|
|||
if ($id) { |
|||
$res['code'] = 1; |
|||
$res['msg'] = '添加成功'; |
|||
$res['data'] = ['id' => $id]; |
|||
} else { |
|||
$res['msg'] = '添加失败'; |
|||
} |
|||
} catch (\Exception $e) { |
|||
$res['msg'] = '添加失败:' . $e->getMessage(); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
|
|||
/** |
|||
* 获取学员列表 |
|||
* @param array $data |
|||
* @return array |
|||
*/ |
|||
public function getList(array $data) |
|||
{ |
|||
$res = [ |
|||
'code' => 0, |
|||
'msg' => '操作失败', |
|||
'data' => [] |
|||
]; |
|||
|
|||
try { |
|||
$where = []; |
|||
$where[] = ['deleted_at', '=', 0]; |
|||
|
|||
if (!empty($data['user_id'])) { |
|||
$where[] = ['user_id', '=', $data['user_id']]; |
|||
} |
|||
|
|||
if (!empty($data['parent_resource_id'])) { |
|||
$where[] = ['user_id', '=', $data['parent_resource_id']]; |
|||
} |
|||
|
|||
if (!empty($data['campus_id'])) { |
|||
$where[] = ['campus_id', '=', $data['campus_id']]; |
|||
} |
|||
|
|||
if (!empty($data['status'])) { |
|||
$where[] = ['status', '=', $data['status']]; |
|||
} |
|||
|
|||
$list = Db::table('school_student') |
|||
->where($where) |
|||
->order('created_at', 'desc') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 为每个学员添加到访情况信息 |
|||
foreach ($list as &$student) { |
|||
// 查询该学员的课程安排记录,按日期排序获取一访和二访信息 |
|||
$visitRecords = Db::table('school_person_course_schedule') |
|||
->where([ |
|||
['person_id', '=', $student['id']], |
|||
['person_type', '=', 'student'] |
|||
]) |
|||
->order('course_date', 'asc') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 初始化到访信息 |
|||
$student['first_visit_time'] = ''; |
|||
$student['second_visit_time'] = ''; |
|||
$student['first_visit_status'] = '未到访'; |
|||
$student['second_visit_status'] = '未到访'; |
|||
|
|||
// 设置一访和二访信息 |
|||
if (!empty($visitRecords)) { |
|||
if (isset($visitRecords[0])) { |
|||
$student['first_visit_time'] = $visitRecords[0]['course_date']; |
|||
$student['first_visit_status'] = '已到访'; |
|||
} |
|||
if (isset($visitRecords[1])) { |
|||
$student['second_visit_time'] = $visitRecords[1]['course_date']; |
|||
$student['second_visit_status'] = '已到访'; |
|||
} |
|||
} |
|||
|
|||
// 保留原始访问记录数据供前端使用 |
|||
$student['visit_records'] = $visitRecords; |
|||
} |
|||
|
|||
$res['code'] = 1; |
|||
$res['data'] = $list; |
|||
} catch (\Exception $e) { |
|||
$res['msg'] = '获取失败:' . $e->getMessage(); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
} |
|||
@ -0,0 +1,420 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\service\api\login; |
|||
|
|||
use app\dict\sys\AppTypeDict; |
|||
use app\model\member\Member; |
|||
use app\model\personnel\Personnel; |
|||
use app\model\site\Site; |
|||
use app\service\core\menu\CoreMenuService; |
|||
use core\util\TokenAuth; |
|||
use core\base\BaseService; |
|||
use core\exception\CommonException; |
|||
use think\facade\Cache; |
|||
|
|||
/** |
|||
* 统一登录服务类 |
|||
*/ |
|||
class UnifiedLoginService extends BaseService |
|||
{ |
|||
/** |
|||
* 用户类型常量 |
|||
*/ |
|||
const USER_TYPE_STAFF = 'staff'; // 员工端 |
|||
const USER_TYPE_MEMBER = 'member'; // 会员端 |
|||
|
|||
/** |
|||
* 员工角色类型 |
|||
*/ |
|||
const STAFF_ROLE_MARKET = 1; // 市场人员 |
|||
const STAFF_ROLE_COACH = 2; // 教练 |
|||
const STAFF_ROLE_SALES = 3; // 销售 |
|||
const STAFF_ROLE_TEACHER = 4; // 教师 |
|||
|
|||
/** |
|||
* 统一登录方法 |
|||
* @param array $data |
|||
* @return array |
|||
* @throws \Exception |
|||
*/ |
|||
public function unifiedLogin(array $data) |
|||
{ |
|||
$username = trim($data['username']); |
|||
$password = trim($data['password']); |
|||
$loginType = trim($data['login_type']); |
|||
|
|||
if (empty($username) || empty($password)) { |
|||
throw new CommonException('用户名和密码不能为空'); |
|||
} |
|||
|
|||
switch ($loginType) { |
|||
case self::USER_TYPE_STAFF: |
|||
return $this->handleStaffLogin($username, $password); |
|||
case self::USER_TYPE_MEMBER: |
|||
return $this->handleMemberLogin($username, $password); |
|||
default: |
|||
throw new CommonException('不支持的登录类型'); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 员工端登录处理 |
|||
* @param string $username |
|||
* @param string $password |
|||
* @return array |
|||
* @throws \Exception |
|||
*/ |
|||
private function handleStaffLogin(string $username, string $password) |
|||
{ |
|||
// 查找员工信息及关联的系统用户信息 |
|||
$personnel = new Personnel(); |
|||
$staffInfo = $personnel->alias('p') |
|||
->leftJoin('school_sys_user u', 'p.sys_user_id = u.uid') |
|||
->where('p.phone', $username) |
|||
->where('p.status', 2) // 2=已审核(正常状态) |
|||
->where('u.status', 1) |
|||
->field('p.*, u.username, u.password, u.real_name') |
|||
->find(); |
|||
|
|||
if (!$staffInfo) { |
|||
throw new CommonException('员工账号不存在或已禁用'); |
|||
} |
|||
|
|||
// 验证密码 |
|||
if (!password_verify($password, $staffInfo['password'])) { |
|||
throw new CommonException('密码错误'); |
|||
} |
|||
|
|||
// 根据account_type确定角色类型 |
|||
$roleType = $this->getAccountTypeCode($staffInfo['account_type']); |
|||
|
|||
// 生成Token |
|||
$tokenData = [ |
|||
'user_id' => $staffInfo['id'], |
|||
'user_type' => self::USER_TYPE_STAFF, |
|||
'role_type' => $roleType, |
|||
'site_id' => 0, // 默认站点ID |
|||
]; |
|||
|
|||
$tokenResult = TokenAuth::createToken($staffInfo['id'], AppTypeDict::PERSONNEL, $tokenData, 86400); |
|||
$token = $tokenResult['token']; |
|||
|
|||
// 获取角色信息和菜单权限 |
|||
$roleInfo = $this->getStaffRoleInfo($roleType); |
|||
$menuList = $this->getStaffMenuList($roleType); |
|||
|
|||
return [ |
|||
'token' => $token, |
|||
'user_info' => [ |
|||
'id' => $staffInfo['id'], |
|||
'name' => $staffInfo['name'], |
|||
'phone' => $staffInfo['phone'], |
|||
'avatar' => $staffInfo['head_img'] ?? '', |
|||
'real_name' => $staffInfo['real_name'] ?? $staffInfo['name'], |
|||
'account_type' => $staffInfo['account_type'], |
|||
'employee_number' => $staffInfo['employee_number'], |
|||
'user_type' => self::USER_TYPE_STAFF, |
|||
'role_type' => $roleType, |
|||
], |
|||
'role_info' => $roleInfo, |
|||
'menu_list' => $menuList, |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 会员端登录处理 |
|||
* @param string $username |
|||
* @param string $password |
|||
* @return array |
|||
* @throws \Exception |
|||
*/ |
|||
private function handleMemberLogin(string $username, string $password) |
|||
{ |
|||
// 查找会员信息 |
|||
$member = new Member(); |
|||
$memberInfo = $member->where(function($query) use ($username) { |
|||
$query->where('username', $username) |
|||
->whereOr('mobile', $username); |
|||
}) |
|||
->where('status', 1) |
|||
->find(); |
|||
|
|||
if (!$memberInfo) { |
|||
throw new CommonException('会员账号不存在或已禁用'); |
|||
} |
|||
|
|||
// 验证密码 |
|||
if (!password_verify($password, $memberInfo['password'])) { |
|||
throw new CommonException('密码错误'); |
|||
} |
|||
|
|||
// 生成Token |
|||
$tokenData = [ |
|||
'user_id' => $memberInfo['member_id'], |
|||
'user_type' => self::USER_TYPE_MEMBER, |
|||
'site_id' => $memberInfo['site_id'] ?? 0, |
|||
]; |
|||
|
|||
$tokenResult = TokenAuth::createToken($memberInfo['member_id'], AppTypeDict::API, $tokenData, 86400); |
|||
$token = $tokenResult['token']; |
|||
|
|||
// 获取会员菜单权限 |
|||
$menuList = $this->getMemberMenuList(); |
|||
|
|||
return [ |
|||
'token' => $token, |
|||
'user_info' => [ |
|||
'id' => $memberInfo['member_id'], |
|||
'username' => $memberInfo['username'], |
|||
'nickname' => $memberInfo['nickname'], |
|||
'mobile' => $memberInfo['mobile'], |
|||
'avatar' => $memberInfo['headimg'] ?? '', |
|||
'user_type' => self::USER_TYPE_MEMBER, |
|||
], |
|||
'role_info' => [ |
|||
'role_name' => '会员', |
|||
'role_type' => 'member', |
|||
], |
|||
'menu_list' => $menuList, |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 员工端登录(兼容旧接口) |
|||
* @param array $data |
|||
* @return array |
|||
*/ |
|||
public function staffLogin(array $data) |
|||
{ |
|||
$staffData = [ |
|||
'username' => $data['phone'], |
|||
'password' => $data['password'], |
|||
'login_type' => self::USER_TYPE_STAFF, |
|||
]; |
|||
|
|||
$result = $this->unifiedLogin($staffData); |
|||
|
|||
// 添加user_type到用户信息中用于前端判断 |
|||
$result['user_info']['user_type_code'] = $data['user_type']; |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 会员端登录(兼容旧接口) |
|||
* @param array $data |
|||
* @return array |
|||
*/ |
|||
public function memberLogin(array $data) |
|||
{ |
|||
$memberData = [ |
|||
'username' => $data['username'] ?: $data['mobile'], |
|||
'password' => $data['password'], |
|||
'login_type' => self::USER_TYPE_MEMBER, |
|||
]; |
|||
|
|||
return $this->unifiedLogin($memberData); |
|||
} |
|||
|
|||
/** |
|||
* 获取员工角色信息 |
|||
* @param int $roleType |
|||
* @return array |
|||
*/ |
|||
private function getStaffRoleInfo(int $roleType) |
|||
{ |
|||
$roles = [ |
|||
self::STAFF_ROLE_MARKET => ['role_name' => '市场人员', 'role_code' => 'market'], |
|||
self::STAFF_ROLE_COACH => ['role_name' => '教练', 'role_code' => 'coach'], |
|||
self::STAFF_ROLE_SALES => ['role_name' => '销售人员', 'role_code' => 'sales'], |
|||
self::STAFF_ROLE_TEACHER => ['role_name' => '教师', 'role_code' => 'teacher'], |
|||
]; |
|||
|
|||
return $roles[$roleType] ?? ['role_name' => '教师', 'role_code' => 'teacher']; |
|||
} |
|||
|
|||
/** |
|||
* 获取员工菜单列表 |
|||
* @param int $roleType |
|||
* @return array |
|||
*/ |
|||
private function getStaffMenuList(int $roleType) |
|||
{ |
|||
// 根据角色类型返回对应的菜单权限 |
|||
switch ($roleType) { |
|||
case self::STAFF_ROLE_MARKET: |
|||
return [ |
|||
['path' => '/pages/market/home/index', 'name' => '首页', 'icon' => 'home'], |
|||
['path' => '/pages/market/clue/index', 'name' => '线索管理', 'icon' => 'clue'], |
|||
['path' => '/pages/market/clue/add_clues', 'name' => '添加客户', 'icon' => 'add'], |
|||
['path' => '/pages/market/data/statistics', 'name' => '数据统计', 'icon' => 'data'], |
|||
['path' => '/pages/market/my/index', 'name' => '个人中心', 'icon' => 'user'], |
|||
]; |
|||
|
|||
case self::STAFF_ROLE_COACH: |
|||
case self::STAFF_ROLE_TEACHER: |
|||
return [ |
|||
['path' => '/pages/coach/home/index', 'name' => '首页', 'icon' => 'home'], |
|||
['path' => '/pages/coach/course/list', 'name' => '课表管理', 'icon' => 'course'], |
|||
['path' => '/pages/coach/student/student_list', 'name' => '我的学员', 'icon' => 'student'], |
|||
['path' => '/pages/coach/job/list', 'name' => '作业管理', 'icon' => 'job'], |
|||
['path' => '/pages/coach/my/index', 'name' => '个人中心', 'icon' => 'user'], |
|||
]; |
|||
|
|||
case self::STAFF_ROLE_SALES: |
|||
return [ |
|||
['path' => '/pages/market/index/index', 'name' => '首页', 'icon' => 'home'], |
|||
['path' => '/pages/market/clue/index', 'name' => '线索管理', 'icon' => 'clue'], |
|||
['path' => '/pages/market/clue/add_clues', 'name' => '添加客户', 'icon' => 'add'], |
|||
['path' => '/pages/market/clue/clue_table', 'name' => '数据统计', 'icon' => 'data'], |
|||
['path' => '/pages/market/my/index', 'name' => '个人中心', 'icon' => 'user'], |
|||
]; |
|||
|
|||
default: |
|||
return [ |
|||
['path' => '/pages/coach/home/index', 'name' => '首页', 'icon' => 'home'], |
|||
['path' => '/pages/coach/my/index', 'name' => '个人中心', 'icon' => 'user'], |
|||
]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取会员菜单列表 |
|||
* @return array |
|||
*/ |
|||
private function getMemberMenuList() |
|||
{ |
|||
return [ |
|||
['path' => '/pages/student/index/index', 'name' => '首页', 'icon' => 'home'], |
|||
['path' => '/pages/student/timetable/index', 'name' => '课表', 'icon' => 'timetable'], |
|||
['path' => '/pages/student/my/my', 'name' => '个人中心', 'icon' => 'user'], |
|||
// 家长端菜单 |
|||
['path' => '/pages/parent/user-info/index', 'name' => '用户信息', 'icon' => 'user-info'], |
|||
['path' => '/pages/parent/courses/index', 'name' => '课程管理', 'icon' => 'course'], |
|||
['path' => '/pages/parent/materials/index', 'name' => '教学资料', 'icon' => 'material'], |
|||
['path' => '/pages/parent/services/index', 'name' => '服务管理', 'icon' => 'service'], |
|||
['path' => '/pages/parent/orders/index', 'name' => '订单管理', 'icon' => 'order'], |
|||
['path' => '/pages/parent/messages/index', 'name' => '消息管理', 'icon' => 'message'], |
|||
['path' => '/pages/parent/contracts/index', 'name' => '合同管理', 'icon' => 'contract'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 登出 |
|||
* @throws \Exception |
|||
*/ |
|||
public function logout() |
|||
{ |
|||
$token = request()->header('token'); |
|||
if ($token) { |
|||
(new CoreTokenService())->delete($token); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取用户信息 |
|||
* @return array |
|||
* @throws \Exception |
|||
*/ |
|||
public function getUserInfo() |
|||
{ |
|||
$token = request()->header('token'); |
|||
if (!$token) { |
|||
throw new CommonException('未登录'); |
|||
} |
|||
|
|||
$tokenData = (new CoreTokenService())->verify($token); |
|||
if (!$tokenData) { |
|||
throw new CommonException('Token无效'); |
|||
} |
|||
|
|||
$userType = $tokenData['user_type']; |
|||
$userId = $tokenData['user_id']; |
|||
|
|||
if ($userType === self::USER_TYPE_STAFF) { |
|||
$personnel = new Personnel(); |
|||
$userInfo = $personnel->alias('p') |
|||
->leftJoin('school_sys_user u', 'p.sys_user_id = u.uid') |
|||
->where('p.id', $userId) |
|||
->field('p.*, u.real_name') |
|||
->find(); |
|||
if (!$userInfo) { |
|||
throw new CommonException('员工信息不存在'); |
|||
} |
|||
|
|||
$roleType = $this->getAccountTypeCode($userInfo['account_type']); |
|||
|
|||
return [ |
|||
'id' => $userInfo['id'], |
|||
'name' => $userInfo['name'], |
|||
'phone' => $userInfo['phone'], |
|||
'avatar' => $userInfo['head_img'] ?? '', |
|||
'real_name' => $userInfo['real_name'] ?? $userInfo['name'], |
|||
'account_type' => $userInfo['account_type'], |
|||
'user_type' => self::USER_TYPE_STAFF, |
|||
'role_type' => $roleType, |
|||
]; |
|||
} else { |
|||
$member = new Member(); |
|||
$userInfo = $member->where('member_id', $userId)->find(); |
|||
if (!$userInfo) { |
|||
throw new CommonException('会员信息不存在'); |
|||
} |
|||
|
|||
return [ |
|||
'id' => $userInfo['member_id'], |
|||
'username' => $userInfo['username'], |
|||
'nickname' => $userInfo['nickname'], |
|||
'mobile' => $userInfo['mobile'], |
|||
'avatar' => $userInfo['headimg'] ?? '', |
|||
'user_type' => self::USER_TYPE_MEMBER, |
|||
]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 刷新Token |
|||
* @return array |
|||
* @throws \Exception |
|||
*/ |
|||
public function refreshToken() |
|||
{ |
|||
$token = request()->header('token'); |
|||
if (!$token) { |
|||
throw new CommonException('未登录'); |
|||
} |
|||
|
|||
$newToken = (new CoreTokenService())->refresh($token); |
|||
if (!$newToken) { |
|||
throw new CommonException('Token刷新失败'); |
|||
} |
|||
|
|||
return ['token' => $newToken]; |
|||
} |
|||
|
|||
/** |
|||
* 根据账户类型获取角色编码 |
|||
* @param string $accountType |
|||
* @return int |
|||
*/ |
|||
private function getAccountTypeCode(string $accountType) |
|||
{ |
|||
switch ($accountType) { |
|||
case 'teacher': |
|||
return self::STAFF_ROLE_TEACHER; |
|||
case 'market': |
|||
return self::STAFF_ROLE_MARKET; |
|||
default: |
|||
return self::STAFF_ROLE_TEACHER; // 默认为教师 |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
<script> |
|||
export default { |
|||
name: 'CustomTopPopup', |
|||
props: { |
|||
modelValue: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
computed: { |
|||
show() { |
|||
return this.modelValue |
|||
} |
|||
}, |
|||
methods: { |
|||
closePopup() { |
|||
console.log('关闭弹窗') |
|||
this.$emit('update:modelValue', false) |
|||
}, |
|||
handleMaskClick() { |
|||
console.log('点击遮罩') |
|||
this.closePopup() |
|||
} |
|||
}, |
|||
mounted() { |
|||
console.log('CustomTopPopup mounted, modelValue:', this.modelValue) |
|||
}, |
|||
watch: { |
|||
modelValue(newVal) { |
|||
console.log('CustomTopPopup modelValue changed:', newVal) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<view v-if="show" class="top-popup-mask" @tap="handleMaskClick"> |
|||
<view class="top-popup-content" @tap.stop> |
|||
<slot /> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<style lang="scss"> |
|||
.top-popup-mask { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(0, 0, 0, 0.6); |
|||
z-index: 1001; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.top-popup-content { |
|||
background: #fff; |
|||
border-bottom-left-radius: 24rpx; |
|||
border-bottom-right-radius: 24rpx; |
|||
animation: slideDown 0.3s ease-out; |
|||
width: 100%; |
|||
} |
|||
|
|||
@keyframes slideDown { |
|||
from { |
|||
transform: translateY(-100%); |
|||
} |
|||
to { |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,230 @@ |
|||
<template> |
|||
<view class="home-container"> |
|||
<!-- 自定义导航栏 --> |
|||
<uni-nav-bar |
|||
:statusBar="true" |
|||
backgroundColor="#181A20" |
|||
color="#fff" |
|||
title="首页" |
|||
/> |
|||
|
|||
<!-- 用户信息区域 --> |
|||
<view class="user-info-section"> |
|||
<view class="user-avatar"> |
|||
<image :src="userInfo.avatar || '/static/icon-img/tou.png'" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="user-details"> |
|||
<text class="user-name">{{ userInfo.name || '员工姓名' }}</text> |
|||
<text class="user-role">{{ (userInfo.role_info && userInfo.role_info.role_name) || '员工角色' }}</text> |
|||
<text class="user-number">工号:{{ userInfo.employee_number || '未设置' }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 九宫格操作区域 --> |
|||
<view class="grid-container"> |
|||
<view class="grid-title">员工操作</view> |
|||
<view class="grid-content"> |
|||
<view |
|||
class="grid-item" |
|||
v-for="(item, index) in gridItems" |
|||
:key="index" |
|||
@click="handleGridClick(item)" |
|||
> |
|||
<view class="grid-icon"> |
|||
<uni-icons :type="item.icon" size="32" color="#29d3b4"></uni-icons> |
|||
</view> |
|||
<text class="grid-text">{{ item.title }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
userInfo: {}, |
|||
gridItems: [ |
|||
{ |
|||
title: '客户资源', |
|||
icon: 'person-filled', |
|||
path: '/pages/market/clue/index' |
|||
}, |
|||
{ |
|||
title: '课程安排', |
|||
icon: 'calendar-filled', |
|||
path: '/pages/market/clue/class_arrangement' |
|||
}, |
|||
{ |
|||
title: '课程查询', |
|||
icon: 'search', |
|||
path: '/pages/coach/schedule/schedule_table' |
|||
}, |
|||
{ |
|||
title: '学员管理', |
|||
icon: 'contact-filled', |
|||
path: '/pages/coach/student/student_list' |
|||
}, |
|||
{ |
|||
title: '我的数据', |
|||
icon: 'bars', |
|||
path: '/pages/market/my/my_data' |
|||
}, |
|||
{ |
|||
title: '部门数据', |
|||
icon: 'staff', |
|||
path: '/pages/market/my/dept_data' |
|||
}, |
|||
{ |
|||
title: '校区数据', |
|||
icon: 'location-filled', |
|||
path: '/pages/market/my/campus_data' |
|||
}, |
|||
{ |
|||
title: '考勤管理', |
|||
icon: 'checkmarkempty', |
|||
path: '/pages/common/my_attendance' |
|||
}, |
|||
{ |
|||
title: '我的消息', |
|||
icon: 'chat-filled', |
|||
path: '/pages/common/my_message' |
|||
}, |
|||
{ |
|||
title: '报销管理', |
|||
icon: 'wallet-filled', |
|||
path: '/pages/market/reimbursement/list' |
|||
}, |
|||
{ |
|||
title: '资料库', |
|||
icon: 'folder-add-filled', |
|||
path: '/pages/coach/my/teaching_management' |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
onLoad() { |
|||
this.loadUserInfo(); |
|||
}, |
|||
methods: { |
|||
loadUserInfo() { |
|||
// 从本地存储获取用户信息 |
|||
const userInfo = uni.getStorageSync('userInfo'); |
|||
if (userInfo) { |
|||
this.userInfo = userInfo; |
|||
} |
|||
}, |
|||
handleGridClick(item) { |
|||
uni.navigateTo({ |
|||
url: item.path, |
|||
fail: (err) => { |
|||
console.error('页面跳转失败:', err); |
|||
uni.showToast({ |
|||
title: '页面暂未开放', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.home-container { |
|||
background-color: #181A20; |
|||
min-height: 100vh; |
|||
color: #fff; |
|||
} |
|||
|
|||
.user-info-section { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 20px; |
|||
background: linear-gradient(135deg, #29d3b4 0%, #1a9b7c 100%); |
|||
margin: 20px; |
|||
border-radius: 12px; |
|||
} |
|||
|
|||
.user-avatar { |
|||
width: 60px; |
|||
height: 60px; |
|||
margin-right: 15px; |
|||
} |
|||
|
|||
.user-avatar image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.user-details { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.user-name { |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
margin-bottom: 5px; |
|||
color: #fff; |
|||
} |
|||
|
|||
.user-role { |
|||
font-size: 14px; |
|||
color: rgba(255, 255, 255, 0.8); |
|||
margin-bottom: 3px; |
|||
} |
|||
|
|||
.user-number { |
|||
font-size: 12px; |
|||
color: rgba(255, 255, 255, 0.6); |
|||
} |
|||
|
|||
.grid-container { |
|||
margin: 20px; |
|||
} |
|||
|
|||
.grid-title { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
margin-bottom: 15px; |
|||
color: #fff; |
|||
} |
|||
|
|||
.grid-content { |
|||
display: grid; |
|||
grid-template-columns: repeat(3, 1fr); |
|||
gap: 15px; |
|||
} |
|||
|
|||
.grid-item { |
|||
background-color: #292929; |
|||
border-radius: 8px; |
|||
padding: 20px 10px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
min-height: 80px; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.grid-item:active { |
|||
background-color: #3a3a3a; |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
.grid-icon { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.grid-text { |
|||
font-size: 12px; |
|||
color: #fff; |
|||
text-align: center; |
|||
line-height: 1.2; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,245 @@ |
|||
<template> |
|||
<view class="profile-container"> |
|||
<!-- 自定义导航栏 --> |
|||
<uni-nav-bar |
|||
:statusBar="true" |
|||
backgroundColor="#181A20" |
|||
color="#fff" |
|||
title="我的" |
|||
/> |
|||
|
|||
<!-- 用户头像和基本信息 --> |
|||
<view class="profile-header"> |
|||
<view class="avatar-section"> |
|||
<image :src="userInfo.avatar || '/static/icon-img/tou.png'" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="user-info"> |
|||
<text class="user-name">{{ userInfo.name || '员工姓名' }}</text> |
|||
<text class="user-role">{{ (userInfo.role_info && userInfo.role_info.role_name) || '员工角色' }}</text> |
|||
<text class="user-phone">{{ userInfo.phone || '手机号码' }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 我的功能九宫格 --> |
|||
<view class="grid-container"> |
|||
<view class="grid-title">个人中心</view> |
|||
<view class="grid-content"> |
|||
<view |
|||
class="grid-item" |
|||
v-for="(item, index) in profileItems" |
|||
:key="index" |
|||
@click="handleProfileClick(item)" |
|||
> |
|||
<view class="grid-icon"> |
|||
<uni-icons :type="item.icon" size="32" color="#29d3b4"></uni-icons> |
|||
</view> |
|||
<text class="grid-text">{{ item.title }}</text> |
|||
<text class="grid-desc" v-if="item.desc">{{ item.desc }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
userInfo: {}, |
|||
profileItems: [ |
|||
{ |
|||
title: '我的资料', |
|||
icon: 'contact', |
|||
desc: '查看编辑个人信息', |
|||
action: 'viewProfile' |
|||
}, |
|||
{ |
|||
title: '我的合同', |
|||
icon: 'compose', |
|||
path: '/pages/parent/contracts/index' |
|||
}, |
|||
{ |
|||
title: '我的工资', |
|||
icon: 'wallet', |
|||
desc: '查看工资明细', |
|||
action: 'viewSalary' |
|||
}, |
|||
{ |
|||
title: '我的考勤', |
|||
icon: 'calendar', |
|||
path: '/pages/common/my_attendance' |
|||
}, |
|||
{ |
|||
title: '系统设置', |
|||
icon: 'settings', |
|||
path: '/pages/market/my/set_up' |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
onLoad() { |
|||
this.loadUserInfo(); |
|||
}, |
|||
onShow() { |
|||
this.loadUserInfo(); |
|||
}, |
|||
methods: { |
|||
loadUserInfo() { |
|||
// 从本地存储获取用户信息 |
|||
const userInfo = uni.getStorageSync('userInfo'); |
|||
if (userInfo) { |
|||
this.userInfo = userInfo; |
|||
} |
|||
}, |
|||
handleProfileClick(item) { |
|||
if (item.action) { |
|||
// 处理特殊操作 |
|||
switch (item.action) { |
|||
case 'viewProfile': |
|||
this.viewPersonalProfile(); |
|||
break; |
|||
case 'viewSalary': |
|||
this.viewSalaryInfo(); |
|||
break; |
|||
} |
|||
} else if (item.path) { |
|||
// 页面跳转 |
|||
uni.navigateTo({ |
|||
url: item.path, |
|||
fail: (err) => { |
|||
console.error('页面跳转失败:', err); |
|||
uni.showToast({ |
|||
title: '页面暂未开放', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
viewPersonalProfile() { |
|||
// 显示个人资料弹窗或跳转到编辑页面 |
|||
uni.showModal({ |
|||
title: '个人资料', |
|||
content: '个人资料页面开发中,将包含employee_number、name、phone、email、address等字段的查看和编辑功能', |
|||
showCancel: false |
|||
}); |
|||
}, |
|||
viewSalaryInfo() { |
|||
// 显示工资信息弹窗或跳转到工资页面 |
|||
uni.showModal({ |
|||
title: '工资明细', |
|||
content: '工资明细页面开发中,将显示base_salary、performance_bonus、deductions、net_salary等字段,只能查看不能修改', |
|||
showCancel: false |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.profile-container { |
|||
background-color: #181A20; |
|||
min-height: 100vh; |
|||
color: #fff; |
|||
} |
|||
|
|||
.profile-header { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 30px 20px; |
|||
background: linear-gradient(135deg, #29d3b4 0%, #1a9b7c 100%); |
|||
margin: 20px; |
|||
border-radius: 12px; |
|||
} |
|||
|
|||
.avatar-section { |
|||
width: 80px; |
|||
height: 80px; |
|||
margin-right: 20px; |
|||
} |
|||
|
|||
.avatar-section image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
border: 3px solid rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
.user-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.user-name { |
|||
font-size: 20px; |
|||
font-weight: bold; |
|||
margin-bottom: 8px; |
|||
color: #fff; |
|||
} |
|||
|
|||
.user-role { |
|||
font-size: 14px; |
|||
color: rgba(255, 255, 255, 0.8); |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.user-phone { |
|||
font-size: 12px; |
|||
color: rgba(255, 255, 255, 0.6); |
|||
} |
|||
|
|||
.grid-container { |
|||
margin: 20px; |
|||
} |
|||
|
|||
.grid-title { |
|||
font-size: 16px; |
|||
font-weight: bold; |
|||
margin-bottom: 15px; |
|||
color: #fff; |
|||
} |
|||
|
|||
.grid-content { |
|||
display: grid; |
|||
grid-template-columns: repeat(3, 1fr); |
|||
gap: 15px; |
|||
} |
|||
|
|||
.grid-item { |
|||
background-color: #292929; |
|||
border-radius: 8px; |
|||
padding: 20px 10px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
min-height: 90px; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.grid-item:active { |
|||
background-color: #3a3a3a; |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
.grid-icon { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.grid-text { |
|||
font-size: 12px; |
|||
color: #fff; |
|||
text-align: center; |
|||
line-height: 1.2; |
|||
margin-bottom: 3px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.grid-desc { |
|||
font-size: 10px; |
|||
color: rgba(255, 255, 255, 0.6); |
|||
text-align: center; |
|||
line-height: 1.1; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
Loading…
Reference in new issue