提交 04f6f468 作者: 刘飞飞

Initial commit

上级
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ public/ [L]
RewriteRule (.*) public/$1 [L]
</IfModule>
\ No newline at end of file
<?php
/**
* This file is part of the Phalcon Developer Tools.
*
* (c) Phalcon Team <team@phalcon.io>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) {
return false;
}
$_GET['_url'] = $_SERVER['REQUEST_URI'];
require_once __DIR__ . '/public/index.php';
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/gradeManageSystem.iml" filepath="$PROJECT_DIR$/.idea/gradeManageSystem.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpCodeSniffer">
<phpcs_settings>
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="2f27d9f9-abea-4f57-a81b-f2f9fc4a813a" timeout="30000" />
</phpcs_settings>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.1">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
<component name="PhpStan">
<PhpStan_settings>
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="2f27d9f9-abea-4f57-a81b-f2f9fc4a813a" timeout="60000" />
</PhpStan_settings>
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="Psalm">
<Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="2f27d9f9-abea-4f57-a81b-f2f9fc4a813a" timeout="60000" />
</Psalm_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>
\ No newline at end of file
<?php
/*
* Modified: prepend directory path of current file, because of this file own different ENV under between Apache and command line.
* NOTE: please remove this comment.
*/
defined('BASE_PATH') || define('BASE_PATH', getenv('BASE_PATH') ?: realpath(dirname(__FILE__) . '/../..'));
defined('APP_PATH') || define('APP_PATH', BASE_PATH . '/app');
return new \Phalcon\Config\Config([
'database' => [
'adapter' => 'Mysql',
'host' => 'rm-bp18k55bv9osa6u1vmo.mysql.rds.aliyuncs.com:3306',
'username' => 'develop_local',
'password' => 'gN2!MZzl0ukOpKYZdP',
'dbname' => 'develop_local',
'charset' => 'utf8',
],
'application' => [
'appDir' => APP_PATH . '/',
'controllersDir' => APP_PATH . '/controllers/',
'modelsDir' => APP_PATH . '/models/',
'migrationsDir' => APP_PATH . '/migrations/',
'viewsDir' => APP_PATH . '/views/',
'pluginsDir' => APP_PATH . '/plugins/',
'libraryDir' => APP_PATH . '/library/',
'cacheDir' => BASE_PATH . '/cache/',
'baseUri' => '/',
]
]);
<?php
$loader = new \Phalcon\Autoload\Loader();
/**
* We're a registering a set of directories taken from the configuration file
*/
$loader->setDirectories(
[
$config->application->controllersDir,
$config->application->modelsDir
]
)->register();
<?php
$router = $di->getRouter();
// Define your routes here
$router->handle($_SERVER['REQUEST_URI']);
<?php
declare(strict_types=1);
use Phalcon\Html\Escaper;
use Phalcon\Flash\Direct as Flash;
use Phalcon\Mvc\Model\Metadata\Memory as MetaDataAdapter;
use Phalcon\Mvc\View;
use Phalcon\Mvc\View\Engine\Php as PhpEngine;
use Phalcon\Mvc\View\Engine\Volt as VoltEngine;
use Phalcon\Session\Adapter\Stream as SessionAdapter;
use Phalcon\Session\Manager as SessionManager;
use Phalcon\Mvc\Url as UrlResolver;
/**
* Shared configuration service
*/
$di->setShared('config', function () {
return include APP_PATH . "/config/config.php";
});
/**
* The URL component is used to generate all kind of urls in the application
*/
$di->setShared('url', function () {
$config = $this->getConfig();
$url = new UrlResolver();
$url->setBaseUri($config->application->baseUri);
return $url;
});
/**
* Setting up the view component
*/
$di->setShared('view', function () {
$config = $this->getConfig();
$view = new View();
$view->setDI($this);
$view->setViewsDir($config->application->viewsDir);
$view->registerEngines([
'.volt' => function ($view) {
$config = $this->getConfig();
$volt = new VoltEngine($view, $this);
$volt->setOptions([
'path' => $config->application->cacheDir,
'separator' => '_'
]);
return $volt;
},
'.phtml' => PhpEngine::class
]);
return $view;
});
/**
* Database connection is created based in the parameters defined in the configuration file
*/
$di->setShared('db', function () {
$config = $this->getConfig();
$class = 'Phalcon\Db\Adapter\Pdo\\' . $config->database->adapter;
$params = [
'host' => $config->database->host,
'username' => $config->database->username,
'password' => $config->database->password,
'dbname' => $config->database->dbname,
'charset' => $config->database->charset
];
if ($config->database->adapter == 'Postgresql') {
unset($params['charset']);
}
return new $class($params);
});
/**
* If the configuration specify the use of metadata adapter use it or use memory otherwise
*/
$di->setShared('modelsMetadata', function () {
return new MetaDataAdapter();
});
/**
* Register the session flash service with the Twitter Bootstrap classes
*/
$di->set('flash', function () {
$escaper = new Escaper();
$flash = new Flash($escaper);
$flash->setImplicitFlush(false);
$flash->setCssClasses([
'error' => 'alert alert-danger',
'success' => 'alert alert-success',
'notice' => 'alert alert-info',
'warning' => 'alert alert-warning'
]);
return $flash;
});
/**
* Start the session the first time some component request the session service
*/
$di->setShared('session', function () {
$session = new SessionManager();
$files = new SessionAdapter([
'savePath' => sys_get_temp_dir(),
]);
$session->setAdapter($files);
$session->start();
return $session;
});
<?php
declare(strict_types=1);
use Phalcon\Mvc\Controller;
class ControllerBase extends Controller
{
public function initialize()
{
// 1. 检查是否登录
$auth = $this->session->get('auth');
if (empty($auth) || !isset($auth['identity'])) {
$this->flashSession->error('请先登录');
return $this->response->redirect('login/index'); // 未登录 → 跳登录页
}
// 2. 获取当前访问的控制器和方法
$currentController = $this->dispatcher->getControllerName(); // 如 'lff_admin'、'index'
$currentAction = $this->dispatcher->getActionName();
$identity = $auth['identity'];
// 3. 允许所有已登录用户访问首页(index 控制器)
if ($currentController === 'index') {
return true; // 跳过后续权限检查,直接允许访问
}
// 4. 超级管理员(0):无限制(除首页外的其他控制器)
if ($identity == 0) {
return true;
}
// 5. 普通管理员(1):限制
if ($identity == 1) {
$allowedControllers = ['lff_admin', 'lff_course', 'lff_college','lff_student','lff_teacher'];
if (!in_array($currentController, $allowedControllers)) {
echo 111;die;
$this->flashSession->error('权限不足:无法访问该功能');
return $this->response->redirect('index/index');
}
if ($currentController == 'lff_admin' && $currentAction == 'acaAdminList') {
$this->flashSession->error('权限不足:无法访问教学管理员账号管理');
return $this->response->redirect('index/index');
}
}
// 6. 教师(2):限制
if ($identity == 2) {
$allowedControllers = ['lff_score'];
if (!in_array($currentController, $allowedControllers)) {
$this->flashSession->error('权限不足:无法访问该功能');
return $this->response->redirect('index/index');
}
}
// 7. 学生(3):限制
if ($identity == 3) {
$allowedControllers = [''];
if (!in_array($currentController, $allowedControllers)) {
$this->flashSession->error('权限不足:无法访问该功能');
return $this->response->redirect('index/index');
}
}
}
}
\ No newline at end of file
<?php
declare(strict_types=1);
use Phalcon\Mvc\Controller;
class IndexController extends Controller
{
public function indexAction()
{
$this->view->setVar('pageTitle', '教学管理系统首页');
}
}
<?php
use Phalcon\Mvc\Model\Criteria;
use Phalcon\Paginator\Adapter\Model;
use Phalcon\Mvc\Controller;
class LffAdminController extends ControllerBase
{
public function indexAction()
{
}
public function acaAdminListAction()
{
// 1. 验证用户权限(确保只有管理员能访问)
// var_dump($this->session->get('auth'));die;
// if (!$this->session->has('auth') || $this->session->get('auth')['identity'] == 0) {
// $this->flashSession->error('无权限访问,请先登录管理员账号');
// return $this->response->redirect('login/index');
// }
// // 2. 获取分页参数(默认第1页,每页10条)
// $currentPage = (int)$this->request->getQuery('page', 'int', 1);
// $limit = 10;
// 3. 查询教师数据(假设教师的identity=2,根据实际业务调整)
// 查询 identity <= 1 的第一条记录
$teachers = LffUsers::find([
"identity <= :identity:", // 条件:identity 小于等于 1
"bind" => [
'identity' => 1 // 绑定参数 1
]
]);
// // 4. 处理分页
// $paginator = new \Phalcon\Paginator\Adapter\Model([
// 'data' => $teachers,
// 'limit' => $limit,
// 'page' => $currentPage
// ]);
// 5. 将分页数据传递给视图
$this->view->teachers = $teachers;
// 6. 可选:传递统计信息
$this->view->totalCount = $teachers->count();
}
public function addAdminAction()
{
}
public function createAction()
{
$user_account = $this->request->getPost('user_account');
$username = $this->request->getPost('username');
$email = $this->request->getPost('email');
$password = $this->request->getPost('password');
// var_dump($password);die;
$identity = $this->request->getPost('identity');
$is_enable = $this->request->getPost('is_enable');
$is_lock = $this->request->getPost('is_lock');
$acdAdmin = LffUsers::findFirst([
"user_account = :user_account:",
"identity = :identity:",
"bind" => [
'user_account' => $user_account
]
]);
if ($acdAdmin) {
$this->flashSession->error('该管理员账号已存在,请更换账号名');
return $this->response->redirect('lff_admin/create');
}
$user = new LffUsers();
$user->user_account = $user_account;
$user->username = $username;
$user->password = $this->security->hash($password); // 密码加密存储
$user->identity = $identity; // 0=超级管理员,1=教学管理员
$user->is_enable = $is_enable;
$user->is_lock = $is_lock;
if (!$user->save()) {
// 收集验证错误信息
foreach ($user->getMessages() as $message) {
$this->flashSession->error($message->getMessage());
}
throw new Exception('用户表数据保存失败');
}
$admin = new LffAdmin();
$admin->user_id = $user->user_id; // 关联users表的主键
$admin->admin_email = $email;
if (!$admin->save()) {
foreach ($admin->getMessages() as $message) {
$this->flashSession->error($message->getMessage());
}
throw new Exception('管理员表数据保存失败');
}
$this->flashSession->success('管理员创建成功');
return $this->response->redirect('lff_admin/acaAdminList');
}
public function editAdminAction($id)
{
$adminInfo = $this->modelsManager->createBuilder()
->columns([
// users表字段(前缀u_区分)
'u.user_id',
'u.user_account',
'u.username',
'u.identity',
'u.is_enable',
'u.is_lock',
'u.create_time',
'u.last_login_time',
'a.admin_email',
])
->from(['u' => 'LffUsers'])
->leftJoin('LffAdmin', 'a.user_id = u.user_id', 'a')
->where('u.user_id = :id:', ['id' => $id])
->getQuery()
->getSingleResult();
if (!$adminInfo) {
$this->flashSession->error('管理员不存在');
return $this->response->redirect('lff_admin/adminList');
}
$this->view->adminInfo = $adminInfo;
}
public function saveAction()
{
$userId = $this->request->getPost('user_id');
if ($userId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_admin/acaAdminList');
}
$username = $this->request->getPost('username');
$email = $this->request->getPost('email');
$is_enable = $this->request->getPost('is_enable');
$is_lock = $this->request->getPost('is_lock');
$reset_password = $this->request->getPost('reset_password');
// var_dump($reset_password);die;
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => [
'user_id' => $userId
]
]);
$admin = LffAdmin::findFirst(['user_id = :uid:', 'bind' => ['uid' => $userId]]);
if ($admin) {
$admin->admin_email = $email;
$admin->save();
}
if (!$user) {
$this->flashSession->error("该管理员不存在");
$this->response->redirect('lff_admin/acaAdminList');
}
$user->username = $username;
$user->is_enable = $is_enable;
$user->is_lock = $is_lock;
// var_dump($user->save());
// die;
if($reset_password == 1){
$defaultPwd = 'admin123?';
$user->password = $this->security->hash($defaultPwd);
// $user->password = $defaultPwd;
}
// die;
if(!$user->save()){
foreach ($user->getMessages() as $message) {
$this->flashSession->error($message->getMessage());
return $this->response->redirect('lff_admin/editAdmin?id=' . $user->id);
}
}
// if (!$user->save()) {
// // 打印所有错误信息(包含验证失败的字段和原因)
// foreach ($user->getMessages() as $message) {
// // 输出格式:字段名 + 错误原因(例如:"username:姓名不能为空")
// $this->flashSession->error("字段 [{$message->getField()}] 错误:{$message->getMessage()}");
// }
// // 跳转回编辑页
// return $this->response->redirect('lff_admin/editAdmin/' . $user->user_id);
// }
$this->flashSession->success("管理员信息修改成功");
return $this->response->redirect('lff_admin/acaAdminList');
}
public function deleteAdminAction($id)
{
if (!$this->session->has('auth') || $this->session->get('auth')['identity'] != 0) {
$this->flashSession->error('仅超级管理员可执行删除操作');
return $this->response->redirect('lff_admin/acaAdminList');
}
// 2. 验证管理员ID有效性
$userId = (int)$id;
if ($userId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_admin/acaAdminList');
}
// 3. 查询当前要删除的管理员信息
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => ['user_id' => $userId]
]);
if (!$user) {
$this->flashSession->error("ID为 {$userId} 的管理员不存在");
return $this->response->redirect('lff_admin/acaAdminList');
}
// 4. 检查是否为超级管理员,且是否是最后一个
$isSuperAdmin = ($user->identity == 0); // 当前删除对象是否为超级管理员
if ($isSuperAdmin) {
$superAdminTotal = LffUsers::count([
"identity = :identity:",
"bind" => ['identity' => 0]
]);
// 若只剩最后1个超级管理员,阻止删除
if ($superAdminTotal <= 1) {
$this->flashSession->error('系统至少需保留1个超级管理员,不可删除最后1个');
return $this->response->redirect('lff_admin/acaAdminList');
}
}
// var_dump($this->session->get('auth')['user_id']);
// var_dump($user->user_id);die;
if($this->session->get('auth')['user_id'] == $user->user_id){
// echo 111;die;
$this->flashSession->error('不可删除自身');
return $this->response->redirect('lff_admin/acaAdminList');
}else {
$this->db->begin();
try {
$admin = LffAdmin::findFirst([
'user_id = :uid:',
'bind' => ['uid' => $userId]
]);
if ($admin && !$admin->delete()) {
throw new \Exception('删除管理员扩展信息失败:' . implode(';', $admin->getMessages()));
}
if (!$user->delete()) {
throw new \Exception('删除管理员账号失败:' . implode(';', $user->getMessages()));
}
$this->db->commit();
$this->flashSession->success('管理员账号(含关联信息)已成功删除');
} catch (\Exception $e) {
// 7. 失败时回滚事务
$this->db->rollback();
$this->flashSession->error('删除失败:' . $e->getMessage());
}
}
// 8. 跳转回列表页
return $this->response->redirect('lff_admin/acaAdminList');
}
public function adminEnableAction($id)
{
$teacherId = (int)$id;
if ($teacherId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_admin/acaAdminList');
}
try {
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $teacherId]
]);
if (!$user) {
throw new Exception('教师账号不存在或已被删除');
}
// 已启用则无需操作
if ($user->is_enable == 1) {
$this->flashSession->notice('该教师账号已处于启用状态');
return $this->response->redirect('lff_admin/acaAdminList');
}
// 更新为启用状态
$user->is_enable = 1;
if (!$user->save()) {
$errorMsg = '启用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('管理员账号已成功启用,可正常登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_admin/acaAdminList');
}
public function adminDisableAction($id)
{
// 获取教师ID
$teacherId = (int)$id;
if ($teacherId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_admin/acaAdminList');
}
try {
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $teacherId]
]);
if (!$user) {
throw new Exception('教师账号不存在或已被删除');
}
// 已禁用则无需操作
if ($user->is_enable == 0) {
$this->flashSession->notice('该管理员账号已处于禁用状态');
return $this->response->redirect('lff_admin/acaAdminList');
}
// 更新为禁用状态
$user->is_enable = 0;
if (!$user->save()) {
$errorMsg = '禁用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('教师账号已成功禁用,将无法登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_admin/acaAdminList');
}
}
\ No newline at end of file
<?php
class LffCollegeController extends ControllerBase
{
public function indexAction()
{
}
public function collegeListAction()
{
$keyword = $this->request->getQuery('keyword', 'string', '');
$conditions = '';
$bind = [];
if (!empty($keyword)) {
$conditions = '(college_id LIKE :keyword: OR college_name LIKE :keyword:)';
$bind['keyword'] = "%{$keyword}%";
}
$colleges = LffCollege::find([
'conditions' => $conditions,
'bind' => $bind,
'order' => 'create_time DESC'
]);
// var_dump($colleges->toArray());die;
$this->view->colleges = $colleges;
$this->view->keyword = $keyword;
}
public function addCollegeAction()
{
}
public function createAction()
{
$college_code = $this->request->getPost('college_code');
$college_name = $this->request->getPost('college_name');
if (empty($college_code) || empty($college_name)) {
$this->flashSession->error("学院编号或者学院名称为空");
return $this->response->redirect("lff_college/addCollege");
}
$nameExist = LffCollege::findFirst([
'college_code = :college_code:',
'bind' => [
'college_code' => $college_code]]);
// var_dump($nameExist);die;
if(!empty($nameExist)){
$this->flashSession->error("学院编号已存在");
return $this->response->redirect("lff_college/addCollege");
}
$college = new LffCollege();
$college->college_code = $college_code;
$college->college_name = $college_name;
if(!$college->save()){
$this->flashSession->error("创建学院信息失败");
return $this->response->redirect("lff_college/addCollege");
}
$this->flashSession->success("创建学院信息成功");
return $this->response->redirect("lff_college/collegeList");
}
public function editCollegeAction($id)
{
$collegeInfo = LffCollege::findFirst([
"college_id = :college_id:",
"bind" => ["college_id" => $id],
]);
// var_dump($collegeInfo->toArray());die;
if (!$collegeInfo) {
$this->flashSession->error('学院不存在');
return $this->response->redirect('lff_college/collegeList');
}
$this->view->collegeInfo = $collegeInfo;
}
public function saveAction()
{
$college_id = $this->request->getPost('college_id');
$college_name = $this->request->getPost('college_name');
if ($college_id <= 0) {
$this->flashSession->error('无效的学院ID');
return $this->response->redirect('lff_college/collegeList');
}
$college = LffCollege::findFirst([
"college_id = :id:",
"bind" => ["id" => $college_id]
]);
if (!$college) {
throw new Exception('学院不存在或已被删除');
}
$nameExist = LffCollege::findFirst([
"college_name = :name: AND college_id != :id:",
"bind" => [
"name" => $college_name,
"id" => $college_id
]
]);
if ($nameExist) {
throw new Exception('该学院名称已存在,请更换');
}
$college->college_name = $college_name;
if (!$college->save()) {
$errors = implode(';', $college->getMessages());
throw new Exception("修改失败:{$errors}");
}
$this->flashSession->success('学院信息修改成功');
return $this->response->redirect('lff_college/collegeList');
}
public function deleteCollegeAction($id)
{
$college_id = (int)$id;
// var_dump($college_id);die;
if (empty($college_id)) {
throw new Exception('无效的学院ID');
}
$college = LffCollege::findFirst([
"college_id = :id:",
"bind" => ["id" => $college_id]
]);
if (!$college) {
throw new Exception('学院不存在或已被删除');
}
$students = LffStudent::find([
'college_id = :cid:',
'bind' => ['cid' => $college_id]
]);
foreach ($students as $student) {
$student->delete();
}
if (!$college->delete()) {
$errors = implode(';', $college->getMessages());
throw new Exception("删除失败:{$errors}");
}
$this->flashSession->success("学院已成功删除");
return $this->response->redirect('lff_college/collegeList');
}
}
\ No newline at end of file
<?php
class LffCourseController extends ControllerBase
{
public function indexAction()
{
}
public function courseListAction()
{
$keyword = $this->request->getQuery('keyword', 'string', '');
$queryBuilder = $this->modelsManager->createBuilder()
->columns([
'c.course_id',
'c.course_name',
'c.course_type',
'c.college_id',
'c.credit',
'c.class_hour',
'c.create_time',
't.teacher_id',
't.user_id',
'u.username as teacher_name',
'col.college_name'
])
->from(['c' => 'LffCourse'])
->leftJoin('LffTeacher', 'c.teacher_id = t.teacher_id', 't')
->leftJoin('LffUsers', 't.user_id = u.user_id', 'u')
->leftJoin('LffCollege', 'c.college_id = col.college_id', 'col');
if (!empty($keyword)) {
$queryBuilder->andWhere(
'(c.course_id LIKE :keyword: OR c.course_name LIKE :keyword:)',
['keyword' => "%{$keyword}%"]
);
}
$queryBuilder->orderBy('c.create_time DESC');
$courses = $queryBuilder->getQuery()->execute();
// var_dump($courses->toArray());die;
$this->view->courseInfo = $courses;
$this->view->keyword = $keyword;
}
public function addCourseAction()
{
$this->view->colleges = LffCollege::find([
'order' => 'college_name ASC'
]);
$this->view->teachers = $this->modelsManager->createBuilder()
->columns([
't.teacher_id',
'u.username as teacher_name'
])
->from(['t' => 'LffTeacher'])
->leftJoin('LffUsers', 't.user_id = u.user_id', 'u')
->orderBy('u.username ASC')
->getQuery()
->execute();
}
public function createAction()
{
$course_code = $this->request->getPost('course_code');
$course_name = $this->request->getPost('course_name');
$course_type = $this->request->getPost('course_type');
$college_id = $this->request->getPost('college_id');
$credit = $this->request->getPost('credit');
$class_hour = $this->request->getPost('class_hour');
$teacher_name = $this->request->getPost('teacher_name');
$description = $this->request->getPost('description');
if(!empty($course_code)){
$college_code = substr($course_code, 0, 2);
$course_type = substr($course_code, 2, 3);
$sequence = substr($course_code, 5, 3);
$college = LffCollege::findFirst([
'college_code = :college_code:',
'bind' => [
'college_code' => $college_code
]]);
if($college->count() < 0){
$this->flashSession->error("课程编码不正确");
return $this->response->redirect("lff_course/addCourse");
}
$existingCourse = LffCourse::findFirst([
'course_code = :course_code:',
'bind' => [
'course_code' => $course_code]]);
if ($existingCourse) {
$this->flashSession->error("课程编码已存在");
return $this->response->redirect("lff_course/addCourse");
}
}
$teachers = $this->modelsManager->createBuilder()
->columns([
't.teacher_id',
'u.username as teacher_name',
'c.college_id',
'c.college_name'
])
->from(['t' => 'LffTeacher'])
->leftJoin('LffUsers', 't.user_id = u.user_id', 'u')
->leftJoin('LffCollege', 't.college_id = c.college_id', 'c')
->where('u.username = :username:', ['username' => $teacher_name])
->andWhere('c.college_id = :college_id:', ['college_id' => $college_id])
->getQuery()
->execute();
if($teachers->getFirst()){
$teacher_id = $teachers->getFirst()->teacher_id;
}else{
$this->flashSession->error("该教师不存在");
return $this->response->redirect("lff_course/addCourse");
}
$course = new LffCourse();
$course->course_code = $course_code;
$course->course_name = $course_name;
$course->course_type = $course_type;
$course->college_id = $college_id;
$course->credit = $credit;
$course->class_hour = $class_hour;
$course->teacher_id = $teacher_id;
$course->description = $description;
if(!$course->save()){
$this->flashSession->error("课程创建失败");
return $this->response->redirect("lff_course/addCourse");
}
$this->flashSession->error("课程创建成功");
return $this->response->redirect("lff_course/courseList");
}
public function editCourseAction($id)
{
$course_id = (int)$id;
if($course_id < 0){
$this->flashSession->error("该课程不存在");
return $this->response->redirect("lff_course/courseList");
}
$courseInfo = $this->modelsManager->createBuilder()
->columns([
// 课程表字段
'c.course_id',
'c.course_code',
'c.course_name',
'c.course_type',
'c.credit',
'c.class_hour',
'c.college_id',
'c.description',
'col.college_name',
't.teacher_id',
't.user_id',
'u.username as teacher_name'
])
->from(['c' => 'LffCourse'])
->leftJoin('LffCollege', 'c.college_id = col.college_id', 'col')
->leftJoin('LffTeacher', 'c.teacher_id = t.teacher_id', 't')
->leftJoin('LffUsers', 't.user_id = u.user_id', 'u')
->where('c.course_id = :course_id:', ['course_id' => $course_id])
->getQuery()
->getSingleResult();
$colleges = LffCollege::find();
// var_dump($colleges->toArray());die;
// var_dump($courseInfo->toArray());die;
$this->view->courseInfo = $courseInfo;
// var_dump($courseInfo->toArray());die;
$this->view->colleges = $colleges;
}
public function saveAction()
{
$course_id = $this->request->getPost('course_id');
$course_code = $this->request->getPost('course_code');
$course_name = $this->request->getPost('course_name');
$course_type = $this->request->getPost('course_type');
$college_id = $this->request->getPost('college_id');
$credit = $this->request->getPost('credit');
$class_hour = $this->request->getPost('class_hour');
$teacher_name = $this->request->getPost('teacher_name');
$description = $this->request->getPost('description');
$teacher = $this->modelsManager->createBuilder()
->columns([
't.teacher_id',
'u.username as teacher_name',
'c.college_id',
'c.college_name'
])
->from(['t' => 'LffTeacher'])
->leftJoin('LffUsers', 't.user_id = u.user_id', 'u')
->leftJoin('LffCollege', 't.college_id = c.college_id', 'c')
->where('u.username = :username:', ['username' => $teacher_name])
->andWhere('c.college_id = :college_id:', ['college_id' => $college_id])
->getQuery()
->getSingleResult();
if($teacher){
$teacher_id = $teacher->teacher_id;
// var_dump($teacher_id);die;
}else{
$this->flashSession->error("该教师不存在");
return $this->response->redirect("lff_course/addCourse");
}
$course = LffCourse::findFirst([
'course_id = :course_id:',
'bind' => [
'course_id' => $course_id
]
]);
$course->course_code = $course_code;
$course->course_name = $course_name;
$course->course_type = $course_type;
$course->college_id = $college_id;
$course->credit = $credit;
$course->class_hour = $class_hour;
$course->teacher_id = $teacher_id;
$course->description = $description;
// var_dump($course->save());die;
if(!$course->save()){
$this->flashSession->error("课程更新失败");
return $this->response->redirect("lff_course/editCourse" . $course_id);
}
$this->flashSession->error("课程更新成功");
return $this->response->redirect("lff_course/courseList");
}
public function deleteCourseAction($id)
{
$course_id = (int)$id;
if($course_id < 0){
$this->flashSession->error("该课程ID无效");
return $this->response->redirect("lff_course/courseList");
}
$course = LffCourse::findFirst([
'course_id = :course_id:',
'bind' => [
'course_id' => $course_id
]
]);
if (!$course) {
$this->flashSession->error("该课程不存在或已被删除");
return $this->response->redirect("lff_course/courseList");
}
if (!$course->delete()) {
$this->flashSession->error("该课程不存在或已被删除");
return $this->response->redirect("lff_course/courseList");
}
$this->flashSession->error("该课程删除成功");
return $this->response->redirect("lff_course/courseList");
}
}
\ No newline at end of file
<?php
declare(strict_types=1);
use Phalcon\Mvc\Model\Criteria;
use Phalcon\Paginator\Adapter\Model;
class LffCustomersController extends ControllerBase
{
/**
* Index action
*/
public function indexAction()
{
$this->view->setVar('lff_customer', new LffCustomers());
}
/**
* Searches for lff_customers
*/
public function searchAction()
{
$numberPage = $this->request->getQuery('page', 'int', 1);
$parameters = Criteria::fromInput($this->di, 'LffCustomers', $_GET)->getParams();
$parameters['order'] = "cst_id";
$paginator = new Model(
[
'model' => 'LffCustomers',
'parameters' => $parameters,
'limit' => 10,
'page' => $numberPage,
]
);
$paginate = $paginator->paginate();
if (0 === $paginate->getTotalItems()) {
$this->flashSessionSession->notice("The search did not find any lff_customers");
$this->dispatcher->forward([
"controller" => "lff_customers",
"action" => "index"
]);
return;
}
$this->view->page = $paginate;
}
/**
* Displays the creation form
*/
public function newAction()
{
$this->view->setVar('lff_customer', new LffCustomers());
}
/**
* Edits a lff_customer
*
* @param string $cst_id
*/
public function editAction($cst_id)
{
if (!$this->request->isPost()) {
$lff_customer = LffCustomers::findFirstBycst_id($cst_id);
if (!$lff_customer) {
$this->flashSession->error("lff_customer was not found");
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
return;
}
$this->view->cst_id = $lff_customer->cst_id;
$this->view->setVar('lff_customer', $lff_customer);
//$assignTagDefaults$
}
}
/**
* Creates a new lff_customer
*/
public function createAction()
{
if (!$this->request->isPost()) {
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
return;
}
$lff_customer = new LffCustomers();
$lff_customer->cst_status_flag = $this->request->getPost("cst_status_flag");
$lff_customer->cst_name_last = $this->request->getPost("cst_name_last");
$lff_customer->cst_name_first = $this->request->getPost("cst_name_first");
// // 获取 POST 请求数据
// $postData = $this->request->getPost();
// // 使用 print_r 输出数据
// echo "<pre>";
// print_r($lff_customer->cstNameLast);
// echo "</pre>";
// die; // 阻止后续代码执行,方便查看输出结果
// $lff_customer->save();
if (!$lff_customer->save()) {
foreach ($lff_customer->getMessages() as $message) {
$this->flashSession->error($message);
}
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'new'
]);
return;
}
$this->flashSession->success("lff_customer was created successfully");
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
}
/**
* Saves a lff_customer edited
*
*/
public function saveAction()
{
if (!$this->request->isPost()) {
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
return;
}
$cst_id = $this->request->getPost("cst_id");
$lff_customer = LffCustomers::findFirstBycst_id($cst_id);
if (!$lff_customer) {
$this->flashSession->error("lff_customer does not exist " . $cst_id);
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
return;
}
$lff_customer->cstStatusFlag = $this->request->getPost("cst_status_flag");
$lff_customer->cstNameLast = $this->request->getPost("cst_name_last");
$lff_customer->cstNameFirst = $this->request->getPost("cst_name_first");
if (!$lff_customer->save()) {
foreach ($lff_customer->getMessages() as $message) {
$this->flashSession->error($message);
}
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'edit',
'params' => [$lff_customer->cst_id]
]);
return;
}
$this->flashSession->success("lff_customer was updated successfully");
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
}
/**
* Deletes a lff_customer
*
* @param string $cst_id
*/
public function deleteAction($cst_id)
{
$lff_customer = LffCustomers::findFirstBycst_id($cst_id);
if (!$lff_customer) {
$this->flashSession->error("lff_customer was not found");
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'index'
]);
return;
}
if (!$lff_customer->delete()) {
foreach ($lff_customer->getMessages() as $message) {
$this->flashSession->error($message);
}
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => 'search'
]);
return;
}
$this->flashSession->success("lff_customer was deleted successfully");
$this->dispatcher->forward([
'controller' => "lff_customers",
'action' => "index"
]);
}
}
<?php
class LffScoreController extends ControllerBase
{
public function indexAction()
{
}
public function getCoursesByTermAction()
{
// echo 111;die;
$teacherId = $this->request->getPost('teacher_id', 'int');
$courses = LffCourse::find([
'conditions' => 'teacher_id = :teacher_id',
'bind' => [
'teacher_id' => $teacherId
]
])->toArray();
return $this->response->setJsonContent([
'code' => 200,
'msg' => $courses ? '查询成功' : '该教师暂无授课课程',
'courses' => $courses
]);
}
public function scoreManageAction()
{
// echo 111;die;
$terms = LffTerm::find();
$this->view->setVar("terms", $terms);
}
}
\ No newline at end of file
<?php
use Phalcon\Mvc\Model\Criteria;
use Phalcon\Paginator\Adapter\Model;
use Phalcon\Mvc\Model\Query\Builder; // 正确引入 Builder 类
use Overtrue\Pinyin\Pinyin;
class LffStudentController extends ControllerBase
{
public function indexAction()
{
}
public function studentListAction()
{
$builder = new Builder();
$builder->from('LffStudent') // 主表:您的教师模型类名
// 关联用户表(假设用户表模型为 LffUser,根据实际情况调整)
->innerjoin('LffUsers', 'LffStudent.user_id = u.user_id', 'u')
// 关联学院表(假设学院表模型为 LffCollege,根据实际情况调整)
->innerjoin('LffCollege', 'LffStudent.college_id = c.college_id', 'c')
->columns([
'u.user_id',
'u.user_account',
'u.username',
'u.is_enable',
'u.is_lock',
'u.create_time',
'LffStudent.student_id',
'LffStudent.admission_year',
'LffStudent.clazz',
'c.college_id',
'c.college_name'
]);
$students = $builder->getQuery()->execute();
$totalCount = $students->count();
$this->view->students = $students;
$this->view->totalCount = $totalCount;
}
public function addStudentAction()
{
$colleges = LffCollege::find([
'order' => 'college_id ASC'
]);
$this->view->colleges = $colleges;
}
/**
* 处理添加学生的表单提交,创建用户和学生记录
*/
public function createAction()
{
try {
$username = $this->request->getPost('username');
$admission_year = date('Y');
$college_id = (int)$this->request->getPost('college_id');
// var_dump($college_id);die;
$clazz = $this->request->getPost('clazz');
$studentCount = LffStudent::count([
"admission_year = :year: AND college_id = :cid:",
"bind" => [
'year' => $admission_year,
'cid' => $college_id
]
]);
// var_dump($studentCount);die;
$sequence = str_pad($studentCount + 1, 3, '0', STR_PAD_LEFT);
$userAccount = $admission_year . $college_id . $sequence;
// echo 111;die;
// var_dump($userAccount);die;
// 3. 插入用户表(login)
$user = new LffUsers();
$user->user_account = $userAccount;
$user->username = $username;
$user->password = password_hash('123456a?', PASSWORD_DEFAULT); // 默认密码加密
$user->identity = 3; // 身份:学生
$user->is_enable = 1; // 启用状态
$user->is_lock = 0;
// var_dump($user->toArray());die;
// var_dump($user->save());
if (!$user->save()) {
$errorMsg = '用户表创建失败:' . implode(';', $user->getMessages());
throw new \Exception($errorMsg);
}
// 4. 插入学生表(lff_student),关联用户ID
$student = new LffStudent();
$student->user_id = $user->user_id;
$student->college_id = $college_id;
$student->admission_year = $admission_year;
$student->clazz = $clazz;
// var_dump($student->toArray());die;
// echo 111;
//
// var_dump($student->save());die;
if (!$student->save()) {
$errorMsg = '学生表创建失败:' . implode(';', $student->getMessages());
// 回滚用户表插入(可选,需开启事务)
$user->delete();
throw new \Exception($errorMsg);
}
// 5. 操作成功
// $this->flashSession->success("学生添加成功!账号:{$userAccount},初始密码:123456a?");
return $this->response->redirect('lff_student/studentList');
} catch (\Exception $e) {
// 捕获异常并返回表单页
$this->flashSession->error($e->getMessage());
return $this->response->redirect('lff_student/addStudent');
}
}
public function editStudentAction($id)
{
$studentInfo = $this->modelsManager->createBuilder()
->columns([
// 1. 用户表字段(别名u)
'u.user_id',
'u.user_account',
'u.username',
'u.identity',
'u.is_enable',
'u.is_lock',
'u.create_time',
'u.last_login_time',
// 2. 教师表字段(别名t)
's.clazz',
's.admission_year',
's.college_id', // 关联学院表的外键,可保留
// 3. 学院表字段(别名c,按需添加)
'c.college_id as college_id', // 学院ID(可与教师表的college_id区分)
'c.college_name', // 学院名称(常用字段示例)
'c.college_code' // 学院编码(其他可能需要的字段)
])
->from(['u' => 'LffUsers']) // 主表:用户表
// 连接教师表(已有的两表连接)
->leftJoin('LffStudent', 'u.user_id = s.user_id', 's') // 注意:原代码中表别名有误,修正为t.user_id = u.user_id
// 新增:连接学院表(通过教师表的college_id关联)
->leftJoin('LffCollege', 's.college_id = c.college_id', 'c')
->where('u.user_id = :id:', ['id' => $id]) // 条件:按用户ID查询
->getQuery()
->getSingleResult();
$collegeList = LffCollege::find([
'order' => 'college_name ASC' // 按学院名称升序排列,方便用户选择
]);
// var_dump($teacherInfo);die;
if (!$studentInfo) {
$this->flashSession->error('学生不存在');
return $this->response->redirect('lff_student/studentList');
}
$this->view->studentInfo = $studentInfo;
$this->view->collegeList = $collegeList;
}
public function saveAction()
{
$userId = $this->request->getPost('user_id');
if ($userId <= 0) {
$this->flashSession->error('无效的学生ID');
return $this->response->redirect('lff_student/studentList');
}
$username = $this->request->getPost('username');
$college_id = $this->request->getPost('college_id');
$clazz = $this->request->getPost('clazz');
$reset_password = $this->request->getPost('reset_password');
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => [
'user_id' => $userId
]
]);
$student = LffStudent::findFirst(['user_id = :uid:', 'bind' => ['uid' => $userId]]);
$college = LffCollege::findFirst(['college_id = :college_id:', 'bind' => ['college_id' => $college_id]]);
if (!$user) {
$this->flashSession->error("该学生不存在");
$this->response->redirect('lff_student/studentList');
}
if (!$college) {
throw new \Exception("所选学院不存在,请选择有效学院");
}
$student->clazz = $clazz;
$student->college_id = $college_id;
if (!$student->save()) {
$studentErrors = implode(',', $student->getMessages());
throw new \Exception("教师信息更新失败:{$studentErrors}");
}
$user->username = $username;
if($reset_password == 1){
$defaultPwd = 'Xs@' . $student->admission_year;
var_dump($defaultPwd);die;
$user->password = $this->security->hash($defaultPwd);
// $user->password = $defaultPwd;
}
if(!$user->save()){
foreach ($user->getMessages() as $message) {
$this->flashSession->error($message->getMessage());
return $this->response->redirect('lff_student/editStudent?id=' . $user->user_id);
}
}
$this->flashSession->success("教师信息修改成功");
return $this->response->redirect('lff_student/studentList');
}
public function deleteStudentAction($id)
{
if (!$this->session->has('auth') || $this->session->get('auth')['identity'] > 1) {
$this->flashSession->error('仅超级管理员可执行删除操作');
return $this->response->redirect('lff_student/studentList');
}
// 2. 验证管理员ID有效性
$userId = (int)$id;
if ($userId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_student/studentList');
}
// 3. 查询当前要删除的管理员信息
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => ['user_id' => $userId]
]);
if (!$user) {
$this->flashSession->error("ID为 {$userId} 的学生不存在");
return $this->response->redirect('lff_student/studentList');
}
$this->db->begin();
try {
$student = LffStudent::findFirst([
'user_id = :uid:',
'bind' => ['uid' => $userId]
]);
if ($student && !$student->delete()) {
throw new \Exception('删除学生扩展信息失败:' . implode(';', $student->getMessages()));
}
if (!$user->delete()) {
throw new \Exception('删除学生账号失败:' . implode(';', $user->getMessages()));
}
// 6. 提交事务
$this->db->commit();
$this->flashSession->success('管理员账号(含关联信息)已成功删除');
} catch (\Exception $e) {
// 7. 失败时回滚事务
$this->db->rollback();
$this->flashSession->error('删除失败:' . $e->getMessage());
}
return $this->response->redirect('lff_student/studentList');
}
public function studentEnableAction($id)
{
$studentId = (int)$id;
if ($studentId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_student/studentList');
}
try {
// 1. 先查询教师表,确认教师记录存在
$teacher = LffStudent::findFirst([
"user_id = :id:",
"bind" => ['id' => $studentId]
]);
if (!$teacher) {
throw new Exception('学生账号不存在或已被删除');
}
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $studentId]
]);
if (!$user) {
throw new Exception('关联的用户信息不存在');
}
if ($user->is_enable == 1) {
$this->flashSession->notice('该学生账号已处于启用状态');
return $this->response->redirect('lff_student/studentList');
}
$user->is_enable = 1;
if (!$user->save()) {
$errorMsg = '启用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('学生账号已成功启用,可正常登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_student/studentList');
}
public function studentDisableAction($id)
{
$studentId = (int)$id;
if ($studentId <= 0) {
$this->flashSession->error('无效的教师ID');
return $this->response->redirect('lff_student/studentList');
}
try {
$teacher = LffStudent::findFirst([
"user_id = :id:",
"bind" => ['id' => $studentId]
]);
if (!$teacher) {
throw new Exception('学生账号不存在或已被删除');
}
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $studentId]
]);
if (!$user) {
throw new Exception('关联的用户信息不存在');
}
// 已禁用则无需操作
if ($user->is_enable == 0) {
$this->flashSession->notice('该学生账号已处于禁用状态');
return $this->response->redirect('lff_student/studentList');
}
// 更新为禁用状态
$user->is_enable = 0;
if (!$user->save()) {
$errorMsg = '禁用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('学生账号已成功禁用,将无法登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_student/studentList');
}
}
\ No newline at end of file
<?php
use Phalcon\Mvc\Model\Criteria;
use Phalcon\Paginator\Adapter\Model;
use Phalcon\Mvc\Model\Query\Builder; // 正确引入 Builder 类
use Overtrue\Pinyin\Pinyin;
class LffTeacherController extends ControllerBase
{
public function indexAction()
{
}
public function teacherListAction()
{
$builder = new Builder();
$builder->from('LffTeacher')
->innerjoin('LffUsers', 'LffTeacher.user_id = u.user_id', 'u')
->innerjoin('LffCollege', 'LffTeacher.college_id = c.college_id', 'c')
->columns([
'u.user_id',
'u.user_account',
'u.username',
'u.is_enable',
'u.is_lock',
'u.create_time',
// 教师表字段(LffTeacher)
'LffTeacher.teacher_id',
'LffTeacher.email',
'LffTeacher.admission_year',
// 学院表字段(LffCollege)
'c.college_id',
'c.college_name'
]);
$teachers = $builder->getQuery()->execute();
$totalCount = $teachers->count();
$this->view->teachers = $teachers;
$this->view->totalCount = $totalCount;
}
public function addTeacherAction()
{
if (!$this->session->has('auth') || $this->session->get('auth')['identity'] > 1) {
$this->flashSession->error('无权限访问,请先登录教学管理员账号');
return $this->response->redirect('login/index');
}
// 2. 查询学院列表(用于下拉选择)
$colleges = $this->modelsManager->createQuery(
"SELECT college_id, college_name FROM LffCollege"
)->execute()->toArray();
$this->view->colleges = $colleges;
}
public function createAction()
{
$username = $this->request->getPost('username');
$college_id = $this->request->getPost('college_id');
$email = $this->request->getPost('email');
$admission_year = $this->request->getPost('admission_year');
$is_enable = $this->request->getPost('is_enable');
$pinyin = new Pinyin();
$pinyin = $pinyin->abbr($username);
$firstPart = implode("", $pinyin->toArray());
// $firstPart = mb_substr(strtolower($username),0,1,'utf-8');
// var_dump($firstPart);die;
do{
$otherPart = rand(1000,9999);
$autoAccount = $firstPart . $otherPart;
$accountExist = $this->modelsManager->createQuery(
"SELECT user_id FROM LffUsers WHERE user_account = :account:"
)->execute(['account' => $autoAccount])->count();
}while($accountExist > 0);
$autoPassword = "Js@" . date("Y");
// var_dump($autoPassword);die;
$this->db->begin();
try {
// 5.1 插入用户表(LffUsers)
$user = new LffUsers();
$user->user_account = $autoAccount;
$user->username = $username;
$user->password = password_hash($autoPassword, PASSWORD_DEFAULT); // 密码加密
$user->is_enable = $is_enable;
// var_dump($user->is_enabled);
$user->is_lock = 0; // 初始不锁定
$user->identity = 2;
// var_dump($user->save());die;
if (!$user->save()) {
throw new \Exception('用户表保存失败:' . implode(',', $user->getMessages()));
}
// 5.2 插入教师表(LffTeacher)
$teacher = new LffTeacher();
$teacher->user_id = $user->user_id; // 关联用户ID
// var_dump($user->user_id);die;
// var_dump($teacher->user_id);die;
$teacher->college_id = $college_id;
// var_dump($teacher->college_id);die;
$teacher->email = $email;
// var_dump($teacher->email);die;
$teacher->admission_year = date('Y');
// var_dump($teacher->admission_year);die;
// var_dump($teacher->save());die;
if (!$teacher->save()) {
throw new \Exception('教师表保存失败:' . implode(',', $teacher->getMessages()));
}
// 提交事务
$this->db->commit();
$this->flashSession->success("教师创建成功!账号:{$autoAccount},初始密码:{$autoPassword}(首次登录需修改)");
return $this->response->redirect('lff_teacher/teacherList');
} catch (\Exception $e) {
// 回滚事务
$this->db->rollback();
echo $e->getMessage();
$this->flashSession->error("创建失败:" . $e->getMessage());
return $this->response->redirect('teacher/add');
}
}
public function editTeacherAction($id)
{
$teacherInfo = $this->modelsManager->createBuilder()
->columns([
// 1. 用户表字段(别名u)
'u.user_id',
'u.user_account',
'u.username',
'u.identity',
'u.is_enable',
'u.is_lock',
'u.create_time',
'u.last_login_time',
// 2. 教师表字段(别名t)
't.email',
't.admission_year',
't.college_id', // 关联学院表的外键,可保留
// 3. 学院表字段(别名c,按需添加)
'c.college_id as college_id', // 学院ID(可与教师表的college_id区分)
'c.college_name', // 学院名称(常用字段示例)
'c.college_code' // 学院编码(其他可能需要的字段)
])
->from(['u' => 'LffUsers']) // 主表:用户表
// 连接教师表(已有的两表连接)
->leftJoin('LffTeacher', 'u.user_id = t.user_id', 't') // 注意:原代码中表别名有误,修正为t.user_id = u.user_id
// 新增:连接学院表(通过教师表的college_id关联)
->leftJoin('LffCollege', 't.college_id = c.college_id', 'c')
->where('u.user_id = :id:', ['id' => $id]) // 条件:按用户ID查询
->getQuery()
->getSingleResult();
$collegeList = LffCollege::find([
'order' => 'college_name ASC' // 按学院名称升序排列,方便用户选择
]);
// var_dump($teacherInfo);die;
if (!$teacherInfo) {
$this->flashSession->error('管理员不存在');
return $this->response->redirect('lff_teacher/teacherList');
}
$this->view->teacherInfo = $teacherInfo;
$this->view->collegeList = $collegeList;
}
public function saveAction()
{
$userId = $this->request->getPost('user_id');
if ($userId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_teacher/teacherList');
}
$username = $this->request->getPost('username');
$email = $this->request->getPost('email');
$is_enable = $this->request->getPost('is_enable');
$is_lock = $this->request->getPost('is_lock');
$admission_year = $this->request->getPost('admission_year');
$college_id = $this->request->getPost('college_id');
$reset_password = $this->request->getPost('reset_password');
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => [
'user_id' => $userId
]
]);
$teacher = LffTeacher::findFirst(['user_id = :uid:', 'bind' => ['uid' => $userId]]);
$college = LffCollege::findFirst(['college_id = :college_id:', 'bind' => ['college_id' => $college_id]]);
if (!$user) {
$this->flashSession->error("该教师不存在");
$this->response->redirect('lff_teacher/teacherList');
}
if (!$college) {
throw new \Exception("所选学院不存在,请选择有效学院");
}
$teacher->email = $email;
$teacher->college_id = $college_id;
if (!$teacher->save()) {
$teacherErrors = implode(',', $teacher->getMessages());
throw new \Exception("教师信息更新失败:{$teacherErrors}");
}
$user->username = $username;
$user->is_enable = $is_enable;
$user->is_lock = $is_lock;
if($reset_password == 1){
$defaultPwd = "Js@" . date("Y");
$user->password = $this->security->hash($defaultPwd);
}
if(!$user->save()){
foreach ($user->getMessages() as $message) {
$this->flashSession->error($message->getMessage());
return $this->response->redirect('lff_teacher/editTeacher?id=' . $user->id);
}
}
$this->flashSession->success("教师信息修改成功");
return $this->response->redirect('lff_teacher/teacherList');
}
public function deleteTeacherAction($id)
{
if (!$this->session->has('auth') || $this->session->get('auth')['identity'] > 1) {
$this->flashSession->error('仅超级管理员可执行删除操作');
return $this->response->redirect('lff_admin/acaAdminList');
}
// 2. 验证管理员ID有效性
$userId = (int)$id;
if ($userId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_teacher/teacherList');
}
// 3. 查询当前要删除的管理员信息
$user = LffUsers::findFirst([
"user_id = :user_id:",
"bind" => ['user_id' => $userId]
]);
if (!$user) {
$this->flashSession->error("ID为 {$userId} 的教师不存在");
return $this->response->redirect('lff_teacher/teacherList');
}
$this->db->begin();
try {
$teacher = LffTeacher::findFirst([
'user_id = :uid:',
'bind' => ['uid' => $userId]
]);
if ($teacher && !$teacher->delete()) {
throw new \Exception('删除管理员扩展信息失败:' . implode(';', $teacher->getMessages()));
}
if (!$user->delete()) {
throw new \Exception('删除管理员账号失败:' . implode(';', $user->getMessages()));
}
// 6. 提交事务
$this->db->commit();
$this->flashSession->success('管理员账号(含关联信息)已成功删除');
} catch (\Exception $e) {
// 7. 失败时回滚事务
$this->db->rollback();
$this->flashSession->error('删除失败:' . $e->getMessage());
}
return $this->response->redirect('lff_teacher/teacherList');
}
public function teacherEnableAction($id)
{
$teacherId = (int)$id;
if ($teacherId <= 0) {
$this->flashSession->error('无效的管理员ID');
return $this->response->redirect('lff_teacher/teacherList');
}
try {
// 1. 先查询教师表,确认教师记录存在
$teacher = LffTeacher::findFirst([
"user_id = :id:",
"bind" => ['id' => $teacherId]
]);
if (!$teacher) {
throw new Exception('教师账号不存在或已被删除');
}
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $teacherId]
]);
if (!$user) {
throw new Exception('关联的用户信息不存在');
}
if ($user->identity != 2) {
throw new Exception('该用户不是教师,无法操作');
}
if ($user->is_enable == 1) {
$this->flashSession->notice('该教师账号已处于启用状态');
return $this->response->redirect('lff_teacher/teacherList');
}
$user->is_enable = 1;
if (!$user->save()) {
$errorMsg = '启用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('教师账号已成功启用,可正常登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_teacher/teacherList');
}
public function teacherDisableAction($id)
{
$teacherId = (int)$id;
if ($teacherId <= 0) {
$this->flashSession->error('无效的教师ID');
return $this->response->redirect('lff_teacher/teacherList');
}
try {
$user = LffUsers::findFirst([
"user_id = :id:",
"bind" => ['id' => $teacherId]
]);
if (!$user) {
throw new Exception('教师账号不存在或已被删除');
}
// 已禁用则无需操作
if ($user->is_enable == 0) {
$this->flashSession->notice('该教师账号已处于禁用状态');
return $this->response->redirect('lff_teacher/teacherList');
}
// 更新为禁用状态
$user->is_enable = 0;
if (!$user->save()) {
$errorMsg = '禁用失败:' . implode(';', $user->getMessages());
throw new Exception($errorMsg);
}
$this->flashSession->success('教师账号已成功禁用,将无法登录系统');
} catch (Exception $e) {
$this->flashSession->error($e->getMessage());
}
return $this->response->redirect('lff_teacher/teacherList');
}
}
\ No newline at end of file
<?php
use Phalcon\Mvc\Model\Criteria;
use Phalcon\Paginator\Adapter\Model;
use Phalcon\Mvc\Controller;
class LoginController extends ControllerBase
{
public function initialize()
{
}
public function indexAction()
{
}
public function doLoginAction()
{
$auth = $this->session->get('auth');
if (!empty($auth) && isset($auth['identity'])) {
$this->flashSession->error('该用户已登录');
$this->redirectByIdentity($auth['identity']);
}
$user_account = $this->request->getPost('user_account');
$password = $this->request->getPost('password');
$rememberAccount = $this->request->getPost('remember_account', 'int', 0);
$autoLogin = $this->request->getPost('auto_login', 'int', 0);
// var_dump($user_account, $password);
if (empty($user_account) || empty($password)) {
$this->flashSession->error('用户名或者密码不能为空');
// return $this->dispatcher->forward([
// 'controller' => "login",
// 'action' => 'index'
// ]);
return $this->response->redirect('login/index');
}
// $user = LffUsers::findFirst(
// [
// "user_account" => ":user_account:",
// "bind" => ['user_account' => $user_account]
// ]
// );
// 正确的查询方式:条件字符串 + 绑定参数数组
$user = LffUsers::findFirst([
// 条件字符串:使用 :占位符: 格式
"user_account = :user_account:",
// 绑定参数:键名对应占位符(不带冒号)
"bind" => [
'user_account' => $user_account
]
]);
// echo 111;
// die;
if (!$user) {
$this->flashSession->error("该用户不存在");
// return $this->dispatcher->forward([
// 'controller' => "login",
// 'action' => 'index'
// ]);
return $this->response->redirect('login/index');
} elseif ($user->is_enable != 1) {
$this->flashSession->error("账号被禁用");
// return $this->dispatcher->forward([
// 'controller' => "login",
// 'action' => 'index'
// ]);
return $this->response->redirect('login/index');
} elseif ($user->is_lock == 1) {
$this->flashSession->error("账号被锁定");
// return $this->dispatcher->forward([
// 'controller' => "login",
// 'action' => 'index'
// ]);
return $this->response->redirect('login/index');
}
// echo 111;
// die;
if (!$this->security->checkHash($password, $user->password)) {
$this->flashSession->error('密码错误');
// return $this->dispatcher->forward([
// 'controller' => "login",
// 'action' => 'index'
// ]);
return $this->response->redirect('login/index');
}
// 登录成功,保存会话
$this->session->set('auth', [
'user_id' => $user->user_id,
'user_account' => $user->user_account,
'identity' => $user->identity
]);
// if ($rememberAccount) {
// $this->cookies->set(
// 'remember_account',
// $user_account,
// time() + 7 * 24 * 3600
// );
// } else {
// $this->cookies->get('remember_account')->delete();
// }
//
// if ($autoLogin) {
// $token = $this->security->getRandom()->base64Safe(32);
// $this->cookies->set(
// 'auto_login_token',
// $token,
// time() + 24 * 3600
// );
// $user->auto_login_token = $token;
// $user->token_expire_time = date('Y-m-d H:i:s', time() + 24 * 3600);
// $user->save();
// } else {
// // 未勾选则清除自动登录Cookie和数据库令牌
// $this->cookies->get('auto_login_token')->delete();
// $user->auto_login_token = '';
// $user->token_expire_time = null;
// $user->save();
// }
$this->flashSession->success('登录成功');
switch ($user->identity) {
case 0:
case 1:
// return $this->dispatcher->forward([
// 'controller' => "lff_teacher",
// 'action' => 'index'
// ]);
$this->response->redirect('lff_admin/index');
break;
case 2:
// return $this->dispatcher->forward([
// 'controller' => "lff_student",
// 'action' => 'index'
// ]);
$this->response->redirect('lff_score/scoreManage');
break;
default:
$this->response->redirect('lff_student/index');
break;
}
}
public function redirectByIdentity($identity)
{
switch ($identity) {
case 0:
case 1:
return $this->response->redirect('lff_admin/index');
case 2:
return $this->response->redirect('lff_score/scoreManage');
default:
return $this->response->redirect('lff_student/index');
}
}
public function logoutAction()
{
$this->session->remove('auth');
if(!$this->session->has('auth')){
$this->flashSession->success("用户已退出");
return $this->response->redirect('login/index');
}
}
}
\ No newline at end of file
<?php
class LffAdmin extends \Phalcon\Mvc\Model
{
public $admin_id;
public $user_id;
public $admin_email;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_admin");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffCollege extends \Phalcon\Mvc\Model
{
public $college_id;
public $college_name;
public $college_code;
public $create_time;
public $update_time;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_college");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffCourse extends \Phalcon\Mvc\Model
{
public $course_id;
public $course_code;
public $college_id;
public $course_name;
public $course_type;
public $teacher_id;
public $credit;
public $class_hour;
public $description;
public $create_time;
public $update_time;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_course");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffCustomers extends \Phalcon\Mvc\Model
{
/**
*
* @var integer
*/
public $cst_id;
/**
*
* @var integer
*/
public $cst_status_flag;
/**
*
* @var string
*/
public $cst_name_last;
/**
*
* @var string
*/
public $cst_name_first;
/**
* Initialize method for model.
*/
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_customers");
}
/**
* Allows to query a set of records that match the specified conditions
*
* @param mixed $parameters
* @return LffCustomers[]|LffCustomers|\Phalcon\Mvc\Model\ResultSetInterface
*/
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
/**
* Allows to query the first record that match the specified conditions
*
* @param mixed $parameters
* @return LffCustomers|\Phalcon\Mvc\Model\ResultInterface|\Phalcon\Mvc\ModelInterface|null
*/
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
<?php
class LffOrderItems extends \Phalcon\Mvc\Model
{
/**
*
* @var integer
*/
public $item_id;
/**
*
* @var integer
*/
public $order_id;
/**
*
* @var integer
*/
public $prod_id;
/**
*
* @var integer
*/
public $item_quantity;
/**
*
* @var double
*/
public $item_price;
/**
*
* @var double
*/
public $item_total;
/**
* Initialize method for model.
*/
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_order_items");
}
/**
* Allows to query a set of records that match the specified conditions
*
* @param mixed $parameters
* @return LffOrderItems[]|LffOrderItems|\Phalcon\Mvc\Model\ResultSetInterface
*/
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
/**
* Allows to query the first record that match the specified conditions
*
* @param mixed $parameters
* @return LffOrderItems|\Phalcon\Mvc\Model\ResultInterface|\Phalcon\Mvc\ModelInterface|null
*/
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
<?php
class LffOrders extends \Phalcon\Mvc\Model
{
/**
*
* @var integer
*/
public $order_id;
/**
*
* @var integer
*/
public $cst_id;
/**
*
* @var double
*/
public $order_total;
/**
*
* @var integer
*/
public $order_status;
/**
*
* @var string
*/
public $order_time;
/**
*
* @var string
*/
public $pay_time;
/**
* Initialize method for model.
*/
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_orders");
}
/**
* Allows to query a set of records that match the specified conditions
*
* @param mixed $parameters
* @return LffOrders[]|LffOrders|\Phalcon\Mvc\Model\ResultSetInterface
*/
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
/**
* Allows to query the first record that match the specified conditions
*
* @param mixed $parameters
* @return LffOrders|\Phalcon\Mvc\Model\ResultInterface|\Phalcon\Mvc\ModelInterface|null
*/
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
<?php
class LffProducts extends \Phalcon\Mvc\Model
{
/**
*
* @var integer
*/
public $prod_id;
/**
*
* @var string
*/
public $prod_name;
/**
*
* @var double
*/
public $prod_price;
/**
*
* @var integer
*/
public $prod_stock;
/**
*
* @var integer
*/
public $prod_status;
/**
*
* @var string
*/
public $created_at;
/**
*
* @var string
*/
public $updated_at;
/**
* Initialize method for model.
*/
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_products");
}
/**
* Allows to query a set of records that match the specified conditions
*
* @param mixed $parameters
* @return LffProducts[]|LffProducts|\Phalcon\Mvc\Model\ResultSetInterface
*/
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
/**
* Allows to query the first record that match the specified conditions
*
* @param mixed $parameters
* @return LffProducts|\Phalcon\Mvc\Model\ResultInterface|\Phalcon\Mvc\ModelInterface|null
*/
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
<?php
class LffScore extends \Phalcon\Mvc\Model
{
public $score_id;
public $student_id;
public $course_id;
public $term_id;
public $create_by;
public $score;
public $create_time;
public $update_time;
public function initialize()
{
$this->setSchema('develop_local');
$this->setSource('lff_score');
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findfirst($parameters);
}
}
\ No newline at end of file
<?php
class LffStudent extends \Phalcon\Mvc\Model
{
public $student_id;
public $user_id;
public $college_id;
public $clazz;
public $admission_year;
public $create_time;
public $update_time;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_student");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffTeacher extends \Phalcon\Mvc\Model
{
public $teacher_id;
public $user_id;
public $college_id;
public $email;
public $admission_year;
public $create_time;
public $update_time;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_teacher");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffTerm extends \Phalcon\Mvc\Model
{
public $term_id;
public $term_name;
public $start_date;
public $end_date;
public $is_current;
public $create_time;
public function initialize()
{
$this->setSchema('develop_local');
$this->setSource('lff_term');
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<?php
class LffUsers extends \Phalcon\Mvc\Model
{
public $user_id;
public $user_account;
public $username;
public $password;
public $identity;
public $is_enable;
public $is_lock;
public $create_time;
public $last_login_time;
public function initialize()
{
$this->setSchema("develop_local");
$this->setSource("lff_users");
}
public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
{
return parent::find($parameters);
}
public static function findFirst($parameters = null): ?\Phalcon\Mvc\ModelInterface
{
return parent::findFirst($parameters);
}
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Phalcon Framework</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0-alpha1/css/bootstrap.min.css" integrity="sha512-72OVeAaPeV8n3BdZj7hOkaPSEk/uwpDkaGyP4W2jSzAC8tfiO4LMEDWoL3uFp5mcZu+8Eehb4GhZWFwvrss69Q==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="shortcut icon" type="image/x-icon" href="<?php echo $this->url->get('img/favicon.ico')?>"/>
</head>
<body>
<div class="container">
<?php echo $this->getContent(); ?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.6/umd/popper.min.js" integrity="sha512-6UofPqm0QupIL0kzS/UIzekR73/luZdC6i/kXDbWnLOJoqwklBK6519iUnShaYceJ0y4FaiPtX/hRnV/X/xlUQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0-alpha1/js/bootstrap.min.js" integrity="sha512-eHx4nbBTkIr2i0m9SANm/cczPESd0DUEcfl84JpIuutE6oDxPhXvskMR08Wmvmfx5wUpVjlWdi82G5YLvqqJdA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</body>
</html>
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h1>教学管理系统 - 管理员后台</h1>
<hr>
<!-- 用户信息 -->
<div>
<?php
$auth = $this->session->get('auth');
$identity = $auth['identity'] ?? -1;
?>
<p>当前登录:<?php echo $auth['user_account'] ?? '未知用户' ?></p>
<a href="<?php echo $this->url->get('login/logout') ?>">退出登录</a>
</div>
<hr>
<div>
<!-- 【用户管理】:超级管理员可见全部,普通管理员可见部分 -->
<?php if ($identity === 0 || $identity === 1): ?>
<div>
<p>【用户管理】</p>
<ul>
<!-- 仅超级管理员可见“教学管理员账号管理” -->
<?php if ($identity === 0): ?>
<li><a href="<?php echo $this->url->get('lff_admin/acaAdminList') ?>">教学管理员账号管理</a></li>
<?php endif; ?>
<!-- 超级管理员和普通管理员都可见以下两项 -->
<li><a href="<?php echo $this->url->get('lff_teacher/teacherList') ?>">教师信息管理</a></li>
<li><a href="<?php echo $this->url->get('lff_student/studentList') ?>">学生信息管理</a></li>
</ul>
</div>
<?php endif; ?>
<!-- 【课程管理】:超级管理员和普通管理员都可见 -->
<?php if ($identity === 0 || $identity === 1): ?>
<div>
<p>【课程管理】</p>
<ul>
<li><a href="<?php echo $this->url->get('lff_course/courseList') ?>">课程列表</a></li>
</ul>
</div>
<?php endif; ?>
<!-- 【学院管理】:超级管理员和普通管理员都可见 -->
<?php if ($identity === 0 || $identity === 1): ?>
<div>
<p>【学院管理】</p>
<ul>
<li><a href="<?php echo $this->url->get('lff_college/collegeList') ?>">学院列表</a></li>
</ul>
</div>
<?php endif; ?>
<!-- 【成绩管理】:仅教师(identity=2)可见 -->
<?php if ($identity === 2): ?>
<div>
<p>【成绩管理】</p>
<ul>
<li><a href="<?php echo $this->url->get('lff_score/scoreManage') ?>">成绩录入</a></li>
<li><a href="<?php echo $this->url->get('lff_score/') ?>">成绩展示</a></li>
</ul>
</div>
<?php endif; ?>
<!-- 【个人成绩管理】:仅学生(identity=3)可见 -->
<?php if ($identity === 3): ?>
<div>
<p>【个人成绩管理】</p>
<ul>
<li><a href="<?php echo $this->url->get('') ?>">成绩查看</a></li>
</ul>
</div>
<?php endif; ?>
</div>
\ No newline at end of file
<div class="center-block">
<?php echo $this->getContent(); ?>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="admin-management">
<h2>教学管理员账号管理</h2>
<p><?php echo isset($totalCount) ? $totalCount : 0 ?> 位教师</p>
<!-- 新增教师按钮 -->
<a href="<?php echo $this->url->get('lff_admin/addAdmin') ?>" class="btn-add">
<button>新增教学管理员</button>
</a>
<!-- 教师列表表格 -->
<table class="teacher-table" border="1" cellpadding="8" cellspacing="0" width="100%">
<tr>
<th>ID</th>
<th>账号</th>
<th>姓名</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
<?php if (!empty($teachers)) { ?>
<?php foreach ($teachers as $teacher) { ?>
<tr>
<td><?php echo htmlspecialchars($teacher->user_id ?? '') ?></td>
<td><?php echo htmlspecialchars($teacher->user_account ?? '') ?></td>
<td><?php echo htmlspecialchars($teacher->username ?? '') ?></td>
<td>
<?php if (isset($teacher->is_enable) && $teacher->is_enable == 1) { ?>
<span class="status-normal" style="color: green;">正常</span>
<?php } else { ?>
<span class="status-disabled" style="color: red;">禁用</span>
<?php } ?>
</td>
<td><?php if (isset($teacher->is_lock) && $teacher->is_lock == 1) { ?>
<span class="status-normal" style="color: red;">锁定</span>
<?php } else { ?>
<span class="status-disabled" style="color: green;">未锁定</span>
<?php } ?>
</td>
<td><?php echo htmlspecialchars($teacher->create_time ?? '') ?></td>
<td class="operation-buttons">
<!-- 编辑按钮 -->
<a href="<?php echo $this->url->get("lff_admin/editAdmin/" . $teacher->user_id) ?>">
编辑
</a>
|
<!-- 启用/禁用按钮 -->
<?php if (isset($teacher->is_enable) && $teacher->is_enable == 1) { ?>
<a href="<?php echo $this->url->get('lff_admin/adminDisable/' . $teacher->user_id) ?>"
onclick="return confirm('确定要禁用该管理员吗?')">
禁用
</a>
<?php } else { ?>
<a href="<?php echo $this->url->get('lff_admin/adminEnable/'. $teacher->user_id ) ?>"
onclick="return confirm('确定要启用该管理员吗?')">
启用
</a>
<?php } ?>
<!-- 新增删除按钮 -->
<a href="<?php echo $this->url->get('lff_admin/deleteAdmin/' . ($teacher->user_id ?? 0)) ?>"
onclick="return confirm('确定要删除该管理员吗?删除后数据不可恢复!')"
style="color: #dc3545;">
删除
</a>
</td>
</tr>
<?php } ?>
<?php } else { ?>
<tr>
<td colspan="6" align="center">暂无教师数据</td>
</tr>
<?php } ?>
</table>
<!-- 移除分页控件 -->
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="admin-add-container">
<h2>添加管理员</h2>
<form action="<?php echo $this->url->get('lff_admin/create') ?>" method="post" class="admin-add-form">
<!-- 1. 管理员账号(存储在users表) -->
<div class="form-group">
<label for="user_account">管理员账号 <span class="required">*</span></label>
<input type="text"
id="user_account"
name="user_account"
required
placeholder="请输入6-20位字母/数字组合"
maxlength="20"
minlength="6"
pattern="^[A-Za-z0-9!@#$%^&*()_+\-=\[\]{};':\"
title="账号支持字母、数字和特殊符号(!@#$%^&*)">
</div>
<div class="form-group">
<label for="username">管理员姓名 <span class="required">*</span></label>
<input type="text"
id="username"
name="username"
required
placeholder="请输入真实姓名"
maxlength="10"
pattern="^[\u4e00-\u9fa5]+$"
title="姓名仅支持中文">
</div>
<div class="form-group">
<label for="email">联系邮箱 <span class="required">*</span></label>
<input type="email"
id="email"
name="email"
required
placeholder="请输入有效的邮箱地址"
maxlength="50"
title="请输入正确的邮箱格式(例如:admin@example.com)">
</div>
<div class="form-group">
<label for="password">初始密码 <span class="required">*</span></label>
<input type="password"
id="password"
name="password"
required
placeholder="请设置8-16位密码(含字母和数字)"
maxlength="16"
minlength="8"
pattern="^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]+$"
title="密码需包含字母和数字,长度8-16位">
</div>
<div class="form-group">
<label>管理员角色 <span class="required">*</span></label>
<div class="role-radio">
<label>
<input type="radio"
name="identity"
value="0"
checked
required>
超级管理员(拥有全部权限)
</label>
<label>
<input type="radio"
name="identity"
value="1"
required>
教学管理员(仅管理教师/课程相关)
</label>
</div>
</div>
<div class="form-group">
<label>账号状态</label>
<select name="is_enable" id="is_enable">
<option value="1" selected>启用(创建后可直接登录)</option>
<option value="0">禁用(需手动启用后才能登录)</option>
</select>
</div>
<div class="form-group">
<label>锁定状态</label>
<select name="is_locke" id="is_locke">
<option value="0" selected>未锁定(正常使用)</option>
<option value="1">锁定(禁止登录,需解锁后使用)</option>
</select>
</div>
<div class="form-submit">
<button type="submit" class="btn-submit">创建管理员</button>
<a href="<?php echo $this->url->get('lff_admin/acaAdminList') ?>" class="btn-cancel">取消</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="admin-edit-container">
<h2>修改教学管理员信息</h2>
<p class="subtitle">账号为唯一标识,不可修改</p>
<!-- 管理员信息修改表单:使用 adminInfo 的 user_id 传参 -->
<form action="<?php echo $this->url->get("lff_admin/save") ?>" method="post" class="admin-edit-form">
<input type="hidden" name="user_id" value="<?php echo $adminInfo->user_id ?? 0 ?>">
<!-- 1. 管理员账号(不可修改,补充展示,来自 adminInfo 的 users 表字段) -->
<div class="form-group">
<label>管理员账号</label>
<input type="text"
value="<?php echo htmlspecialchars($adminInfo->user_account ?? '') ?>"
id="user_account"
name="user_account"
readonly
class="readonly-input"
title="账号为唯一标识,不可修改">
</div>
<div class="form-group">
<label for="username">管理员姓名 <span class="required">*</span></label>
<input type="text"
id="username"
name="username"
required
value="<?php echo htmlspecialchars($adminInfo->username ?? '') ?>"
placeholder="请输入真实姓名"
maxlength="20"
pattern="^[\u4e00-\u9fa5]+$"
title="姓名仅支持中文"
</div>
<div class="form-group">
<label for="email">联系邮箱 <span class="required">*</span></label>
<input type="email"
id="email"
name="email"
required
value="<?php echo htmlspecialchars($adminInfo->admin_email ?? '') ?>"
placeholder="请输入有效的邮箱地址"
maxlength="50">
</div>
<div class="form-group">
<label for="is_enable">账号状态</label>
<select name="is_enable" id="is_enable" required>
<option value="1" <?php echo (isset($adminInfo->is_enable) && $adminInfo->is_enable == 1) ? 'selected' : '' ?>>
启用(可正常登录)
</option>
<option value="0" <?php echo (isset($adminInfo->is_enable) && $adminInfo->is_enable == 0) ? 'selected' : '' ?>>
禁用(禁止登录)
</option>
</select>
</div>
<div class="form-group">
<label for="is_lock">账号锁定状态</label>
<select name="is_lock" id="is_lock" required>
<option value="0" <?php echo (isset($adminInfo->is_lock) && $adminInfo->is_lock == 0) ? 'selected' : '' ?>>
未锁定(正常使用)
</option>
<option value="1" <?php echo (isset($adminInfo->is_lock) && $adminInfo->is_lock == 1) ? 'selected' : '' ?>>
已锁定(禁止登录,需解锁后使用)
</option>
</select>
</div>
<div class="password-reset-section">
<h3>密码重置</h3>
<p class="reset-hint">勾选后将密码重置为系统默认初始密码(默认密码:123456a?),需重新告知管理员</p>
<div class="form-group">
<label>
<input type="checkbox" name="reset_password" id="reset_password" value="1">
确认重置密码
</label>
</div>
</div>
<!-- 6. 操作按钮 -->
<div class="form-actions">
<button type="submit" class="btn-save">保存修改</button>
<a href="<?php echo $this->url->get('lff_admin/adminList') ?>" class="btn-cancel">取消</a>
</div>
</form>
</div>
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h3>新增学院信息</h3>
<div class="card-body">
<form id="addCollegeForm" action="<?= $this->url->get('lff_college/create') ?>" method="post">
<div class="form-group">
<label for="college_code" class="form-label">
学院编号 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="college_code" name="college_code"
placeholder="请输入2位学院编号(如01、02)" maxlength="2" required
value="<?= htmlspecialchars($this->data['college_code'] ?? '') ?>">
<div class="form-text">限2位数字,作为学院的唯一标识编码(如01代表文学院)</div>
<?php if (isset($this->errors['college_code'])): ?>
<div class="error-message"><?= $this->errors['college_code'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="college_name" class="form-label">
学院名称 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="college_name" name="college_name"
placeholder="请输入学院名称" maxlength="50" required
value="<?= htmlspecialchars($this->data['college_name'] ?? '') ?>">
<div class="form-text">限50字以内,填写标准学院名称(如文学院、计算机学院)</div>
<?php if (isset($this->errors['college_name'])): ?>
<div class="error-message"><?= $this->errors['college_name'] ?></div>
<?php endif; ?>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">提交保存</button>
<a href="<?= $this->url->get('lff_college/collegeList') ?>" class="btn btn-secondary ms-2">取消返回</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h2>学院信息管理</h2>
<a href="<?php echo $this->url->get('lff_college/addCollege') ?>" class="btn btn-primary mb-3">添加学院</a>
<form action="<?php echo $this->url->get('lff_college/collegeList') ?>" method="get" class="search-form row">
<div class="col-10">
<input type="text" name="keyword" class="form-control"
placeholder="输入学院编号或名称查询"
value="<?php echo htmlspecialchars($keyword ?? '') ?>">
</div>
<div class="col-2">
<button type="submit" class="btn btn-secondary w-100">查询</button>
</div>
</form>
<table class="table table-striped table-bordered">
<thead class="table-light">
<tr>
<th>学院编号</th>
<th>学院名称</th>
<th>创建时间</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (isset($colleges) && $colleges->count() > 0): ?>
<?php foreach ($colleges as $college): ?>
<tr>
<td><?php echo htmlspecialchars($college->college_id) ?></td>
<td><?php echo htmlspecialchars($college->college_name) ?></td>
<td><?php echo htmlspecialchars($college->create_time) ?></td>
<td><?php echo htmlspecialchars($college->update_time) ?></td>
<td>
<!-- 修改按钮 -->
<a href="<?php echo $this->url->get("lff_college/editCollege/" . $college->college_id) ?>"
class="btn btn-sm btn-outline-primary operation-btn">修改</a>
<!-- 删除按钮(修正学院ID和提示文本) -->
<a href="<?php echo $this->url->get("lff_college/deleteCollege/" . $college->college_id) ?>"
onclick="return confirm('确定要删除该学院吗?删除后数据不可恢复!')"
class="btn btn-sm btn-outline-danger operation-btn">
删除
</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center py-3">暂无学院数据</td>
</tr>
<?php endif; ?>
</tbody>
</table>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h3>修改学院信息</h3>
<div class="card-body">
<form action="<?php echo $this->url->get('lff_college/save') ?>" method="post" id="editForm">
<input type="hidden" name="college_id"
value="<?php echo htmlspecialchars($collegeInfo->college_id ?? '') ?>">
<div class="form-group">
<label for="collegeId" class="form-label">学院编号</label>
<input type="text" class="form-control" id="collegeId"
value="<?php echo htmlspecialchars($collegeInfo->college_id ?? '') ?>"
readonly
style="background-color: #f8f9fa; cursor: not-allowed;">
<div class="form-text">学院编号为唯一标识,不可修改</div>
</div>
<div class="form-group">
<label for="collegeName" class="form-label">
学院名称 <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="collegeName"
name="college_name"
value="<?php echo htmlspecialchars($collegeInfo->college_name ?? '') ?>"
required
maxlength="50"
placeholder="请输入学院名称(如:计算机科学与技术学院)">
<div class="invalid-feedback">学院名称为必填项,最多50个字符</div>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">保存修改</button>
<a href="<?php echo $this->url->get('lff_college/collegeList') ?>"
class="btn btn-secondary ms-2">取消</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h3>添加课程信息</h3>
<div class="card-body">
<form id="addCourseForm" action="<?php echo $this->url->get('lff_course/create') ?>" method="post">
<div class="form-group">
<label for="course_id" class="form-label">
课程编号 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="course_code" name="course_code"
placeholder="格式:2位学院编号+3位课程类别码+3位序号(如01001001)"
maxlength="8" required
value="<?php echo htmlspecialchars($this->data['course_code'] ?? '') ?>">
<div class="form-text">
示例:01(学院编号)001(课程类别码)001(序号)→ 01001001,总长度8位
</div>
<?php if (isset($this->errors['course_code'])): ?>
<div class="error-message show"><?php echo $this->errors['course_code'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="course_name" class="form-label">
课程名称 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="course_name" name="course_name"
placeholder="请输入课程名称" maxlength="50" required
value="<?php echo htmlspecialchars($this->data['course_name'] ?? '') ?>">
<div class="form-text">限50字以内,禁止包含特殊符号(如@#$%^&*等)</div>
<?php if (isset($this->errors['course_name'])): ?>
<div class="error-message show"><?php echo $this->errors['course_name'] ?></div>
<?php endif; ?>
</div>
<!-- 课程类型 -->
<div class="form-group">
<label for="course_type" class="form-label">
课程类型 <span class="required-mark">*</span>
</label>
<select class="form-select" id="course_type" name="course_type" required>
<option value="">请选择课程类型</option>
<option value="1" <?php echo isset($this->data['course_type']) && $this->data['course_type'] == 'required' ? 'selected' : '' ?>>必修课</option>
<option value="2" <?php echo isset($this->data['course_type']) && $this->data['course_type'] == 'elective' ? 'selected' : '' ?>>选修课</option>
<option value="3" <?php echo isset($this->data['course_type']) && $this->data['course_type'] == 'practice' ? 'selected' : '' ?>>实践课</option>
</select>
<?php if (isset($this->errors['course_type'])): ?>
<div class="error-message show"><?php echo $this->errors['course_type'] ?></div>
<?php endif; ?>
</div>
<!-- 所属学院 -->
<div class="form-group">
<label for="college_id" class="form-label">
所属学院 <span class="required-mark">*</span>
</label>
<select class="form-select" id="college_id" name="college_id" required>
<option value="">请选择所属学院</option>
<?php if (isset($colleges) && $colleges->count() > 0): ?>
<?php foreach ($colleges as $college): ?>
<option value="<?php echo htmlspecialchars($college->college_id ?? '') ?>"
<?php echo isset($this->data['college_id']) && $this->data['college_id'] == $college->college_id ? 'selected' : '' ?>>
<?php echo htmlspecialchars($college->college_name ?? '') ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
<?php if (isset($this->errors['college_id'])): ?>
<div class="error-message show"><?php echo $this->errors['college_id'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="credit" class="form-label">
学分 <span class="required-mark">*</span>
</label>
<input type="number" class="form-control" id="credit" name="credit"
placeholder="请输入学分" min="0.5" max="6.0" step="0.5" required
value="<?php echo htmlspecialchars($this->data['credit'] ?? '') ?>">
<div class="form-text">数值范围0.5-6.0,支持0.5为步长(如0.5、1.0、1.5...)</div>
<?php if (isset($this->errors['credit'])): ?>
<div class="error-message show"><?php echo $this->errors['credit'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="class_hour" class="form-label">
学时 <span class="required-mark">*</span>
</label>
<input type="number" class="form-control" id="class_hour" name="class_hour"
placeholder="请输入学时" min="16" max="128" step="1" required
value="<?php echo htmlspecialchars($this->data['class_hour'] ?? '') ?>">
<div class="form-text">整数范围16-128(如16、32、48...)</div>
<?php if (isset($this->errors['class_hour'])): ?>
<div class="error-message show"><?php echo $this->errors['class_hour'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="teacher_name" class="form-label">
授课教师 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="teacher_name" name="teacher_name"
placeholder="请输入授课教师姓名(需与系统中教师姓名一致)" maxlength="50" required
value="<?php echo htmlspecialchars($this->data['teacher_name'] ?? '') ?>">
<div class="form-text">请输入真实姓名,系统将自动匹配教师信息</div>
<?php if (isset($this->errors['teacher_name'])): ?>
<div class="error-message show"><?php echo $this->errors['teacher_name'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="description" class="form-label">课程描述</label>
<textarea class="form-control" id="description" name="description"
placeholder="请输入课程描述(可选)" maxlength="200" rows="3"><?php echo htmlspecialchars($this->data['description'] ?? '') ?></textarea>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">提交课程</button>
<a href="<?php echo $this->url->get('lff_course/courseList') ?>" class="btn btn-secondary ms-2">取消</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h2>课程管理</h2>
<a href="<?php echo $this->url->get('lff_course/addCourse') ?>" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> 添加课程
</a>
<form action="<?php echo $this->url->get('lff_course/courseList') ?>" method="get" class="search-form mb-4">
<div class="input-group">
<input type="text" name="keyword" class="form-control"
placeholder="输入课程编号/名称搜索"
value="<?php echo htmlspecialchars($this->keyword ?? '') ?>">
<button type="submit" class="btn btn-secondary">搜索</button>
<?php if (!empty($this->keyword)): ?>
<a href="<?php echo $this->url->get('lff_course/courseList') ?>" class="btn btn-outline-secondary">重置</a>
<?php endif; ?>
</div>
</form>
<table class="table table-striped table-bordered">
<thead class="table-light">
<tr>
<th>课程编号</th>
<th>课程名称</th>
<th>课程类型</th>
<th>所属学院</th>
<th>学分</th>
<th>学时</th>
<th>授课教师</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (isset($courseInfo) && $courseInfo->count() > 0): ?>
<?php foreach ($courseInfo as $course): ?>
<tr>
<td><?php echo htmlspecialchars($course->course_id) ?></td>
<td><?php echo htmlspecialchars($course->course_name) ?></td>
<td>
<?php
// 转换课程类型为中文显示
$typeMap = [
'1' => '必修课',
'2' => '选修课',
'3' => '实践课'
];
echo $typeMap[$course->course_type] ?? $course->course_type;
?>
</td>
<td><?php echo htmlspecialchars($course->college_name) ?></td>
<td><?php echo htmlspecialchars($course->credit) ?></td>
<td><?php echo htmlspecialchars($course->class_hour) ?></td>
<td><?php echo htmlspecialchars($course->teacher_name) ?></td>
<td>
<div class="operation-btns">
<a href="<?php echo $this->url->get("lff_course/editCourse/" . $course->course_id ?? 0) ?>"
class="btn btn-sm btn-outline-primary">修改</a>
<a href="<?php echo $this->url->get("lff_course/deleteCourse/" . $course->course_id ?? 0) ?>"
onclick="return confirm('确定删除课程【<?php echo htmlspecialchars($course->course_name) ?>】吗?删除后不可恢复!')"
class="btn btn-sm btn-outline-danger">删除</a>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="9" class="text-center py-4">
<div>暂无课程数据</div>
<a href="<?php echo $this->url->get('admin/addCourse') ?>" class="text-primary">点击添加课程</a>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
\ No newline at end of file
<?php if ($this->flashSession->output()) ?>
<h3>编辑课程信息</h3>
<form id="editCourseForm" action="<?= $this->url->get('lff_course/save') ?>" method="post">
<input type="hidden" name="course_id" value="<?= htmlspecialchars($courseInfo->course_id ?? '') ?>">
<input type="hidden" name="course_code" value="<?= htmlspecialchars($courseInfo->course_code ?? '') ?>">
<div class="form-group">
<label for="course_id" class="form-label">
课程编号 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="course_id"
value="<?= htmlspecialchars($courseInfo->course_id ?? '') ?>"
readonly disabled>
<div class="form-text">课程唯一标识,不可修改</div>
</div>
<div class="form-group">
<label for="course_name" class="form-label">
课程名称 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="course_name" name="course_name"
placeholder="请输入课程名称" maxlength="50" required
value="<?= htmlspecialchars($this->data['course_name'] ?? $courseInfo->course_name ?? '') ?>">
<div class="form-text">限50字以内,禁止包含特殊符号(如@#$%^&*等)</div>
</div>
<div class="form-group">
<label for="course_type" class="form-label">
课程类型 <span class="required-mark">*</span>
</label>
<select class="form-select" id="course_type" name="course_type" required>
<option value="">请选择课程类型</option>
<option value="1" <?= (isset($this->data['course_type']) ? $this->data['course_type'] : $courseInfo->course_type ?? '') == '1' ? 'selected' : '' ?>>必修课</option>
<option value="2" <?= (isset($this->data['course_type']) ? $this->data['course_type'] : $courseInfo->course_type ?? '') == '2' ? 'selected' : '' ?>>选修课</option>
<option value="3" <?= (isset($this->data['course_type']) ? $this->data['course_type'] : $courseInfo->course_type ?? '') == '3' ? 'selected' : '' ?>>实践课</option>
</select>
</div>
<div class="form-group">
<label for="college_id" class="form-label">
所属学院 <span class="required-mark">*</span>
</label>
<select class="form-select" id="college_id" name="college_id" required>
<option value="">请选择所属学院</option>
<?php if (isset($colleges) && $colleges->count() > 0): ?>
<?php foreach ($colleges as $college): ?>
<option value="<?= htmlspecialchars($college->college_id ?? '') ?>"
<?= (isset($this->data['college_id']) ? $this->data['college_id'] : $courseInfo->college_id ?? '') == $college->college_id ? 'selected' : '' ?>>
<?= htmlspecialchars($college->college_name ?? '') ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
<?php if (isset($this->errors['college_id'])): ?>
<div class="error-message show"><?= $this->errors['college_id'] ?></div>
<?php endif; ?>
</div>
<!-- 学分 -->
<div class="form-group">
<label for="credit" class="form-label">
学分 <span class="required-mark">*</span>
</label>
<input type="number" class="form-control" id="credit" name="credit"
placeholder="请输入学分" min="0.5" max="6.0" step="0.5" required
value="<?= htmlspecialchars($this->data['credit'] ?? $courseInfo->credit ?? '') ?>">
<div class="form-text">数值范围0.5-6.0,支持0.5为步长(如0.5、1.0、1.5...)</div>
<?php if (isset($this->errors['credit'])): ?>
<div class="error-message show"><?= $this->errors['credit'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="class_hour" class="form-label">
学时 <span class="required-mark">*</span>
</label>
<input type="number" class="form-control" id="class_hour" name="class_hour"
placeholder="请输入学时" min="16" max="128" step="1" required
value="<?= htmlspecialchars($this->data['class_hour'] ??$courseInfo->class_hour ?? '') ?>">
<div class="form-text">整数范围16-128(如16、32、48...)</div>
<?php if (isset($this->errors['class_hour'])): ?>
<div class="error-message show"><?= $this->errors['class_hour'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="teacher_name" class="form-label">
授课教师 <span class="required-mark">*</span>
</label>
<input type="text" class="form-control" id="teacher_name" name="teacher_name"
placeholder="请输入授课教师姓名(需属于所选学院)" maxlength="50" required
value="<?= htmlspecialchars($this->data['teacher_name'] ?? $courseInfo->teacher_name ?? '') ?>">
<div class="form-text">请输入真实姓名,系统将验证该教师是否属于所选学院</div>
<?php if (isset($this->errors['teacher_name'])): ?>
<div class="error-message show"><?= $this->errors['teacher_name'] ?></div>
<?php endif; ?>
</div>
<div class="form-group">
<label for="description" class="form-label">课程描述</label>
<textarea class="form-control" id="description" name="description"
placeholder="请输入课程描述(可选)" maxlength="200" rows="3"><?= htmlspecialchars($this->data['description'] ?? $courseInfo->description ?? '') ?></textarea>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">保存修改</button>
<a href="<?= $this->url->get('lff_course/courseList') ?>" class="btn btn-secondary ms-2">取消返回</a>
</div>
</form>
\ No newline at end of file
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_customers"), "Go Back") ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Edit lff_customers</h1>
</div>
<?php echo $this->getContent(); ?>
<form action="<?= $this->url->get('lff_customers/save') ?>" class="form-horizontal" method="post">
<div class="form-group">
<label for="fieldCstStatusFlag" class="col-sm-2 control-label">Cst Of Status Of Flag</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_status_flag", $lff_customer->cst_status_flag, ["size" => 30, "class" => "form-control", "id" => "fieldCstStatusFlag"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameLast" class="col-sm-2 control-label">Cst Of Name Of Last</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_last", $lff_customer->cst_name_last, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameLast"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameFirst" class="col-sm-2 control-label">Cst Of Name Of First</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_first", $lff_customer->cst_name_first, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameFirst"]) ?>
</div>
</div>
<?php echo $this->tag->inputHidden("id", $lff_customer->cst_id) ?>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("save", "Save", ['class' => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="page-header">
<h1>Search lff_customers</h1>
<p><?php echo $this->tag->a($this->url->get("lff_customers/new"), "Create lff_customers") ?></p>
</div>
<?php echo $this->getContent() ?>
<?php echo $this->flashSession->output() ?>
<form action="<?= $this->url->get('lff_customers/search') ?>" class="form-horizontal" method="get">
<div class="form-group">
<label for="fieldCstId" class="col-sm-2 control-label">Cst</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("cst_id", $lff_customer->cst_id, ["class" => "form-control", "id" => "fieldCstId"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstStatusFlag" class="col-sm-2 control-label">Cst Of Status Of Flag</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_status_flag", $lff_customer->cst_status_flag, ["size" => 30, "class" => "form-control", "id" => "fieldCstStatusFlag"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameLast" class="col-sm-2 control-label">Cst Of Name Of Last</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_last", $lff_customer->cst_name_last, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameLast"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameFirst" class="col-sm-2 control-label">Cst Of Name Of First</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_first", $lff_customer->cst_name_first, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameFirst"]) ?>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("search", "Search", ["class" => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_customers"), "Go Back") ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Create lff_customers</h1>
</div>
<?php echo $this->getContent(); ?>
<form action="<?= $this->url->get('lff_customers/create') ?>" class="form-horizontal" method="post">
<div class="form-group">
<label for="fieldCstStatusFlag" class="col-sm-2 control-label">Cst Of Status Of Flag</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_status_flag", $lff_customer->cst_status_flag, ["size" => 30, "class" => "form-control", "id" => "fieldCstStatusFlag"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameLast" class="col-sm-2 control-label">Cst Of Name Of Last</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_last", $lff_customer->cst_name_last, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameLast"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCstNameFirst" class="col-sm-2 control-label">Cst Of Name Of First</label>
<div class="col-sm-10">
<?= $this->tag->inputText("cst_name_first", $lff_customer->cst_name_first, ["size" => 30, "class" => "form-control", "id" => "fieldCstNameFirst"]) ?>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("save", "Save", ['class' => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_customers/index"), "Go Back"); ?></li>
<li class="next"><?php echo $this->tag->a($this->url->get("lff_customers/new"), "Create"); ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Search result</h1>
</div>
<?php echo $this->getContent(); ?>
<div class="row">
<table class="table table-bordered">
<thead>
<tr>
<th>Cst</th>
<th>Cst Of Status Of Flag</th>
<th>Cst Of Name Of Last</th>
<th>Cst Of Name Of First</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($page->getItems() as $lff_customer): ?>
<tr>
<td><?php echo $lff_customer->cst_id; ?></td>
<td><?php echo $lff_customer->cst_status_flag; ?></td>
<td><?php echo $lff_customer->cst_name_last; ?></td>
<td><?php echo $lff_customer->cst_name_first; ?></td>
<td><?php echo $this->tag->a($this->url->get("lff_customers/edit/" . $lff_customer->cst_id), "Edit"); ?></td>
<td><?php echo $this->tag->a($this->url->get("lff_customers/delete/" . $lff_customer->cst_id), "Delete"); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="row">
<div class="col-sm-1">
<p class="pagination" style="line-height: 1.42857;padding: 6px 12px;">
<?php echo $page->getCurrent(), "/", $page->getTotalItems() ?>
</p>
</div>
<div class="col-sm-11">
<nav>
<ul class="pagination">
<li><?php echo $this->tag->a($this->url->get("lff_customers/search"), "First", ['class' => 'page-link', 'id' => 'first']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_customers/search?page=" . $page->getPrevious()), "Previous", ['class' => 'page-link', 'id' => 'previous']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_customers/search?page=" . $page->getNext()), "Next", ['class' => 'page-link', 'id' => 'next']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_customers/search?page=" . $page->getLast()), "Last", ['class' => 'page-link', 'id' => 'last']) ?></li>
</ul>
</nav>
</div>
</div>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_products"), "Go Back") ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Edit lff_products</h1>
</div>
<?php echo $this->getContent(); ?>
<form action="<?= $this->url->get('lff_products/save') ?>" class="form-horizontal" method="post">
<div class="form-group">
<label for="fieldProdName" class="col-sm-2 control-label">Prod Of Name</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_name", $lff_product->prod_name, ["size" => 30, "class" => "form-control", "id" => "fieldProdName"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdPrice" class="col-sm-2 control-label">Prod Of Price</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_price", $lff_product->prod_price, ["class" => "form-control", "id" => "fieldProdPrice"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStock" class="col-sm-2 control-label">Prod Of Stock</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_stock", $lff_product->prod_stock, ["class" => "form-control", "id" => "fieldProdStock"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStatus" class="col-sm-2 control-label">Prod Of Status</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_status", $lff_product->prod_status, ["size" => 30, "class" => "form-control", "id" => "fieldProdStatus"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCreatedAt" class="col-sm-2 control-label">Created</label>
<div class="col-sm-10">
<?= $this->tag->inputText("created_at", $lff_product->created_at, ["size" => 30, "class" => "form-control", "id" => "fieldCreatedAt"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldUpdatedAt" class="col-sm-2 control-label">Updated</label>
<div class="col-sm-10">
<?= $this->tag->inputText("updated_at", $lff_product->updated_at, ["size" => 30, "class" => "form-control", "id" => "fieldUpdatedAt"]) ?>
</div>
</div>
<?php echo $this->tag->inputHidden("id", $lff_product->prod_id) ?>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("save", "Save", ['class' => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="page-header">
<h1>Search lff_products</h1>
<p><?php echo $this->tag->a($this->url->get("lff_products/new"), "Create lff_products") ?></p>
</div>
<?php echo $this->getContent() ?>
<?php echo $this->flashSession->output() ?>
<form action="<?= $this->url->get('lff_products/search') ?>" class="form-horizontal" method="get">
<div class="form-group">
<label for="fieldProdId" class="col-sm-2 control-label">Prod</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_id", $lff_product->prod_id, ["class" => "form-control", "id" => "fieldProdId"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdName" class="col-sm-2 control-label">Prod Of Name</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_name", $lff_product->prod_name, ["size" => 30, "class" => "form-control", "id" => "fieldProdName"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdPrice" class="col-sm-2 control-label">Prod Of Price</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_price", $lff_product->prod_price, ["class" => "form-control", "id" => "fieldProdPrice"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStock" class="col-sm-2 control-label">Prod Of Stock</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_stock", $lff_product->prod_stock, ["class" => "form-control", "id" => "fieldProdStock"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStatus" class="col-sm-2 control-label">Prod Of Status</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_status", $lff_product->prod_status, ["size" => 30, "class" => "form-control", "id" => "fieldProdStatus"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldCreatedAt" class="col-sm-2 control-label">Created</label>
<div class="col-sm-10">
<?= $this->tag->inputText("created_at", $lff_product->created_at, ["size" => 30, "class" => "form-control", "id" => "fieldCreatedAt"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldUpdatedAt" class="col-sm-2 control-label">Updated</label>
<div class="col-sm-10">
<?= $this->tag->inputText("updated_at", $lff_product->updated_at, ["size" => 30, "class" => "form-control", "id" => "fieldUpdatedAt"]) ?>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("search", "Search", ["class" => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_products"), "Go Back") ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Create lff_products</h1>
</div>
<?php echo $this->getContent(); ?>
<form action="<?= $this->url->get('lff_products/create') ?>" class="form-horizontal" method="post">
<div class="form-group">
<label for="fieldProdName" class="col-sm-2 control-label">Prod Of Name</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_name", $lff_product->prod_name, ["size" => 30, "class" => "form-control", "id" => "fieldProdName"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdPrice" class="col-sm-2 control-label">Prod Of Price</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_price", $lff_product->prod_price, ["class" => "form-control", "id" => "fieldProdPrice"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStock" class="col-sm-2 control-label">Prod Of Stock</label>
<div class="col-sm-10">
<?= $this->tag->inputNumeric("prod_stock", $lff_product->prod_stock, ["class" => "form-control", "id" => "fieldProdStock"]) ?>
</div>
</div>
<div class="form-group">
<label for="fieldProdStatus" class="col-sm-2 control-label">Prod Of Status</label>
<div class="col-sm-10">
<?= $this->tag->inputText("prod_status", $lff_product->prod_status, ["size" => 30, "class" => "form-control", "id" => "fieldProdStatus"]) ?>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<?php echo $this->tag->inputSubmit("save", "Save", ['class' => "btn btn-primary"]) ?>
</div>
</div>
</form>
<?php
/**
* @var \Phalcon\Mvc\View\Engine\Php $this
*/
?>
<div class="row">
<nav>
<ul class="pager">
<li class="previous"><?php echo $this->tag->a($this->url->get("lff_products/index"), "Go Back"); ?></li>
<li class="next"><?php echo $this->tag->a($this->url->get("lff_products/new"), "Create"); ?></li>
</ul>
</nav>
</div>
<div class="page-header">
<h1>Search result</h1>
</div>
<?php echo $this->getContent(); ?>
<div class="row">
<table class="table table-bordered">
<thead>
<tr>
<th>Prod</th>
<th>Prod Of Name</th>
<th>Prod Of Price</th>
<th>Prod Of Stock</th>
<th>Prod Of Status</th>
<th>Created</th>
<th>Updated</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($page->getItems() as $lff_product): ?>
<tr>
<td><?php echo $lff_product->prod_id; ?></td>
<td><?php echo $lff_product->prod_name; ?></td>
<td><?php echo $lff_product->prod_price; ?></td>
<td><?php echo $lff_product->prod_stock; ?></td>
<td><?php echo $lff_product->prod_status; ?></td>
<td><?php echo $lff_product->created_at; ?></td>
<td><?php echo $lff_product->updated_at; ?></td>
<td><?php echo $this->tag->a($this->url->get("lff_products/edit/" . $lff_product->prod_id), "Edit"); ?></td>
<td><?php echo $this->tag->a($this->url->get("lff_products/delete/" . $lff_product->prod_id), "Delete"); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="row">
<div class="col-sm-1">
<p class="pagination" style="line-height: 1.42857;padding: 6px 12px;">
<?php echo $page->getCurrent(), "/", $page->getTotalItems() ?>
</p>
</div>
<div class="col-sm-11">
<nav>
<ul class="pagination">
<li><?php echo $this->tag->a($this->url->get("lff_products/search"), "First", ['class' => 'page-link', 'id' => 'first']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_products/search?page=" . $page->getPrevious()), "Previous", ['class' => 'page-link', 'id' => 'previous']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_products/search?page=" . $page->getNext()), "Next", ['class' => 'page-link', 'id' => 'next']) ?></li>
<li><?php echo $this->tag->a($this->url->get("lff_products/search?page=" . $page->getLast()), "Last", ['class' => 'page-link', 'id' => 'last']) ?></li>
</ul>
</nav>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>成绩管理</h3>
</div>
<div class="card-body">
<form id="scoreManageForm">
<!-- 学期选择 -->
<div class="form-group mb-4">
<label for="term_id" class="form-label">
选择学期 <span class="required-mark">*</span>
</label>
<select class="form-select" id="term_id" name="term_id" required>
<option value="">请选择学期</option>
<?php if (isset($terms) && count($terms) > 0): ?>
<?php foreach ($terms as $term): ?>
<option value="<?= htmlspecialchars($term->term_id) ?>">
<?= htmlspecialchars($term->term_name) ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<!-- 课程选择(初始禁用) -->
<div class="form-group mb-4">
<label for="course_id" class="form-label">
选择课程 <span class="required-mark">*</span>
</label>
<select class="form-select" id="course_id" name="course_id" disabled required>
<option value="">请先选择学期</option>
</select>
</div>
<!-- 操作按钮 -->
<div class="btn-group">
<button type="submit" class="btn btn-primary" id="enterScoreBtn" disabled>
成绩录入
</button>
</div>
</form>
</div>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<form action="<?php echo $this->url->get('lff_student/create') ?>" method="post" class="mt-3">
<div class="mb-3">
<label for="username" class="form-label">学生姓名 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="username" name="username"
required placeholder="请输入真实姓名" pattern="^[\u4e00-\u9fa5]{2,20}$">
</div>
<div class="mb-3">
<label for="college_id" class="form-label">所属学院 <span class="text-danger">*</span></label>
<select class="form-select" id="college_id" name="college_id" required>
<option value="">请选择学院</option>
<?php foreach ($colleges as $college): ?>
<option value="<?php echo $college->college_id ?>">
<?php echo htmlspecialchars($college->college_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="clazz" class="form-label">班级 <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="clazz" name="clazz"
required placeholder="如:计科2401班">
</div>
<button type="submit" class="btn btn-primary">提交</button>
<a href="<?php echo $this->url->get('lff_student/studentList') ?>" class="btn btn-secondary ms-2">取消</a>
</form>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="admin-edit-container">
<h2>修改学生信息</h2>
<form action="<?php echo $this->url->get("lff_student/save") ?>" method="post" class="admin-edit-form">
<input type="hidden" name="user_id" value="<?php echo $studentInfo->user_id ?? 0 ?>">
<div class="form-group">
<label>管理员账号</label>
<input type="text"
value="<?php echo htmlspecialchars($studentInfo->user_account ?? '') ?>"
id="user_account"
name="user_account"
readonly
class="readonly-input"
title="账号为唯一标识,不可修改">
</div>
<div class="form-group">
<label for="username">管理员姓名 <span class="required">*</span></label>
<input type="text"
id="username"
name="username"
required
value="<?php echo htmlspecialchars($studentInfo->username ?? '') ?>"
placeholder="请输入真实姓名"
maxlength="20"
pattern="^[\u4e00-\u9fa5]+$"
title="姓名仅支持中文">
</div>
<div class="form-group">
<label for="college_id">所属学院 <span class="required">*</span></label>
<select name="college_id" id="college_id" required>
<!-- 后端传递的学院列表,循环渲染选项 -->
<?php foreach ($collegeList as $college): ?>
<option value="<?php echo $college->college_id ?>"
<?php echo (isset($studentInfo->college_id) && $studentInfo->college_id == $college->college_id) ? 'selected' : '' ?>>
<?php echo htmlspecialchars($college->college_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="clazz">班级 <span class="required">*</span></label>
<input type="text"
id="clazz"
name="clazz"
required
value="<?php echo htmlspecialchars($studentInfo->clazz ?? '') ?>"
placeholder="请输入班级(如:计科2401班)"
maxlength="50"
title="班级名称支持中文、数字和字符组合">
</div>
<div class="form-group">
<label>入学年份</label>
<input type="text"
value="<?php echo htmlspecialchars($studentInfo->admission_year ?? '') ?>"
readonly
class="readonly-input"
title="入学年份不可修改">
</div>
<div class="password-reset-section">
<h3>密码重置</h3>
<p class="reset-hint">勾选后将密码重置为系统默认初始密码(默认密码:Xs@ + 入学年份),需重新告知学生</p>
<div class="form-group">
<label>
<input type="checkbox" name="reset_password" id="reset_password" value="1">
确认重置密码
</label>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn-save">保存修改</button>
<a href="<?php echo $this->url->get('lff_student/studentList') ?>" class="btn-cancel">取消</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="student-management">
<h2>学生账号管理</h2>
<p><?php echo isset($totalCount) ? $totalCount : 0 ?> 名学生</p>
<a href="<?php echo $this->url->get('lff_student/addStudent') ?>" class="btn-add">
<button>新增学生</button>
</a>
<table class="student-table" border="1" cellpadding="8" cellspacing="0" width="100%">
<tr>
<th>ID</th>
<th>姓名</th>
<th>学院</th>
<th>入学年份</th>
<th>班级</th>
<th>账号状态</th>
<th>锁定状态</th>
<th>操作</th>
</tr>
<?php if (!empty($students)) { ?>
<?php foreach ($students as $student) { ?>
<tr>
<td><?php echo htmlspecialchars($student->user_id ?? '') ?></td>
<td><?php echo htmlspecialchars($student->username ?? '') ?></td>
<td><?php echo htmlspecialchars($student->college_name ?? '未分配') ?></td>
<td><?php echo htmlspecialchars($student->admission_year ?? '未填写') ?></td>
<td><?php echo htmlspecialchars($student->clazz ?? '未分配') ?></td>
<td>
<?php if (isset($student->is_enable) && $student->is_enable == 1) { ?>
<span class="status-normal" style="color: green;">启用</span>
<?php } else { ?>
<span class="status-disabled" style="color: red;">禁用</span>
<?php } ?>
</td>
<td>
<?php if (isset($student->is_lock) && $student->is_lock == 1) { ?>
<span class="status-locked" style="color: red;">已锁定</span>
<?php } else { ?>
<span class="status-unlocked" style="color: green;">未锁定</span>
<?php } ?>
</td>
<td class="operation-buttons">
<!-- 编辑按钮 -->
<a href="<?php echo $this->url->get("lff_student/editStudent/" . $student->user_id) ?>">
编辑
</a>
|
<!-- 启用/禁用按钮 -->
<?php if (isset($student->is_enable) && $student->is_enable == 1) { ?>
<a href="<?php echo $this->url->get('lff_student/studentDisable/' . $student->user_id ) ?>"
onclick="return confirm('确定要禁用该学生账号吗?')">
禁用
</a>
<?php } else { ?>
<a href="<?php echo $this->url->get('lff_student/studentEnable/' . $student->user_id ) ?>"
onclick="return confirm('确定要启用该学生账号吗?')">
启用
</a>
<?php } ?>
|
<!-- 锁定/解锁按钮 -->
<?php if (isset($student->is_lock) && $student->is_lock == 1) { ?>
<a href="<?php echo $this->url->get('lff_student/studentUnlock', ['id' => $student->user_id ?? 0]) ?>"
onclick="return confirm('确定要解锁该学生账号吗?')">
解锁
</a>
<?php } else { ?>
<a href="<?php echo $this->url->get('lff_student/studentLock', ['id' => $student->user_id ?? 0]) ?>"
onclick="return confirm('确定要锁定该学生账号吗?')">
锁定
</a>
<?php } ?>
|
<!-- 删除按钮 -->
<a href="<?php echo $this->url->get('lff_student/deleteStudent/' . ($student->user_id ?? 0)) ?>"
onclick="return confirm('确定要删除该学生吗?删除后数据不可恢复!')"
style="color: #dc3545;">
删除
</a>
</td>
</tr>
<?php } ?>
<?php } else { ?>
<tr>
<td colspan="8" align="center">暂无学生数据</td>
</tr>
<?php } ?>
</table>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h1>教学管理系统 - 添加教师</h1>
<div style="margin-bottom: 20px;">
<?php $auth = $this->session->get('auth'); ?>
<p>当前登录:<?php echo $auth['user_account'] ?? '未知用户' ?></p>
<a href="<?php echo $this->url->get('login/logout') ?>">退出登录</a>
</div>
<form method="post" action="<?php echo $this->url->get('lff_teacher/create') ?>" style="max-width: 800px; margin: 0 auto;">
<table border="0" cellpadding="10" cellspacing="0" style="width: 100%;">
<!-- 教师姓名(必填) -->
<tr>
<td style="width: 150px; text-align: right; font-weight: bold;">教师姓名:</td>
<td>
<input type="text" name="username" required
style="padding: 6px; width: 300px;"
placeholder="请输入教师姓名(必填)">
</td>
</tr>
<tr>
<td style="text-align: right; font-weight: bold;">所属学院:</td>
<td>
<select name="college_id" required
style="padding: 6px; width: 314px;">
<option value="">请选择学院</option>
<?php foreach ($colleges as $college): ?>
<option value="<?php echo $college['college_id'] ?>">
<?php echo $college['college_name'] ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<td style="text-align: right; font-weight: bold;">联系邮箱:</td>
<td>
<input type="email" name="email"
style="padding: 6px; width: 300px;"
placeholder="请输入联系邮箱(选填)">
</td>
</tr>
<tr>
<td style="text-align: right; font-weight: bold;">账号状态:</td>
<td>
<label style="margin-right: 20px;">
<input type="radio" name="is_enable" value="1" checked> 启用
</label>
<label>
<input type="radio" name="is_enable" value="0"> 禁用
</label>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center;">
<button type="submit" style="padding: 8px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
提交添加
</button>
<a href="<?php echo $this->url->get('lff_teacher/teacherList') ?>"
style="margin-left: 20px; padding: 8px 20px; background: #f0f0f0; text-decoration: none; border-radius: 4px;">
返回教师列表
</a>
</td>
</tr>
</table>
</form>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="admin-edit-container">
<h2>修改教师信息</h2>
<p class="subtitle">账号为唯一标识,不可修改</p>
<form action="<?php echo $this->url->get("lff_teacher/save") ?>" method="post" class="admin-edit-form">
<input type="hidden" name="user_id" value="<?php echo $teacherInfo->user_id ?? 0 ?>">
<div class="form-group">
<label>管理员账号</label>
<input type="text"
value="<?php echo htmlspecialchars($teacherInfo->user_account ?? '') ?>"
id="user_account"
name="user_account"
readonly
class="readonly-input"
title="账号为唯一标识,不可修改">
</div>
<div class="form-group">
<label for="username">管理员姓名 <span class="required">*</span></label>
<input type="text"
id="username"
name="username"
required
value="<?php echo htmlspecialchars($teacherInfo->username ?? '') ?>"
placeholder="请输入真实姓名"
maxlength="20"
pattern="^[\u4e00-\u9fa5]+$"
title="姓名仅支持中文">
</div>
<div class="form-group">
<label for="email">联系邮箱 <span class="required">*</span></label>
<input type="email"
id="email"
name="email"
required
value="<?php echo htmlspecialchars($teacherInfo->email ?? '') ?>"
placeholder="请输入有效的邮箱地址"
maxlength="50">
</div>
<!-- 4. 所属学院(可修改,改为下拉框) -->
<div class="form-group">
<label for="college_id">所属学院 <span class="required">*</span></label>
<select name="college_id" id="college_id" required>
<!-- 后端传递的学院列表,循环渲染选项 -->
<?php foreach ($collegeList as $college): ?>
<option value="<?php echo $college->college_id ?>"
<?php echo (isset($teacherInfo->college_id) && $teacherInfo->college_id == $college->college_id) ? 'selected' : '' ?>>
<?php echo htmlspecialchars($college->college_name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- 5. 入学年份(不可修改,保留展示) -->
<div class="form-group">
<label>入学年份</label>
<input type="text"
value="<?php echo htmlspecialchars($teacherInfo->admission_year ?? '') ?>"
readonly
class="readonly-input"
title="入学年份不可修改">
</div>
<!-- 6. 账号状态(可修改:启用/禁用) -->
<div class="form-group">
<label for="is_enable">账号状态</label>
<select name="is_enable" id="is_enable" required>
<option value="1" <?php echo (isset($teacherInfo->is_enable) && $teacherInfo->is_enable == 1) ? 'selected' : '' ?>>
启用(可正常登录)
</option>
<option value="0" <?php echo (isset($teacherInfo->is_enable) && $teacherInfo->is_enable == 0) ? 'selected' : '' ?>>
禁用(禁止登录)
</option>
</select>
</div>
<!-- 7. 账号锁定状态(可修改:未锁定/已锁定) -->
<div class="form-group">
<label for="is_lock">账号锁定状态</label>
<select name="is_lock" id="is_lock" required>
<option value="0" <?php echo (isset($teacherInfo->is_lock) && $teacherInfo->is_lock == 0) ? 'selected' : '' ?>>
未锁定(正常使用)
</option>
<option value="1" <?php echo (isset($teacherInfo->is_lock) && $teacherInfo->is_lock == 1) ? 'selected' : '' ?>>
已锁定(禁止登录,需解锁后使用)
</option>
</select>
</div>
<!-- 8. 密码重置区域(保留原有功能) -->
<div class="password-reset-section">
<h3>密码重置</h3>
<p class="reset-hint">勾选后将密码重置为系统默认初始密码(默认密码:Js@ + 入职年份),需重新告知管理员</p>
<div class="form-group">
<label>
<input type="checkbox" name="reset_password" id="reset_password" value="1">
确认重置密码
</label>
</div>
</div>
<!-- 9. 操作按钮 -->
<div class="form-actions">
<button type="submit" class="btn-save">保存修改</button>
<a href="<?php echo $this->url->get('lff_teacher/teacherList') ?>" class="btn-cancel">取消</a>
</div>
</form>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<div class="teacher-management">
<h2>教师信息管理</h2>
<p><?php echo isset($totalCount) ? $totalCount : 0 ?> 位教师</p>
<a href="<?php echo $this->url->get('lff_teacher/addTeacher') ?>" class="btn-add">
<button>新增教师</button>
</a>
<table class="teacher-table" border="1" cellpadding="8" cellspacing="0" width="100%">
<tr>
<th>姓名</th>
<th>所属学院</th>
<th>邮箱</th>
<th>入学年份</th>
<th>状态</th>
<th>锁定状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
<?php if (!empty($teachers)) { ?>
<?php foreach ($teachers as $teacher) { ?>
<tr>
<td><?php echo htmlspecialchars($teacher->username ?? '') ?></td>
<!-- 新增:所属学院名称 -->
<td><?php echo htmlspecialchars($teacher->college_name ?? '未设置') ?></td>
<!-- 新增:邮箱 -->
<td><?php echo htmlspecialchars($teacher->email ?? '未设置') ?></td>
<!-- 新增:入学年份 -->
<td><?php echo htmlspecialchars($teacher->admission_year ?? '未设置') ?></td>
<!-- 状态列(原状态列调整为启用/禁用状态) -->
<td>
<?php if (isset($teacher->is_enable) && $teacher->is_enable == 1) { ?>
<span class="status-normal" style="color: green;">正常</span>
<?php } else { ?>
<span class="status-disabled" style="color: red;">禁用</span>
<?php } ?>
</td>
<!-- 锁定状态列(拆分原状态列) -->
<td>
<?php if (isset($teacher->is_lock) && $teacher->is_lock == 1) { ?>
<span class="status-normal" style="color: red;">锁定</span>
<?php } else { ?>
<span class="status-disabled" style="color: green;">未锁定</span>
<?php } ?>
</td>
<td><?php echo htmlspecialchars($teacher->create_time ?? '') ?></td>
<td class="operation-buttons">
<!-- 编辑按钮 -->
<a href="<?php echo $this->url->get("lff_teacher/editTeacher/" . $teacher->user_id) ?>">
编辑
</a>
|
<!-- 启用/禁用按钮 -->
<?php if (isset($teacher->is_enable) && $teacher->is_enable == 1) { ?>
<a href="<?php echo $this->url->get('lff_teacher/teacherDisable/' . $teacher->user_id) ?>"
onclick="return confirm('确定要禁用该教师吗?')">
禁用
</a>
<?php } else { ?>
<a href="<?php echo $this->url->get('lff_teacher/teacherEnable/' . $teacher->user_id) ?>"
onclick="return confirm('确定要启用该教师吗?')">
启用
</a>
<?php } ?>
|
<!-- 删除按钮 -->
<a href="<?php echo $this->url->get('lff_teacher/deleteTeacher/' . ($teacher->user_id ?? 0)) ?>"
onclick="return confirm('确定要删除该教师吗?删除后数据不可恢复!')"
style="color: #dc3545;">
删除
</a>
</td>
</tr>
<?php } ?>
<?php } else { ?>
<tr>
<td colspan="10" align="center">暂无教师数据</td>
</tr>
<?php } ?>
</table>
</div>
\ No newline at end of file
<div style="color: red;">
<?php echo $this->flashSession->output() ?>
</div>
<h1>用户登录</h1>
<form action="<?php echo $this->url->get('login/doLogin') ?>" method="post">
<div>
<label>用户名:</label>
<!-- 从Cookie自动填充账号(若存在) -->
<input type="text" name="user_account"
value="<?php echo $this->cookies->has('remember_account') ? $this->cookies->get('remember_account')->getValue() : '' ?>"
required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<label>
<input type="checkbox" name="remember_account" value="1"> 记住账号(7天)
</label>
</div>
<div>
<label>
<input type="checkbox" name="auto_login" value="1"> 自动登录(24小时,个人设备推荐)
</label>
</div>
<div>
<button type="submit">登录</button>
</div>
</form>
\ No newline at end of file
<html><body><h1>Mod-Rewrite is not enabled</h1><p>Please enable rewrite module on your web server to continue</body></html>
\ No newline at end of file
AddDefaultCharset UTF-8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
</IfModule>
\ No newline at end of file
<?php
declare(strict_types=1);
use Phalcon\Di\FactoryDefault;
error_reporting(E_ALL);
ini_set('display_errors', "1"); // 允许在浏览器中显示错误
define('BASE_PATH', dirname(__DIR__));
define('APP_PATH', BASE_PATH . '/app');
try {
/**
* The FactoryDefault Dependency Injector automatically registers
* the services that provide a full stack framework.
*/
$di = new FactoryDefault();
/**
* Read services
*/
include APP_PATH . '/config/services.php';
/**
* Handle routes
*/
include APP_PATH . '/config/router.php';
/**
* Get config service for use in inline setup below
*/
$config = $di->getConfig();
// public/index.php 中添加(通常在文件顶部)
require_once __DIR__ . '/../vendor/autoload.php';
/**
* Include Autoloader
*/
include APP_PATH . '/config/loader.php';
/**
* Handle the request
*/
$application = new \Phalcon\Mvc\Application($di);
echo $application->handle($_SERVER['REQUEST_URI'])->getContent();
} catch (\Exception $e) {
echo $e->getMessage() . '<br>';
echo '<pre>' . $e->getTraceAsString() . '</pre>';
}
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit3b41b5a9e577b9df749d30b01302c2df::getLoader();
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../overtrue/pinyin/bin/pinyin)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/overtrue/pinyin/bin/pinyin');
}
}
return include __DIR__ . '/..'.'/overtrue/pinyin/bin/pinyin';
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}
return $installed;
}
}
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Overtrue\\Pinyin\\' => array($vendorDir . '/overtrue/pinyin/src'),
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit3b41b5a9e577b9df749d30b01302c2df
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit3b41b5a9e577b9df749d30b01302c2df', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit3b41b5a9e577b9df749d30b01302c2df', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit3b41b5a9e577b9df749d30b01302c2df::getInitializer($loader));
$loader->register(true);
return $loader;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit3b41b5a9e577b9df749d30b01302c2df
{
public static $prefixLengthsPsr4 = array (
'O' =>
array (
'Overtrue\\Pinyin\\' => 16,
),
);
public static $prefixDirsPsr4 = array (
'Overtrue\\Pinyin\\' =>
array (
0 => __DIR__ . '/..' . '/overtrue/pinyin/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit3b41b5a9e577b9df749d30b01302c2df::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit3b41b5a9e577b9df749d30b01302c2df::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit3b41b5a9e577b9df749d30b01302c2df::$classMap;
}, null, ClassLoader::class);
}
}
{
"packages": [
{
"name": "overtrue/pinyin",
"version": "6.0.0",
"version_normalized": "6.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/overtrue/pinyin.git",
"reference": "729422bec2c9d3aa1e54e8d65afdab954f52014f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/overtrue/pinyin/zipball/729422bec2c9d3aa1e54e8d65afdab954f52014f",
"reference": "729422bec2c9d3aa1e54e8d65afdab954f52014f",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^3.0",
"laravel/pint": "^1.10",
"nunomaduro/termwind": "^2.0",
"phpunit/phpunit": "^12.0.21"
},
"time": "2025-09-08T10:34:18+00:00",
"bin": [
"bin/pinyin"
],
"type": "library",
"extra": {
"hooks": {
"pre-push": [
"composer pint",
"composer test"
],
"pre-commit": [
"composer pint",
"composer test"
]
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Overtrue\\Pinyin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "overtrue",
"email": "anzhengchao@gmail.com",
"homepage": "http://github.com/overtrue"
}
],
"description": "Chinese to pinyin translator.",
"homepage": "https://github.com/overtrue/pinyin",
"keywords": [
"Chinese",
"Pinyin",
"cn2pinyin"
],
"support": {
"issues": "https://github.com/overtrue/pinyin/issues",
"source": "https://github.com/overtrue/pinyin/tree/6.0.0"
},
"funding": [
{
"url": "https://github.com/overtrue",
"type": "github"
}
],
"install-path": "../overtrue/pinyin"
}
],
"dev": true,
"dev-package-names": []
}
<?php return array(
'root' => array(
'name' => '__root__',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'overtrue/pinyin' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => '729422bec2c9d3aa1e54e8d65afdab954f52014f',
'type' => 'library',
'install_path' => __DIR__ . '/../overtrue/pinyin',
'aliases' => array(),
'dev_requirement' => false,
),
),
);
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
time: "21:00"
open-pull-requests-limit: 10
ignore:
- dependency-name: phpunit/phpunit
versions:
- ">= 8.a, < 9"
name: Test
on: [push, pull_request]
jobs:
phpunit:
name: PHP-${{ matrix.php_version }}-${{ matrix.perfer }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php_version:
- 8.1
- 8.2
- 8.3
- 8.4
- 8.5
perfer:
- stable
- lowest
steps:
- uses: actions/checkout@master
- name: Install Dependencies
run: composer update --prefer-dist --no-interaction --no-suggest --prefer-${{ matrix.perfer }}
- name: Run PHPUnit
run: ./vendor/bin/phpunit
## [6.0.0] - 2025-09-09
### 🚀 Major Features
- **性能优化策略系统**: 全新的转换策略架构,针对不同使用场景提供最优性能
- **内存优化策略** (Memory Optimized): 占用 ~400KB 内存,适合 Web 请求和内存受限环境
- **缓存策略** (Cached): 占用 ~4MB 内存,重复转换性能提升 2-3 倍,适合批处理任务
- **智能策略** (Smart): 占用 600KB-1.5MB 内存,根据文本复杂度自适应加载
- **ConverterFactory**: 新的工厂模式管理转换器实例和策略选择
- **自动策略选择**: 根据运行环境(Web/CLI/内存限制)自动推荐最佳策略
- **性能基准测试工具**: 内置的策略性能对比和监控工具
- **内存监控**: 支持实时监控内存使用情况和性能指标
### 🔧 API Changes
- 新增 `Pinyin::useMemoryOptimized()` - 切换到内存优化策略
- 新增 `Pinyin::useCached()` - 切换到缓存策略
- 新增 `Pinyin::useSmart()` - 切换到智能策略
- 新增 `Pinyin::useAutoStrategy()` - 自动选择最佳策略
- 新增 `Pinyin::clearCache()` - 清理所有转换器缓存
- 新增 `ConverterFactory::make($strategy)` - 创建指定策略的转换器
- 新增 `ConverterFactory::recommend()` - 获取推荐策略
- 新增 `ConverterFactory::getStrategiesInfo()` - 获取所有策略信息
### ⚡ Performance Improvements
- **内存使用优化**: 默认内存占用从 ~4MB 降低到 ~400KB
- **转换速度提升**: 缓存策略下重复转换速度提升 2-3 倍
- **智能加载**: 根据文本复杂度动态调整数据加载策略
- **按需加载**: 内存优化策略仅在需要时加载转换数据
### 📊 Benchmark & Monitoring
- 新增 `php benchmark/run.php` - 运行性能基准测试
- 新增 `php benchmark/compare-strategies.php` - 策略对比测试
- 新增基准测试文档 `docs/benchmark-guide.md`
- 支持内存使用情况实时监控
#### 运行基准测试
```bash
# 运行标准基准测试,显示所有方法的性能表现
php benchmark/run.php
# 详细的策略对比测试,对比三种策略的性能差异
php benchmark/compare-strategies.php
```
基准测试会显示:
- 每种策略的执行时间和内存使用情况
- 不同文本长度下的性能表现
- 策略之间的性能对比和推荐场景
### 🔄 Breaking Changes
- **默认策略变更**: 从全缓存改为内存优化策略,降低默认内存占用
- **转换器架构重构**: 引入策略模式,旧的直接实例化转换器方式仍兼容
- **性能特征变化**: 首次转换可能略慢,但内存占用显著降低
### 🔧 Backward Compatibility
- 完全兼容 5.x API,现有代码无需修改即可使用
- `heteronym()` 方法(5.3.3+ 引入)继续保持兼容
- 所有原有的转换方法 (`sentence`, `phrase`, `chars` 等) 保持不变
### 📚 Documentation
- 更新 README.md 增加性能优化策略说明
- 新增性能对比表格和使用建议
- 新增基准测试指南
- 新增性能优化最佳实践
### 🔧 Development Tools
- 新增性能基准测试脚本
- 新增策略对比工具
- 增强命令行工具支持策略选择
### 🛠️ Migration Guide
从 5.x 升级到 6.0 非常简单,所有现有代码都能正常工作:
#### 无需任何修改(推荐)
```php
// 5.x 和 6.x 都能正常工作
Pinyin::sentence('你好世界');
// 6.x 默认使用内存优化策略,内存占用更低
```
#### 保持 5.x 完全相同的性能特征
```php
// 如果你需要与 5.x 完全相同的高性能(高内存占用)
Pinyin::useCached(); // 一次设置,全局生效
Pinyin::sentence('你好世界');
```
#### 使用新的性能优化特性
```php
// 自动选择最佳策略(推荐用于新项目)
Pinyin::useAutoStrategy();
// 或者根据场景手动选择:
// Web 应用(内存受限)
Pinyin::useMemoryOptimized();
// 批处理任务(性能优先)
Pinyin::useCached();
// 通用场景(平衡)
Pinyin::useSmart();
```
#### 性能监控和优化
```php
// 监控内存使用
$initialMemory = memory_get_usage();
$result = Pinyin::sentence('测试文本');
$memoryUsed = memory_get_usage() - $initialMemory;
echo "内存使用: " . round($memoryUsed / 1024, 2) . " KB";
// 批处理完成后清理缓存
Pinyin::useCached();
// ... 批量处理 ...
Pinyin::clearCache(); // 释放内存
```
### 💡 Performance Comparison
| 策略 | 内存占用 | 首次转换 | 重复转换 | 推荐场景 |
|-----|---------|---------|---------|---------|
| Memory Optimized | ~400KB | 中等 | 中等 | Web 请求、内存受限环境 |
| Cached | ~4MB | 慢 | **最快** (2-3x) | 批处理、长时运行进程 |
| Smart | 600KB-1.5MB | 快 | 快 | 通用场景、自动优化 |
基于 1000 次转换的基准测试结果:
| 文本长度 | Memory Optimized | Cached | Smart |
|---------|-----------------|--------|-------|
| 短文本 (<10字) | 1.2ms | 0.5ms | 0.8ms |
| 中等文本 (10-50字) | 3.5ms | 1.2ms | 2.1ms |
| 长文本 (>100字) | 8.7ms | 3.1ms | 5.2ms |
## [5.3.4] - 2025-03-16
### 🚀 Features
- Resolved #211
### ⚙️ Miscellaneous Tasks
- Format
## [5.3.3] - 2024-08-01
### 🚀 Features
- 使用 heteronym 代替 polyphonic 多音字 #184
- 取首字母时,能否保留完整的英文 #199
### 🐛 Bug Fixes
- 修复 琢 的音频顺序 #207
- 补充案例 #207
- Tests
- Tests
## [5.3.2] - 2024-03-19
### 🚀 Features
- 取首字母时,能否保留完整的英文 #199
## [5.3.1] - 2024-03-19
### 🐛 Bug Fixes
- 「仆区」应该读 pú ōu 而非 pú qū #200
- Tests #200
## [5.3.0] - 2023-10-27
### 🚀 Features
- 添加sentenceFull,支持保留其他字符 (#198)
- Full sentence, #198
## [5.2.2] - 2023-09-27
### ⚙️ Miscellaneous Tasks
- Bin
## [5.2.1] - 2023-06-17
### 🐛 Bug Fixes
- Bin
## [5.2.0] - 2023-06-12
### 🚀 Features
- 增加 Pinyin::polyphonesAsArray. fixed #195
### 📚 Documentation
- 更新文档提示
- 更新文档提示
- 更新文档提示
## [5.1.0] - 2023-04-27
### 💼 Other
- 移除错误语法 (#190)
## [5.0.0] - 2022-07-24
### 🐛 Bug Fixes
- 优化符号匹配规则
## [1.0-beta] - 2014-07-16
The MIT License (MIT)
Copyright (c) 2014 安正超
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
# Pinyin
[![Test](https://github.com/overtrue/pinyin/actions/workflows/test.yml/badge.svg)](https://github.com/overtrue/pinyin/actions/workflows/test.yml)
[![Latest Stable Version](https://poser.pugx.org/overtrue/pinyin/v/stable.svg)](https://packagist.org/packages/overtrue/pinyin) [![Total Downloads](https://poser.pugx.org/overtrue/pinyin/downloads.svg)](https://packagist.org/packages/overtrue/pinyin) [![Latest Unstable Version](https://poser.pugx.org/overtrue/pinyin/v/unstable.svg)](https://packagist.org/packages/overtrue/pinyin) [![License](https://poser.pugx.org/overtrue/pinyin/license.svg)](https://packagist.org/packages/overtrue/pinyin)
:cn: 基于 [mozillazg/pinyin-data](https://github.com/mozillazg/pinyin-data) 词典的中文转拼音工具,更准确的支持多音字的汉字转拼音解决方案。
[喜欢我的项目?点击这里支持我](https://github.com/sponsors/overtrue)
## 特性
- 准确的多音字支持
- 多种拼音风格(声调符号、数字声调、无声调)
- 支持姓氏识别
- 灵活的性能优化策略
- 内存友好的设计
- 完善的测试覆盖
## 安装
使用 Composer 安装:
```bash
composer require overtrue/pinyin:^6.0
```
## 使用
### 拼音风格
除了获取首字母的方法外,所有方法都支持第二个参数,用于指定拼音的格式,可选值为:
- `symbol` (默认)声调符号,例如 `pīn yīn`
- `none` 不输出声调,例如 `pin yin`
- `number` 末尾数字模式的拼音,例如 `pin1 yin1`
你可以使用字符串值或者 `ToneStyle` 枚举:
```php
use Overtrue\Pinyin\Pinyin;
use Overtrue\Pinyin\ToneStyle;
// 使用字符串
echo Pinyin::sentence('你好', 'none'); // ni hao
echo Pinyin::sentence('你好', 'number'); // ni3 hao3
echo Pinyin::sentence('你好', 'symbol'); // nǐ hǎo
// 使用枚举(推荐)
echo Pinyin::sentence('你好', ToneStyle::NONE); // ni hao
echo Pinyin::sentence('你好', ToneStyle::NUMBER); // ni3 hao3
echo Pinyin::sentence('你好', ToneStyle::SYMBOL); // nǐ hǎo
```
### 返回值
除了 `permalink` 返回字符串外,其它方法都返回集合类型 [`Overtrue\Pinyin\Collection`](https://github.com/overtrue/pinyin/blob/master/src/Collection.php)
```php
use Overtrue\Pinyin\Pinyin;
$pinyin = Pinyin::sentence('你好,世界');
```
你可以通过以下方式访问集合内容:
```php
echo $pinyin; // nǐ hǎo shì jiè
// 直接将对象转成字符串
$string = (string) $pinyin; // nǐ hǎo shì jiè
$pinyin->toArray(); // ['nǐ', 'hǎo', 'shì', 'jiè']
// 直接使用索引访问
$pinyin[0]; // 'nǐ'
// 使用函数遍历
$pinyin->map('ucfirst'); // ['Nǐ', 'Hǎo', 'Shì', 'Jiè']
// 拼接为字符串
$pinyin->join(' '); // 'nǐ hǎo shì jiè'
$pinyin->join('-'); // 'nǐ-hǎo-shì-jiè'
// 转成 json
$pinyin->toJson(); // '["nǐ","hǎo","shì","jiè"]'
json_encode($pinyin); // '["nǐ","hǎo","shì","jiè"]'
```
### 文字段落转拼音
```php
use Overtrue\Pinyin\Pinyin;
use Overtrue\Pinyin\ToneStyle;
echo Pinyin::sentence('带着希望去旅行,比到达终点更美好');
// dài zhe xī wàng qù lǚ xíng , bǐ dào dá zhōng diǎn gèng měi hǎo
// 去除声调
echo Pinyin::sentence('带着希望去旅行,比到达终点更美好', ToneStyle::NONE);
// dai zhe xi wang qu lv xing , bi dao da zhong dian geng mei hao
// 保留所有非汉字字符
echo Pinyin::fullSentence('ル是片假名,π是希腊字母', ToneStyle::NONE);
// ル shi pian jia ming ,π shi xi la zi mu
```
### 生成用于链接的拼音字符串
通常用于文章链接等,可以使用 `permalink` 方法获取拼音字符串:
```php
echo Pinyin::permalink('带着希望去旅行'); // dai-zhe-xi-wang-qu-lyu-xing
echo Pinyin::permalink('带着希望去旅行', '.'); // dai.zhe.xi.wang.qu.lyu.xing
```
### 获取首字符字符串
通常用于创建搜索用的索引,可以使用 `abbr` 方法转换:
```php
Pinyin::abbr('带着希望去旅行'); // ['d', 'z', 'x', 'w', 'q', 'l', 'x']
echo Pinyin::abbr('带着希望去旅行')->join('-'); // d-z-x-w-q-l-x
echo Pinyin::abbr('你好2018!')->join(''); // nh2018
echo Pinyin::abbr('Happy New Year! 2018!')->join(''); // HNY2018
// 保留原字符串的英文单词
echo Pinyin::abbr('CGV电影院', false, true)->join(''); // CGVdyy
```
**姓名首字母**
将首字作为姓氏转换,其余作为普通词语转换:
```php
Pinyin::nameAbbr('欧阳'); // ['o', 'y']
echo Pinyin::nameAbbr('单单单')->join('-'); // s-d-d
```
### 姓名转换
姓名的姓的读音有些与普通字不一样,比如 ‘单’ 常见的音为 `dan`,而作为姓的时候读 `shan`
```php
Pinyin::name('单某某'); // ['shàn', 'mǒu', 'mǒu']
Pinyin::name('单某某', 'none'); // ['shan', 'mou', 'mou']
Pinyin::name('单某某', 'none')->join('-'); // shan-mou-mou
```
### 护照姓名转换
根据国家规定 [关于中国护照旅行证上姓名拼音 ü(吕、律、闾、绿、女等)统一拼写为 YU 的提醒](http://sg.china-embassy.gov.cn/lsfw/zghz1/hzzxdt/201501/t20150122_2022198.htm) 的规则,将 `ü` 转换为 `yu`
```php
Pinyin::passportName('吕小布'); // ['lyu', 'xiao', 'bu']
Pinyin::passportName('女小花'); // ['nyu', 'xiao', 'hua']
Pinyin::passportName('律师'); // ['lyu', 'shi']
```
### 多音字
多音字的返回值为关联数组的集合,默认返回去重后的所有读音:
```php
$pinyin = Pinyin::heteronym('重庆');
$pinyin['重']; // ["zhòng", "chóng", "tóng"]
$pinyin['庆']; // ["qìng"]
$pinyin->toArray();
// [
// "重": ["zhòng", "chóng", "tóng"],
// "庆": ["qìng"]
// ]
```
如果不想要去重,可以数组形式返回:
```php
$pinyin = Pinyin::heteronym('重庆重庆', ToneStyle::SYMBOL, true);
// or
$pinyin = Pinyin::heteronymAsList('重庆重庆', ToneStyle::SYMBOL);
$pinyin->toArray();
// [
// ["重" => ["zhòng", "chóng", "tóng"]],
// ["庆" => ["qìng"]],
// ["重" => ["zhòng", "chóng", "tóng"]],
// ["庆" => ["qìng"]]
// ]
```
### 单字转拼音
和多音字类似,单字的返回值为字符串,多音字将根据该字字频调整得到常用音:
```php
$pinyin = Pinyin::chars('重庆');
echo $pinyin['重']; // "zhòng"
echo $pinyin['庆']; // "qìng"
$pinyin->toArray();
// [
// "重": "zhòng",
// "庆": "qìng"
// ]
```
> **Warning**
>
> 当单字处理时由于多音字来自词频表中取得常用音,所以在词语环境下可能出现不正确的情况,建议使用多音字处理。
## 性能优化策略 🚀
v6.0+ 版本提供了三种不同的转换策略,以适应不同的使用场景:
### 1. 内存优化策略(Memory Optimized)- 默认
- **内存占用**:~400KB
- **适用场景**:Web 请求、内存受限环境
- **特点**:每次加载一个词典段,用完即释放
```php
use Overtrue\Pinyin\Pinyin;
// 使用内存优化策略(默认)
Pinyin::useMemoryOptimized();
$result = Pinyin::sentence('你好世界');
echo $result; // nǐ hǎo shì jiè
```
### 2. 缓存策略(Cached)
- **内存占用**:~4MB
- **适用场景**:批处理、长时运行进程
- **特点**:缓存所有词典数据,重复转换速度提升 2-3 倍
```php
// 使用缓存策略
Pinyin::useCached();
// 批量处理时性能更好
foreach ($largeDataset as $text) {
$result = Pinyin::sentence($text);
echo $result . "\n";
}
// 清理缓存(可选)
\Overtrue\Pinyin\Converters\CachedConverter::clearCache();
```
### 3. 智能策略(Smart)
- **内存占用**:600KB-1.5MB
- **适用场景**:通用场景、自动优化
- **特点**:根据文本长度智能选择加载策略
```php
// 使用智能策略
Pinyin::useSmart();
// 短文本自动优化
$result1 = Pinyin::sentence('你好'); // 跳过长词词典
echo $result1; // nǐ hǎo
// 长文本自动调整
$result2 = Pinyin::sentence($longText); // 加载必要的词典
echo $result2;
```
### 自动选择策略
```php
// 根据运行环境自动选择最佳策略
Pinyin::useAutoStrategy();
// 获取推荐策略信息
$recommended = \Overtrue\Pinyin\ConverterFactory::recommend();
echo "推荐策略: {$recommended}";
```
### 直接使用 Converter
```php
use Overtrue\Pinyin\ConverterFactory;
// 创建特定策略的转换器
$converter = ConverterFactory::make('cached');
$result = $converter->convert('你好世界');
echo $result; // nǐ hǎo shì jiè
// 监控内存使用情况
$initialMemory = memory_get_usage();
$converter->convert('测试文本');
$memoryGrowth = memory_get_usage() - $initialMemory;
echo "内存增长: " . round($memoryGrowth / 1024, 2) . " KB";
```
### 性能对比
| 策略 | 内存占用 | 首次转换 | 重复转换 | 推荐场景 |
|-----|---------|---------|---------|---------|
| Memory Optimized | ~400KB | 中等 | 中等 | Web 请求 |
| Cached | ~4MB | 慢 | **最快** | 批处理 |
| Smart | 600KB-1.5MB | 快 | 快 | 通用场景 |
运行基准测试查看实际性能:
```bash
# 运行标准基准测试
php benchmark/run.php
# 详细的策略对比测试
php benchmark/compare-strategies.php
```
## 性能优化最佳实践
### 选择合适的策略
根据您的使用场景选择最合适的转换策略:
#### Web 应用(Laravel、Symfony 等)
```php
// 在应用启动时设置
Pinyin::useMemoryOptimized(); // 默认策略,内存占用最小
// 或在 ServiceProvider 中配置
public function boot()
{
Pinyin::useMemoryOptimized();
}
```
#### CLI 批处理脚本
```php
// 处理大量数据时使用缓存策略
Pinyin::useCached();
$results = [];
foreach ($thousandsOfTexts as $text) {
$results[] = Pinyin::sentence($text);
}
// 处理完成后清理缓存
\Overtrue\Pinyin\Converters\CachedConverter::clearCache();
```
#### 队列任务处理
```php
class ConvertPinyinJob implements ShouldQueue
{
public function handle()
{
// 队列任务中使用智能策略
Pinyin::useSmart();
// 处理任务...
}
}
```
### 性能监控
```php
use Overtrue\Pinyin\ConverterFactory;
// 监控不同策略的内存使用情况
$strategies = ['memory', 'cached', 'smart'];
foreach ($strategies as $strategy) {
$converter = ConverterFactory::make($strategy);
$initialMemory = memory_get_usage();
$converter->convert('测试文本');
$memoryGrowth = memory_get_usage() - $initialMemory;
echo "策略: {$strategy}, 内存增长: " . round($memoryGrowth / 1024, 2) . " KB" . PHP_EOL;
}
```
### 基准测试
项目提供了多个基准测试工具:
```bash
# 运行标准基准测试
php benchmark/run.php
# 详细的策略对比
php benchmark/compare-strategies.php
```
基准测试会显示不同策略的性能对比,包括:
- 内存使用情况
- 转换速度
- 不同文本长度的性能表现
### 内存管理建议
1. **生产环境**:使用 `Memory Optimized` 策略,避免内存泄漏
2. **开发环境**:可以使用 `Smart` 策略,平衡性能和内存
3. **批处理任务**:使用 `Cached` 策略,处理完成后调用 `clearCache()`
4. **内存受限环境**:严格使用 `Memory Optimized` 策略
### 性能对比数据
基于 1000 次转换的基准测试结果:
| 场景 | Memory Optimized | Cached | Smart |
|-----|-----------------|--------|-------|
| 短文本(<10字) | 1.2ms | 0.5ms | 0.8ms |
| 中等文本(10-50字) | 3.5ms | 1.2ms | 2.1ms |
| 长文本(>100字) | 8.7ms | 3.1ms | 5.2ms |
| 内存占用 | 400KB | 4MB | 1.5MB |
> 💡 **提示**:缓存策略在重复转换时性能提升约 2-3 倍,但会占用更多内存。
## v/yu/ü 的问题
根据国家语言文字工作委员会的规定,`lv``lyu``lǚ` 都是正确的,但是 `lv` 是最常用的,所以默认使用 `lv`,如果你需要使用其他的,可以在初始化时传入:
```php
echo Pinyin::sentence('旅行');
// lǚ xíng
echo Pinyin::sentence('旅行', 'none');
// lv xing
echo Pinyin::yuToYu()->sentence('旅行', 'none');
// lyu xing
echo Pinyin::yuToU()->sentence('旅行', 'none');
// lu xing
echo Pinyin::yuToV()->sentence('旅行', 'none');
// lv xing
```
> **Warning**
>
> 仅在拼音风格为非 `none` 模式下有效。
## 命令行工具
你可以使用命令行来实现拼音的转换:
```bash
php ./bin/pinyin 带着希望去旅行 --method=sentence --tone-style=symbol
# dài zhe xī wàng qù lǚ xíng
```
更多使用方法,可以查看帮助文档:
```bash
php ./bin/pinyin --help
# Usage:
# ./pinyin [chinese] [method] [options]
# Options:
# -j, --json 输出 JSON 格式.
# -c, --compact 不格式化输出 JSON.
# -m, --method=[method] 转换方式,可选:sentence/fullSentence/permalink/abbr/nameAbbr/name/passportName/phrase/heteronym/chars.
# --no-tone 不使用音调.
# --tone-style=[style] 音调风格,可选值:symbol/none/number, default: none.
# -h, --help 显示帮助.
```
### 命令行工具示例
```bash
# 基本转换
php ./bin/pinyin "你好世界"
# ni hao shi jie
# 指定音调风格
php ./bin/pinyin "你好世界" --tone-style=symbol
# nǐ hǎo shì jiè
php ./bin/pinyin "你好世界" --tone-style=number
# ni3 hao3 shi4 jie4
# 生成链接格式
php ./bin/pinyin "带着希望去旅行" --method=permalink
# dai-zhe-xi-wang-qu-lv-xing
# 获取首字母
php ./bin/pinyin "带着希望去旅行" --method=abbr
# d z x w q l x
# 多音字转换(JSON格式)
php ./bin/pinyin "重庆" --method=heteronym --json
# {"重":["zhong","chong","tong"],"庆":["qing"]}
# 姓名转换
php ./bin/pinyin "欧阳修" --method=name
# ou yang xiu
```
## 在 Laravel 中使用
独立的包在这里:[overtrue/laravel-pinyin](https://github.com/overtrue/laravel-pinyin)
## 中文简繁转换
如何你有这个需求,也可以了解我的另一个包:[overtrue/php-opencc](https://github.com/overtrue/php-opencc)
## Contribution
欢迎提意见及完善补充词库:
- 单字拼音错误请添加到:[sources/pathes/chars.txt](https://github.com/overtrue/pinyin/blob/master/sources/pathes/chars.txt)
- 词语错误或补齐,请添加到:[sources/pathes/words.txt](https://github.com/overtrue/pinyin/blob/master/sources/pathes/words.txt)
## 参考
- [mozillazg/pinyin-data](https://github.com/mozillazg/pinyin-data)
- [overtrue/pinyin-resources](https://github.com/overtrue/pinyin-resources)
## :heart: Sponsor me
如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
# License
MIT
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/utils.php';
use Overtrue\Pinyin\Pinyin;
$methods = ['sentence','fullSentence','name','passportName','phrase','permalink','heteronym','heteronymAsList','chars','abbr','nameAbbr'];
$method = 'sentence';
$inputOptions = [];
$methodAsString = implode('/', $methods);
$help = <<<"HELP"
Usage:
./pinyin [chinese] [method] [options]
Options:
-j, --json 输出 JSON 格式.
-c, --compact 不格式化输出 JSON.
-m, --method=[method] 转换方式可选{$methodAsString}.
--no-tone 不使用音调.
--tone-style=[style] 音调风格可选值symbol/none/number, default: none.
-h, --help 显示帮助.
HELP;
function has_option($option, $alias = null): bool
{
global $inputOptions;
if ($alias) {
return array_key_exists($option, $inputOptions) || array_key_exists($alias, $inputOptions);
}
return array_key_exists($option, $inputOptions);
}
function get_option($option, $default = null, $alias = null): ?string
{
global $inputOptions;
if ($alias) {
return $inputOptions[$option] ?? $inputOptions[$alias] ?? $default;
}
return $inputOptions[$option] ?? $default;
}
$inputOptions = parse_options($argv);
$input = $inputOptions[0] ?? null;
if (empty($input) || has_option('help', 'h')) {
echo $help;
exit(0);
}
$method = get_option('method', $method, 'm');
$toneStyle = has_option('no-tone') ? 'none' : get_option('tone-style', 'none');
if (! in_array($method, $methods)) {
echo "Method '{$method}' is not supported.\n";
exit(1);
}
$result = Pinyin::$method($input, $method === 'permalink' ? '-' : $toneStyle);
$toJson = has_option('json', 'j') || in_array($method, ['heteronym', 'heteronymAsList']);
if ($toJson) {
$options = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT;
if (has_option('compact', 'c')) {
$options = 0;
}
$result = json_encode($result, $options);
}
echo $result, "\n";
{
"name": "overtrue/pinyin",
"description": "Chinese to pinyin translator.",
"keywords": [
"chinese",
"pinyin",
"cn2pinyin"
],
"homepage": "https://github.com/overtrue/pinyin",
"license": "MIT",
"authors": [
{
"name": "overtrue",
"homepage": "http://github.com/overtrue",
"email": "anzhengchao@gmail.com"
}
],
"autoload": {
"psr-4": {
"Overtrue\\Pinyin\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Overtrue\\Pinyin\\Tests\\": "tests/"
}
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^12.0.21",
"brainmaestro/composer-git-hooks": "^3.0",
"nunomaduro/termwind": "^2.0",
"laravel/pint": "^1.10"
},
"bin": [
"bin/pinyin"
],
"extra": {
"hooks": {
"pre-commit": [
"composer pint",
"composer test"
],
"pre-push": [
"composer pint",
"composer test"
]
}
},
"scripts": {
"post-update-cmd": [
"cghooks update"
],
"post-merge": "composer install",
"post-install-cmd": [
"cghooks remove",
"cghooks add --ignore-lock"
],
"cghooks": "vendor/bin/cghooks",
"pint": "vendor/bin/pint ./src ./tests",
"fix-style": "vendor/bin/pint ./src ./tests",
"test": "vendor/bin/phpunit --colors=always",
"build": "php ./bin/build",
"benchmark": "php ./benchmark/run.php"
},
"scripts-descriptions": {
"test": "Run all tests.",
"fix-style": "Run style checks and fix violations."
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
return array (
'万俟' => ' mò qí ',
'尉迟' => ' yù chí ',
'单于' => ' chán yú ',
'重' => ' chóng ',
'秘' => ' bì ',
'冼' => ' xiǎn ',
'华' => ' huà ',
'过' => ' guō ',
'纪' => ' jǐ ',
'燕' => ' yān ',
'种' => ' chóng ',
'繁' => ' pó ',
'幺' => ' yāo ',
'覃' => ' qín ',
'冯' => ' féng ',
'石' => ' shí ',
'缪' => ' miào ',
'瞿' => ' qú ',
'曾' => ' zēng ',
'解' => ' xiè ',
'折' => ' shè ',
'那' => ' nā ',
'佴' => ' nài ',
'难' => ' nàn ',
'粘' => ' niàn ',
'藏' => ' zàng ',
'扎' => ' zā ',
'翟' => ' zhái ',
'都' => ' dū ',
'六' => ' lù ',
'薄' => ' bó ',
'贾' => ' jiǎ ',
'的' => ' dē ',
'哈' => ' hǎ ',
'居' => ' jū ',
'盖' => ' gě ',
'查' => ' zhā ',
'盛' => ' shèng ',
'塔' => ' tǎ ',
'和' => ' hé ',
'柏' => ' bǎi ',
'朴' => ' piáo ',
'蓝' => ' lán ',
'牟' => ' móu ',
'殷' => ' yīn ',
'陆' => ' lù ',
'乜' => ' niè ',
'乐' => ' yuè ',
'阚' => ' kàn ',
'叶' => ' yè ',
'强' => ' qiáng ',
'不' => ' fǒu ',
'丁' => ' dīng ',
'阿' => ' ā ',
'汤' => ' tāng ',
'万' => ' wàn ',
'车' => ' chē ',
'称' => ' chēng ',
'沈' => ' shěn ',
'区' => ' ōu ',
'仇' => ' qiú ',
'宿' => ' sù ',
'南' => ' nán ',
'单' => ' shàn ',
'卜' => ' bǔ ',
'鸟' => ' niǎo ',
'思' => ' sī ',
'殳' => ' shū ',
'寻' => ' xún ',
'於' => ' yú ',
'烟' => ' yān ',
'余' => ' yú ',
'浅' => ' qiǎn ',
'艾' => ' ài ',
'浣' => ' wǎn ',
'无' => ' wú ',
'信' => ' xìn ',
'许' => ' xǔ ',
'齐' => ' qí ',
'俞' => ' yú ',
'若' => ' ruò ',
'贠' => ' yùn ',
'貟' => ' yùn ',
'么' => ' yāo ',
);
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
return array (
'騛' => ' fēi ',
'騜' => ' huáng ',
'騝' => ' qián ',
'騞' => ' huō ',
'騟' => ' yú ',
'騠' => ' tí ',
'騡' => ' quán ',
'騢' => ' xiá ',
'騣' => ' zōng ',
'騤' => ' kuí ',
'騥' => ' róu ',
'騦' => ' sī ',
'騧' => ' guā ',
'騨' => ' tuó ',
'騩' => ' guī ',
'騪' => ' sōu ',
'騫' => ' qiān ',
'騬' => ' chéng ',
'騭' => ' zhì ',
'騮' => ' liú ',
'騯' => ' péng ',
'騰' => ' téng ',
'騱' => ' xí ',
'騲' => ' cǎo ',
'騳' => ' dú ',
'騴' => ' yàn ',
'騵' => ' yuán ',
'騶' => ' zōu ',
'騷' => ' sāo ',
'騸' => ' shàn ',
'騹' => ' qí ',
'騺' => ' zhì ',
'騻' => ' shuāng ',
'騼' => ' lù ',
'騽' => ' xí ',
'騾' => ' luó ',
'騿' => ' zhāng ',
'驀' => ' mò ',
'驁' => ' ào ',
'驂' => ' cān ',
'驃' => ' biāo ',
'驄' => ' cōng ',
'驅' => ' qū ',
'驆' => ' bì ',
'驇' => ' zhì ',
'驈' => ' yù ',
'驉' => ' xū ',
'驊' => ' huá ',
'驋' => ' bō ',
'驌' => ' sù ',
'驍' => ' xiāo ',
'驎' => ' lín ',
'驏' => ' zhàn ',
'驐' => ' dūn ',
'驑' => ' liú ',
'驒' => ' tuó ',
'驓' => ' céng ',
'驔' => ' diàn ',
'驕' => ' jiāo ',
'驖' => ' tiě ',
'驗' => ' yàn ',
'驘' => ' luó ',
'驙' => ' zhān ',
'驚' => ' jīng ',
'驛' => ' yì ',
'驜' => ' yè ',
'驝' => ' tuō ',
'驞' => ' pīn ',
'驟' => ' zhòu ',
'驠' => ' yàn ',
'驡' => ' lóng ',
'驢' => ' lǘ ',
'驣' => ' téng ',
'驤' => ' xiāng ',
'驥' => ' jì ',
'驦' => ' shuāng ',
'驧' => ' jú ',
'驨' => ' xí ',
'驩' => ' huān ',
'驪' => ' lí ',
'驫' => ' biāo ',
'马' => ' mǎ ',
'驭' => ' yù ',
'驮' => ' tuó ',
'驯' => ' xùn ',
'驰' => ' chí ',
'驱' => ' qū ',
'驲' => ' rì ',
'驳' => ' bó ',
'驴' => ' lǘ ',
'驵' => ' zǎng ',
'驶' => ' shǐ ',
'驷' => ' sì ',
'驸' => ' fù ',
'驹' => ' jū ',
'驺' => ' zōu ',
'驻' => ' zhù ',
'驼' => ' tuó ',
'驽' => ' nú ',
'驾' => ' jià ',
'驿' => ' yì ',
'骀' => ' dài ',
'骁' => ' xiāo ',
'骂' => ' mà ',
'骃' => ' yīn ',
'骄' => ' jiāo ',
'骅' => ' huá ',
'骆' => ' luò ',
'骇' => ' hài ',
'骈' => ' pián ',
'骉' => ' biāo ',
'骊' => ' lí ',
'骋' => ' chěng ',
'验' => ' yàn ',
'骍' => ' xīng ',
'骎' => ' qīn ',
'骏' => ' jùn ',
'骐' => ' qí ',
'骑' => ' qí ',
'骒' => ' kè ',
'骓' => ' zhuī ',
'骔' => ' zōng ',
'骕' => ' sù ',
'骖' => ' cān ',
'骗' => ' piàn ',
'骘' => ' zhì ',
'骙' => ' kuí ',
'骚' => ' sāo ',
'骛' => ' wù ',
'骜' => ' ào ',
'骝' => ' liú ',
'骞' => ' qiān ',
'骟' => ' shàn ',
'骠' => ' biāo ',
'骡' => ' luó ',
'骢' => ' cōng ',
'骣' => ' chǎn ',
'骤' => ' zhòu ',
'骥' => ' jì ',
'骦' => ' shuāng ',
'骧' => ' xiāng ',
'骨' => ' gǔ ',
'骩' => ' wěi ',
'骪' => ' wěi ',
'骫' => ' wěi ',
'骬' => ' yú ',
'骭' => ' gàn ',
'骮' => ' yì ',
'骯' => ' āng ',
'骰' => ' tóu ',
'骱' => ' jiè ',
'骲' => ' bào ',
'骳' => ' bèi ',
'骴' => ' cī ',
'骵' => ' tǐ ',
'骶' => ' dǐ ',
'骷' => ' kū ',
'骸' => ' hái ',
'骹' => ' qiāo ',
'骺' => ' hóu ',
'骻' => ' kuà ',
'骼' => ' gé ',
'骽' => ' tuǐ ',
'骾' => ' gěng ',
'骿' => ' pián ',
'髀' => ' bì ',
'髁' => ' kē ',
'髂' => ' qià ',
'髃' => ' yú ',
'髄' => ' suǐ ',
'髅' => ' lóu ',
'髆' => ' bó ',
'髇' => ' xiāo ',
'髈' => ' bǎng ',
'髉' => ' bó ',
'髊' => ' cī ',
'髋' => ' kuān ',
'髌' => ' bìn ',
'髍' => ' mó ',
'髎' => ' liáo ',
'髏' => ' lóu ',
'髐' => ' xiāo ',
'髑' => ' dú ',
'髒' => ' zāng ',
'髓' => ' suǐ ',
'體' => ' tǐ ',
'髕' => ' bìn ',
'髖' => ' kuān ',
'髗' => ' lú ',
'高' => ' gāo ',
'髙' => ' gāo ',
'髚' => ' qiào ',
'髛' => ' kāo ',
'髜' => ' qiǎo ',
'髝' => ' láo ',
'髞' => ' sào ',
'髟' => ' biāo ',
'髠' => ' kūn ',
'髡' => ' kūn ',
'髢' => ' dí ',
'髣' => ' fǎng ',
'髤' => ' xiū ',
'髥' => ' rán ',
'髦' => ' máo ',
'髧' => ' dàn ',
'髨' => ' kūn ',
'髩' => ' bìn ',
'髪' => ' fà ',
'髫' => ' tiáo ',
'髬' => ' pī ',
'髭' => ' zī ',
'髮' => ' fà ',
'髯' => ' rán ',
'髰' => ' tì ',
'髱' => ' bào ',
'髲' => ' bì ',
'髳' => ' máo ',
'髴' => ' fú ',
'髵' => ' ér ',
'髶' => ' róng ',
'髷' => ' qū ',
'髸' => ' gōng ',
'髹' => ' xiū ',
'髺' => ' kuò ',
'髻' => ' jì ',
'髼' => ' péng ',
'髽' => ' zhuā ',
'髾' => ' shāo ',
'髿' => ' suō ',
'鬀' => ' tì ',
'鬁' => ' lì ',
'鬂' => ' bìn ',
'鬃' => ' zōng ',
'鬄' => ' dí ',
'鬅' => ' péng ',
'鬆' => ' sōng ',
'鬇' => ' zhēng ',
'鬈' => ' quán ',
'鬉' => ' zōng ',
'鬊' => ' shùn ',
'鬋' => ' jiǎn ',
'鬌' => ' tuǒ ',
'鬍' => ' hú ',
'鬎' => ' là ',
'鬏' => ' jiū ',
'鬐' => ' qí ',
'鬑' => ' lián ',
'鬒' => ' zhěn ',
'鬓' => ' bìn ',
'鬔' => ' péng ',
'鬕' => ' mà ',
'鬖' => ' sān ',
'鬗' => ' mán ',
'鬘' => ' mán ',
'鬙' => ' sēng ',
'鬚' => ' xū ',
'鬛' => ' liè ',
'鬜' => ' qiān ',
'鬝' => ' qiān ',
'鬞' => ' náng ',
'鬟' => ' huán ',
'鬠' => ' kuò ',
'鬡' => ' níng ',
'鬢' => ' bìn ',
'鬣' => ' liè ',
'鬤' => ' ráng ',
'鬥' => ' dòu ',
'鬦' => ' dòu ',
'鬧' => ' nào ',
'鬨' => ' hòng ',
'鬩' => ' xì ',
'鬪' => ' dòu ',
'鬫' => ' hǎn ',
'鬬' => ' dòu ',
'鬭' => ' dòu ',
'鬮' => ' jiū ',
'鬯' => ' chàng ',
'鬰' => ' yù ',
'鬱' => ' yù ',
'鬲' => ' gé ',
'鬳' => ' yàn ',
'鬴' => ' fǔ ',
'鬵' => ' qín ',
'鬶' => ' guī ',
'鬷' => ' zōng ',
'鬸' => ' liù ',
'鬹' => ' guī ',
'鬺' => ' shāng ',
'鬻' => ' yù ',
'鬼' => ' guǐ ',
'鬽' => ' mèi ',
'鬾' => ' jì ',
'鬿' => ' qí ',
'魀' => ' gà ',
'魁' => ' kuí ',
'魂' => ' hún ',
'魃' => ' bá ',
'魄' => ' pò ',
'魅' => ' mèi ',
'魆' => ' xū ',
'魇' => ' yǎn ',
'魈' => ' xiāo ',
'魉' => ' liǎng ',
'魊' => ' yù ',
'魋' => ' tuí ',
'魌' => ' qī ',
'魍' => ' wǎng ',
'魎' => ' liǎng ',
'魏' => ' wèi ',
'魐' => ' gān ',
'魑' => ' chī ',
'魒' => ' piāo ',
'魓' => ' bì ',
'魔' => ' mó ',
'魕' => ' jǐ ',
'魖' => ' xū ',
'魗' => ' chǒu ',
'魘' => ' yǎn ',
'魙' => ' zhān ',
'魚' => ' yú ',
'魛' => ' dāo ',
'魜' => ' rén ',
'魝' => ' jié ',
'魞' => ' bā ',
'魟' => ' hóng ',
'魠' => ' tuō ',
'魡' => ' diào ',
'魢' => ' jǐ ',
'魣' => ' xù ',
'魤' => ' é ',
'魥' => ' è ',
'魦' => ' shā ',
'魧' => ' háng ',
'魨' => ' tún ',
'魩' => ' mò ',
'魪' => ' jiè ',
'魫' => ' shěn ',
'魬' => ' bǎn ',
'魭' => ' yuán ',
'魮' => ' pí ',
'魯' => ' lǔ ',
'魰' => ' wén ',
'魱' => ' hú ',
'魲' => ' lú ',
'魳' => ' zā ',
'魴' => ' fáng ',
'魵' => ' fén ',
'魶' => ' nà ',
'魷' => ' yóu ',
'魸' => ' piàn ',
'魹' => ' mó ',
'魺' => ' hé ',
'魻' => ' xiá ',
'魼' => ' qū ',
'魽' => ' hán ',
'魾' => ' pī ',
'魿' => ' líng ',
'鮀' => ' tuó ',
'鮁' => ' bō ',
'鮂' => ' qiú ',
'鮃' => ' píng ',
'鮄' => ' fú ',
'鮅' => ' bì ',
'鮆' => ' cǐ ',
'鮇' => ' wèi ',
'鮈' => ' jū ',
'鮉' => ' diāo ',
'鮊' => ' bà ',
'鮋' => ' yóu ',
'鮌' => ' gǔn ',
'鮍' => ' pī ',
'鮎' => ' nián ',
'鮏' => ' xīng ',
'鮐' => ' tái ',
'鮑' => ' bào ',
'鮒' => ' fù ',
'鮓' => ' zhǎ ',
'鮔' => ' jù ',
'鮕' => ' gū ',
'鮖' => ' shí ',
'鮗' => ' dōng ',
'鮘' => ' dai ',
'鮙' => ' tà ',
'鮚' => ' jié ',
'鮛' => ' shū ',
'鮜' => ' hòu ',
'鮝' => ' xiǎng ',
'鮞' => ' ér ',
'鮟' => ' àn ',
'鮠' => ' wéi ',
'鮡' => ' zhào ',
'鮢' => ' zhū ',
'鮣' => ' yìn ',
'鮤' => ' liè ',
'鮥' => ' luò ',
'鮦' => ' tóng ',
'鮧' => ' tǐ ',
'鮨' => ' yì ',
'鮩' => ' bìng ',
'鮪' => ' wěi ',
'鮫' => ' jiāo ',
'鮬' => ' kū ',
'鮭' => ' guī ',
'鮮' => ' xiān ',
'鮯' => ' gé ',
'鮰' => ' huí ',
'鮱' => ' lǎo ',
'鮲' => ' fú ',
'鮳' => ' kào ',
'鮴' => ' xiū ',
'鮵' => ' duó ',
'鮶' => ' jūn ',
'鮷' => ' tí ',
'鮸' => ' miǎn ',
'鮹' => ' shāo ',
'鮺' => ' zhǎ ',
'鮻' => ' suō ',
'鮼' => ' qīn ',
'鮽' => ' yú ',
'鮾' => ' něi ',
'鮿' => ' zhé ',
'鯀' => ' gǔn ',
'鯁' => ' gěng ',
'鯂' => ' sū ',
'鯃' => ' wú ',
'鯄' => ' qiú ',
'鯅' => ' shān ',
'鯆' => ' pū ',
'鯇' => ' huàn ',
'鯈' => ' tiáo ',
'鯉' => ' lǐ ',
'鯊' => ' shā ',
'鯋' => ' shā ',
'鯌' => ' kào ',
'鯍' => ' méng ',
'鯎' => ' chéng ',
'鯏' => ' lí ',
'鯐' => ' zǒu ',
'鯑' => ' xī ',
'鯒' => ' yǒng ',
'鯓' => ' shēn ',
'鯔' => ' zī ',
'鯕' => ' qí ',
'鯖' => ' zhēng ',
'鯗' => ' xiǎng ',
'鯘' => ' něi ',
'鯙' => ' chún ',
'鯚' => ' jì ',
'鯛' => ' diāo ',
'鯜' => ' qiè ',
'鯝' => ' gù ',
'鯞' => ' zhǒu ',
'鯟' => ' dōng ',
'鯠' => ' lái ',
'鯡' => ' fèi ',
'鯢' => ' ní ',
'鯣' => ' yì ',
'鯤' => ' kūn ',
'鯥' => ' lù ',
'鯦' => ' jiù ',
'鯧' => ' chāng ',
'鯨' => ' jīng ',
'鯩' => ' lún ',
'鯪' => ' líng ',
'鯫' => ' zōu ',
'鯬' => ' lí ',
'鯭' => ' měng ',
'鯮' => ' zōng ',
'鯯' => ' zhì ',
'鯰' => ' nián ',
'鯱' => ' hǔ ',
'鯲' => ' yú ',
'鯳' => ' dǐ ',
'鯴' => ' shī ',
'鯵' => ' shēn ',
'鯶' => ' huàn ',
'鯷' => ' tí ',
'鯸' => ' hóu ',
'鯹' => ' xīng ',
'鯺' => ' zhū ',
'鯻' => ' là ',
'鯼' => ' zōng ',
'鯽' => ' zéi ',
'鯾' => ' biān ',
'鯿' => ' biān ',
'鰀' => ' huàn ',
'鰁' => ' quán ',
'鰂' => ' zéi ',
'鰃' => ' wēi ',
'鰄' => ' wēi ',
'鰅' => ' yú ',
'鰆' => ' chūn ',
'鰇' => ' róu ',
'鰈' => ' dié ',
'鰉' => ' huáng ',
'鰊' => ' liàn ',
'鰋' => ' yǎn ',
'鰌' => ' qiū ',
'鰍' => ' qiū ',
'鰎' => ' jiǎn ',
'鰏' => ' bī ',
'鰐' => ' è ',
'鰑' => ' yáng ',
'鰒' => ' fù ',
'鰓' => ' sāi ',
'鰔' => ' gǎn ',
'鰕' => ' xiā ',
'鰖' => ' tuǒ ',
'鰗' => ' hú ',
'鰘' => ' shì ',
'鰙' => ' ruò ',
'鰚' => ' xuān ',
'鰛' => ' wēn ',
'鰜' => ' qiàn ',
'鰝' => ' hào ',
'鰞' => ' wū ',
'鰟' => ' fáng ',
'鰠' => ' sāo ',
'鰡' => ' liú ',
'鰢' => ' mǎ ',
'鰣' => ' shí ',
'鰤' => ' shī ',
'鰥' => ' guān ',
'鰦' => ' zī ',
'鰧' => ' téng ',
'鰨' => ' tǎ ',
'鰩' => ' yáo ',
'鰪' => ' é ',
'鰫' => ' yóng ',
'鰬' => ' qián ',
'鰭' => ' qí ',
'鰮' => ' wēn ',
'鰯' => ' ruò ',
'鰰' => ' shén ',
'鰱' => ' lián ',
'鰲' => ' áo ',
'鰳' => ' lè ',
'鰴' => ' huī ',
'鰵' => ' mǐn ',
'鰶' => ' jì ',
'鰷' => ' tiáo ',
'鰸' => ' qū ',
'鰹' => ' jiān ',
'鰺' => ' shēn ',
'鰻' => ' mán ',
'鰼' => ' xí ',
'鰽' => ' qiú ',
'鰾' => ' biào ',
'鰿' => ' jì ',
'鱀' => ' jì ',
'鱁' => ' zhú ',
'鱂' => ' jiāng ',
'鱃' => ' xiū ',
'鱄' => ' zhuān ',
'鱅' => ' yōng ',
'鱆' => ' zhāng ',
'鱇' => ' kāng ',
'鱈' => ' xuě ',
'鱉' => ' biē ',
'鱊' => ' yù ',
'鱋' => ' qū ',
'鱌' => ' xiàng ',
'鱍' => ' bō ',
'鱎' => ' jiǎo ',
'鱏' => ' xún ',
'鱐' => ' sù ',
'鱑' => ' huáng ',
'鱒' => ' zūn ',
'鱓' => ' shàn ',
'鱔' => ' shàn ',
'鱕' => ' fān ',
'鱖' => ' guì ',
'鱗' => ' lín ',
'鱘' => ' xún ',
'鱙' => ' miáo ',
'鱚' => ' xǐ ',
'鱛' => ' zēng ',
'鱜' => ' xiāng ',
'鱝' => ' fèn ',
'鱞' => ' guān ',
'鱟' => ' hòu ',
'鱠' => ' kuài ',
'鱡' => ' zéi ',
'鱢' => ' sāo ',
'鱣' => ' zhān ',
'鱤' => ' gǎn ',
'鱥' => ' guì ',
'鱦' => ' yìng ',
'鱧' => ' lǐ ',
'鱨' => ' cháng ',
'鱩' => ' léi ',
'鱪' => ' shǔ ',
'鱫' => ' ài ',
'鱬' => ' rú ',
'鱭' => ' jì ',
'鱮' => ' xù ',
'鱯' => ' hù ',
'鱰' => ' shǔ ',
'鱱' => ' lì ',
'鱲' => ' liè ',
'鱳' => ' lì ',
'鱴' => ' miè ',
'鱵' => ' zhēn ',
'鱶' => ' xiǎng ',
'鱷' => ' è ',
'鱸' => ' lú ',
'鱹' => ' guàn ',
'鱺' => ' lí ',
'鱻' => ' xiān ',
'鱼' => ' yú ',
'鱽' => ' dāo ',
'鱾' => ' jǐ ',
'鱿' => ' yóu ',
'鲀' => ' tún ',
'鲁' => ' lǔ ',
'鲂' => ' fáng ',
'鲃' => ' bā ',
'鲄' => ' hé ',
'鲅' => ' bà ',
'鲆' => ' píng ',
'鲇' => ' nián ',
'鲈' => ' lú ',
'鲉' => ' yóu ',
'鲊' => ' zhǎ ',
'鲋' => ' fù ',
'鲌' => ' bà ',
'鲍' => ' bào ',
'鲎' => ' hòu ',
'鲏' => ' pí ',
'鲐' => ' tái ',
'鲑' => ' guī ',
'鲒' => ' jié ',
'鲓' => ' kào ',
'鲔' => ' wěi ',
'鲕' => ' ér ',
'鲖' => ' tóng ',
'鲗' => ' zéi ',
'鲘' => ' hòu ',
'鲙' => ' kuài ',
'鲚' => ' jì ',
'鲛' => ' jiāo ',
'鲜' => ' xiān ',
'鲝' => ' zhǎ ',
'鲞' => ' xiǎng ',
'鲟' => ' xún ',
'鲠' => ' gěng ',
'鲡' => ' lí ',
'鲢' => ' lián ',
'鲣' => ' jiān ',
'鲤' => ' lǐ ',
'鲥' => ' shí ',
'鲦' => ' tiáo ',
'鲧' => ' gǔn ',
'鲨' => ' shā ',
'鲩' => ' huàn ',
'鲪' => ' jūn ',
'鲫' => ' jì ',
'鲬' => ' yǒng ',
'鲭' => ' qīng ',
'鲮' => ' líng ',
'鲯' => ' qí ',
'鲰' => ' zōu ',
'鲱' => ' fēi ',
'鲲' => ' kūn ',
'鲳' => ' chāng ',
'鲴' => ' gù ',
'鲵' => ' ní ',
'鲶' => ' nián ',
'鲷' => ' diāo ',
'鲸' => ' jīng ',
'鲹' => ' shēn ',
'鲺' => ' shī ',
'鲻' => ' zī ',
'鲼' => ' fèn ',
'鲽' => ' dié ',
'鲾' => ' bī ',
'鲿' => ' cháng ',
'鳀' => ' tí ',
'鳁' => ' wēn ',
'鳂' => ' wēi ',
'鳃' => ' sāi ',
'鳄' => ' è ',
'鳅' => ' qiū ',
'鳆' => ' fù ',
'鳇' => ' huáng ',
'鳈' => ' quán ',
'鳉' => ' jiāng ',
'鳊' => ' biān ',
'鳋' => ' sāo ',
'鳌' => ' áo ',
'鳍' => ' qí ',
'鳎' => ' tǎ ',
'鳏' => ' guān ',
'鳐' => ' yáo ',
'鳑' => ' páng ',
'鳒' => ' jiān ',
'鳓' => ' lè ',
'鳔' => ' biào ',
'鳕' => ' xuě ',
'鳖' => ' biē ',
'鳗' => ' mán ',
'鳘' => ' mǐn ',
'鳙' => ' yōng ',
'鳚' => ' wèi ',
'鳛' => ' xí ',
'鳜' => ' guì ',
'鳝' => ' shàn ',
'鳞' => ' lín ',
'鳟' => ' zūn ',
'鳠' => ' hù ',
'鳡' => ' gǎn ',
'鳢' => ' lǐ ',
'鳣' => ' zhān ',
'鳤' => ' guǎn ',
'鳥' => ' niǎo ',
'鳦' => ' yǐ ',
'鳧' => ' fú ',
'鳨' => ' lì ',
'鳩' => ' jiū ',
'鳪' => ' bú ',
'鳫' => ' yàn ',
'鳬' => ' fǔ ',
'鳭' => ' diāo ',
'鳮' => ' jī ',
'鳯' => ' fèng ',
'鳰' => ' rù ',
'鳱' => ' gān ',
'鳲' => ' shī ',
'鳳' => ' fèng ',
'鳴' => ' míng ',
'鳵' => ' bǎo ',
'鳶' => ' yuān ',
'鳷' => ' zhī ',
'鳸' => ' hù ',
'鳹' => ' qín ',
'鳺' => ' fū ',
'鳻' => ' bān ',
'鳼' => ' wén ',
'鳽' => ' jiān ',
'鳾' => ' shī ',
'鳿' => ' yù ',
'鴀' => ' fǒu ',
'鴁' => ' yāo ',
'鴂' => ' jué ',
'鴃' => ' jué ',
'鴄' => ' pǐ ',
'鴅' => ' huān ',
'鴆' => ' zhèn ',
'鴇' => ' bǎo ',
'鴈' => ' yàn ',
'鴉' => ' yā ',
'鴊' => ' zhèng ',
'鴋' => ' fāng ',
'鴌' => ' fèng ',
'鴍' => ' wén ',
'鴎' => ' ōu ',
'鴏' => ' dài ',
'鴐' => ' gē ',
'鴑' => ' rú ',
'鴒' => ' líng ',
'鴓' => ' miè ',
'鴔' => ' fú ',
'鴕' => ' tuó ',
'鴖' => ' mín ',
'鴗' => ' lì ',
'鴘' => ' biǎn ',
'鴙' => ' zhì ',
'鴚' => ' gē ',
'鴛' => ' yuān ',
'鴜' => ' cí ',
'鴝' => ' qú ',
'鴞' => ' xiāo ',
'鴟' => ' chī ',
'鴠' => ' dàn ',
'鴡' => ' jū ',
'鴢' => ' yǎo ',
'鴣' => ' gū ',
'鴤' => ' zhōng ',
'鴥' => ' yù ',
'鴦' => ' yāng ',
'鴧' => ' yù ',
'鴨' => ' yā ',
'鴩' => ' tiě ',
'鴪' => ' yù ',
'鴫' => ' tián ',
'鴬' => ' yīng ',
'鴭' => ' duī ',
'鴮' => ' wū ',
'鴯' => ' ér ',
'鴰' => ' guā ',
'鴱' => ' ài ',
'鴲' => ' zhī ',
'鴳' => ' yàn ',
'鴴' => ' héng ',
'鴵' => ' xiāo ',
'鴶' => ' jiá ',
'鴷' => ' liè ',
'鴸' => ' zhū ',
'鴹' => ' yáng ',
'鴺' => ' tí ',
'鴻' => ' hóng ',
'鴼' => ' luò ',
'鴽' => ' rú ',
'鴾' => ' móu ',
'鴿' => ' gē ',
'鵀' => ' rén ',
'鵁' => ' jiāo ',
'鵂' => ' xiū ',
'鵃' => ' zhōu ',
'鵄' => ' chī ',
'鵅' => ' luò ',
'鵆' => ' héng ',
'鵇' => ' nián ',
'鵈' => ' ě ',
'鵉' => ' luán ',
'鵊' => ' jiá ',
'鵋' => ' jì ',
'鵌' => ' tú ',
'鵍' => ' huān ',
'鵎' => ' tuǒ ',
'鵏' => ' bǔ ',
'鵐' => ' wú ',
'鵑' => ' juān ',
'鵒' => ' yù ',
'鵓' => ' bó ',
'鵔' => ' jùn ',
'鵕' => ' jùn ',
'鵖' => ' bī ',
'鵗' => ' xī ',
'鵘' => ' jùn ',
'鵙' => ' jú ',
'鵚' => ' tū ',
'鵛' => ' jīng ',
'鵜' => ' tí ',
'鵝' => ' é ',
'鵞' => ' é ',
'鵟' => ' kuáng ',
'鵠' => ' hú ',
'鵡' => ' wǔ ',
'鵢' => ' shēn ',
'鵣' => ' lài ',
'鵤' => ' jiao ',
'鵥' => ' pàn ',
'鵦' => ' lù ',
'鵧' => ' pí ',
'鵨' => ' shū ',
'鵩' => ' fú ',
'鵪' => ' ān ',
'鵫' => ' zhuó ',
'鵬' => ' péng ',
'鵭' => ' qín ',
'鵮' => ' qiān ',
'鵯' => ' bēi ',
'鵰' => ' diāo ',
'鵱' => ' lù ',
'鵲' => ' què ',
'鵳' => ' jiān ',
'鵴' => ' jú ',
'鵵' => ' tù ',
'鵶' => ' yā ',
'鵷' => ' yuān ',
'鵸' => ' qí ',
'鵹' => ' lí ',
'鵺' => ' yè ',
'鵻' => ' zhuī ',
'鵼' => ' kōng ',
'鵽' => ' duò ',
'鵾' => ' kūn ',
'鵿' => ' shēng ',
'鶀' => ' qí ',
'鶁' => ' jīng ',
'鶂' => ' yì ',
'鶃' => ' yì ',
'鶄' => ' jīng ',
'鶅' => ' zī ',
'鶆' => ' lái ',
'鶇' => ' dōng ',
'鶈' => ' qī ',
'鶉' => ' chún ',
'鶊' => ' gēng ',
'鶋' => ' jū ',
'鶌' => ' jué ',
'鶍' => ' yì ',
'鶎' => ' zūn ',
'鶏' => ' jī ',
'鶐' => ' shù ',
'鶑' => ' yīng ',
'鶒' => ' chì ',
'鶓' => ' miáo ',
'鶔' => ' róu ',
'鶕' => ' ān ',
'鶖' => ' qiū ',
'鶗' => ' tí ',
'鶘' => ' hú ',
'鶙' => ' tí ',
'鶚' => ' è ',
'鶛' => ' jiē ',
'鶜' => ' máo ',
'鶝' => ' fú ',
'鶞' => ' chūn ',
'鶟' => ' tú ',
'鶠' => ' yǎn ',
'鶡' => ' hé ',
'鶢' => ' yuán ',
'鶣' => ' piān ',
'鶤' => ' kūn ',
'鶥' => ' méi ',
'鶦' => ' hú ',
'鶧' => ' yīng ',
'鶨' => ' chuàn ',
'鶩' => ' wù ',
'鶪' => ' jú ',
'鶫' => ' dōng ',
'鶬' => ' cāng ',
'鶭' => ' fǎng ',
'鶮' => ' hè ',
'鶯' => ' yīng ',
'鶰' => ' yuán ',
'鶱' => ' xiān ',
'鶲' => ' wēng ',
'鶳' => ' shī ',
'鶴' => ' hè ',
'鶵' => ' chú ',
'鶶' => ' táng ',
'鶷' => ' xiá ',
'鶸' => ' ruò ',
'鶹' => ' liú ',
'鶺' => ' jí ',
'鶻' => ' gú ',
'鶼' => ' jiān ',
'鶽' => ' sǔn ',
'鶾' => ' hàn ',
'鶿' => ' cí ',
'鷀' => ' cí ',
'鷁' => ' yì ',
'鷂' => ' yào ',
'鷃' => ' yàn ',
'鷄' => ' jī ',
'鷅' => ' lì ',
'鷆' => ' tián ',
'鷇' => ' kòu ',
'鷈' => ' tī ',
'鷉' => ' tī ',
'鷊' => ' yì ',
'鷋' => ' tú ',
'鷌' => ' mǎ ',
'鷍' => ' xiāo ',
'鷎' => ' gāo ',
'鷏' => ' tián ',
'鷐' => ' chén ',
'鷑' => ' jí ',
'鷒' => ' tuán ',
'鷓' => ' zhè ',
'鷔' => ' áo ',
'鷕' => ' yǎo ',
'鷖' => ' yī ',
'鷗' => ' ōu ',
'鷘' => ' chì ',
'鷙' => ' zhì ',
'鷚' => ' liù ',
'鷛' => ' yōng ',
'鷜' => ' lǘ ',
'鷝' => ' bì ',
'鷞' => ' shuāng ',
'鷟' => ' zhuó ',
'鷠' => ' yú ',
'鷡' => ' wú ',
'鷢' => ' jué ',
'鷣' => ' yín ',
'鷤' => ' tí ',
'鷥' => ' sī ',
'鷦' => ' jiāo ',
'鷧' => ' yì ',
'鷨' => ' huá ',
'鷩' => ' bì ',
'鷪' => ' yīng ',
'鷫' => ' sù ',
'鷬' => ' huáng ',
'鷭' => ' fán ',
'鷮' => ' jiāo ',
'鷯' => ' liáo ',
'鷰' => ' yàn ',
'鷱' => ' gāo ',
'鷲' => ' jiù ',
'鷳' => ' xián ',
'鷴' => ' xián ',
'鷵' => ' tú ',
'鷶' => ' mǎi ',
'鷷' => ' zūn ',
'鷸' => ' yù ',
'鷹' => ' yīng ',
'鷺' => ' lù ',
'鷻' => ' tuán ',
'鷼' => ' xián ',
'鷽' => ' xué ',
'鷾' => ' yì ',
'鷿' => ' pì ',
'鸀' => ' chǔ ',
'鸁' => ' luó ',
'鸂' => ' xī ',
'鸃' => ' yí ',
'鸄' => ' jī ',
'鸅' => ' zé ',
'鸆' => ' yú ',
'鸇' => ' zhān ',
'鸈' => ' yè ',
'鸉' => ' yáng ',
'鸊' => ' pì ',
'鸋' => ' níng ',
'鸌' => ' hù ',
'鸍' => ' mí ',
'鸎' => ' yīng ',
'鸏' => ' méng ',
'鸐' => ' dí ',
'鸑' => ' yuè ',
'鸒' => ' yù ',
'鸓' => ' lěi ',
'鸔' => ' bǔ ',
'鸕' => ' lú ',
'鸖' => ' hè ',
'鸗' => ' lóng ',
'鸘' => ' shuāng ',
'鸙' => ' yuè ',
'鸚' => ' yīng ',
'鸛' => ' guàn ',
'鸜' => ' qú ',
'鸝' => ' lí ',
'鸞' => ' luán ',
'鸟' => ' niǎo ',
'鸠' => ' jiū ',
'鸡' => ' jī ',
'鸢' => ' yuān ',
'鸣' => ' míng ',
'鸤' => ' shī ',
'鸥' => ' ōu ',
'鸦' => ' yā ',
'鸧' => ' cāng ',
'鸨' => ' bǎo ',
'鸩' => ' zhèn ',
'鸪' => ' gū ',
'鸫' => ' dōng ',
'鸬' => ' lú ',
'鸭' => ' yā ',
'鸮' => ' xiāo ',
'鸯' => ' yāng ',
'鸰' => ' líng ',
'鸱' => ' chī ',
'鸲' => ' qú ',
'鸳' => ' yuān ',
'鸴' => ' xué ',
'鸵' => ' tuó ',
'鸶' => ' sī ',
'鸷' => ' zhì ',
'鸸' => ' ér ',
'鸹' => ' guā ',
'鸺' => ' xiū ',
'鸻' => ' héng ',
'鸼' => ' zhōu ',
'鸽' => ' gē ',
'鸾' => ' luán ',
'鸿' => ' hóng ',
'鹀' => ' wú ',
'鹁' => ' bó ',
'鹂' => ' lí ',
'鹃' => ' juān ',
'鹄' => ' gǔ ',
'鹅' => ' é ',
'鹆' => ' yù ',
'鹇' => ' xián ',
'鹈' => ' tí ',
'鹉' => ' wǔ ',
'鹊' => ' què ',
'鹋' => ' miáo ',
'鹌' => ' ān ',
'鹍' => ' kūn ',
'鹎' => ' bēi ',
'鹏' => ' péng ',
'鹐' => ' qiān ',
'鹑' => ' chún ',
'鹒' => ' gēng ',
'鹓' => ' yuān ',
'鹔' => ' sù ',
'鹕' => ' hú ',
'鹖' => ' hé ',
'鹗' => ' è ',
'鹘' => ' gǔ ',
'鹙' => ' qiū ',
'鹚' => ' cí ',
'鹛' => ' méi ',
'鹜' => ' wù ',
'鹝' => ' yì ',
'鹞' => ' yào ',
'鹟' => ' wēng ',
'鹠' => ' liú ',
'鹡' => ' jí ',
'鹢' => ' yì ',
'鹣' => ' jiān ',
'鹤' => ' hè ',
'鹥' => ' yī ',
'鹦' => ' yīng ',
'鹧' => ' zhè ',
'鹨' => ' liù ',
'鹩' => ' liáo ',
'鹪' => ' jiāo ',
'鹫' => ' jiù ',
'鹬' => ' yù ',
'鹭' => ' lù ',
'鹮' => ' huán ',
'鹯' => ' zhān ',
'鹰' => ' yīng ',
'鹱' => ' hù ',
'鹲' => ' méng ',
'鹳' => ' guàn ',
'鹴' => ' shuāng ',
'鹵' => ' lǔ ',
'鹶' => ' jīn ',
'鹷' => ' líng ',
'鹸' => ' jiǎn ',
'鹹' => ' xián ',
'鹺' => ' cuó ',
'鹻' => ' jiǎn ',
'鹼' => ' jiǎn ',
'鹽' => ' yán ',
'鹾' => ' cuó ',
'鹿' => ' lù ',
'麀' => ' yōu ',
'麁' => ' cū ',
'麂' => ' jǐ ',
'麃' => ' páo ',
'麄' => ' cū ',
'麅' => ' páo ',
'麆' => ' zhù ',
'麇' => ' jūn ',
'麈' => ' zhǔ ',
'麉' => ' jiān ',
'麊' => ' mí ',
'麋' => ' mí ',
'麌' => ' yǔ ',
'麍' => ' liú ',
'麎' => ' chén ',
'麏' => ' jūn ',
'麐' => ' lín ',
'麑' => ' ní ',
'麒' => ' qí ',
'麓' => ' lù ',
'麔' => ' jiù ',
'麕' => ' jūn ',
'麖' => ' jīng ',
'麗' => ' lì ',
'麘' => ' xiāng ',
'麙' => ' xián ',
'麚' => ' jiā ',
'麛' => ' mí ',
'麜' => ' lì ',
'麝' => ' shè ',
'麞' => ' zhāng ',
'麟' => ' lín ',
'麠' => ' jīng ',
'麡' => ' qí ',
'麢' => ' líng ',
'麣' => ' yán ',
'麤' => ' cū ',
'麥' => ' mài ',
'麦' => ' mài ',
'麧' => ' hé ',
'麨' => ' chǎo ',
'麩' => ' fū ',
'麪' => ' miàn ',
'麫' => ' miàn ',
'麬' => ' fū ',
'麭' => ' pào ',
'麮' => ' qù ',
'麯' => ' qū ',
'麰' => ' móu ',
'麱' => ' fū ',
'麲' => ' xiàn ',
'麳' => ' lái ',
'麴' => ' qū ',
'麵' => ' miàn ',
'麶' => ' chi ',
'麷' => ' fēng ',
'麸' => ' fū ',
'麹' => ' qū ',
'麺' => ' miàn ',
'麻' => ' má ',
'麼' => ' me ',
'麽' => ' mó ',
'麾' => ' huī ',
'麿' => ' mí ',
'黀' => ' zōu ',
'黁' => ' nún ',
'黂' => ' fén ',
'黃' => ' huáng ',
'黄' => ' huáng ',
'黅' => ' jīn ',
'黆' => ' guāng ',
'黇' => ' tiān ',
'黈' => ' tǒu ',
'黉' => ' hóng ',
'黊' => ' huà ',
'黋' => ' kuàng ',
'黌' => ' hóng ',
'黍' => ' shǔ ',
'黎' => ' lí ',
'黏' => ' nián ',
'黐' => ' chī ',
'黑' => ' hēi ',
'黒' => ' hēi ',
'黓' => ' yì ',
'黔' => ' qián ',
'黕' => ' dǎn ',
'黖' => ' xì ',
'黗' => ' tūn ',
'默' => ' mò ',
'黙' => ' mò ',
'黚' => ' qián ',
'黛' => ' dài ',
'黜' => ' chù ',
'黝' => ' yǒu ',
'點' => ' diǎn ',
'黟' => ' yī ',
'黠' => ' xiá ',
'黡' => ' yǎn ',
'黢' => ' qū ',
'黣' => ' měi ',
'黤' => ' yǎn ',
'黥' => ' qíng ',
'黦' => ' yuè ',
'黧' => ' lí ',
'黨' => ' dǎng ',
'黩' => ' dú ',
'黪' => ' cǎn ',
'黫' => ' yān ',
'黬' => ' yán ',
'黭' => ' yǎn ',
'黮' => ' dǎn ',
'黯' => ' àn ',
'黰' => ' zhěn ',
'黱' => ' dài ',
'黲' => ' cǎn ',
'黳' => ' yī ',
'黴' => ' méi ',
'黵' => ' zhǎn ',
'黶' => ' yǎn ',
'黷' => ' dú ',
'黸' => ' lú ',
'黹' => ' zhǐ ',
'黺' => ' fěn ',
'黻' => ' fú ',
'黼' => ' fǔ ',
'黽' => ' miǎn ',
'黾' => ' mǐn ',
'黿' => ' yuán ',
'鼀' => ' cù ',
'鼁' => ' qù ',
'鼂' => ' cháo ',
'鼃' => ' wā ',
'鼄' => ' zhū ',
'鼅' => ' zhī ',
'鼆' => ' méng ',
'鼇' => ' áo ',
'鼈' => ' biē ',
'鼉' => ' tuó ',
'鼊' => ' bì ',
'鼋' => ' yuán ',
'鼌' => ' cháo ',
'鼍' => ' tuó ',
'鼎' => ' dǐng ',
'鼏' => ' mì ',
'鼐' => ' nài ',
'鼑' => ' dǐng ',
'鼒' => ' zī ',
'鼓' => ' gǔ ',
'鼔' => ' gǔ ',
'鼕' => ' dōng ',
'鼖' => ' fén ',
'鼗' => ' táo ',
'鼘' => ' yuān ',
'鼙' => ' pí ',
'鼚' => ' chāng ',
'鼛' => ' gāo ',
'鼜' => ' qì ',
'鼝' => ' yuān ',
'鼞' => ' tāng ',
'鼟' => ' tēng ',
'鼠' => ' shǔ ',
'鼡' => ' shǔ ',
'鼢' => ' fén ',
'鼣' => ' fèi ',
'鼤' => ' wén ',
'鼥' => ' bá ',
'鼦' => ' diāo ',
'鼧' => ' tuó ',
'鼨' => ' zhōng ',
'鼩' => ' qú ',
'鼪' => ' shēng ',
'鼫' => ' shí ',
'鼬' => ' yòu ',
'鼭' => ' shí ',
'鼮' => ' tíng ',
'鼯' => ' wú ',
'鼰' => ' jú ',
'鼱' => ' jīng ',
'鼲' => ' hún ',
'鼳' => ' jú ',
'鼴' => ' yǎn ',
'鼵' => ' tū ',
'鼶' => ' sī ',
'鼷' => ' xī ',
'鼸' => ' xiàn ',
'鼹' => ' yǎn ',
'鼺' => ' léi ',
'鼻' => ' bí ',
'鼼' => ' yào ',
'鼽' => ' qiú ',
'鼾' => ' hān ',
'鼿' => ' wù ',
'齀' => ' wù ',
'齁' => ' hōu ',
'齂' => ' xiè ',
'齃' => ' è ',
'齄' => ' zhā ',
'齅' => ' xiù ',
'齆' => ' wèng ',
'齇' => ' zhā ',
'齈' => ' nòng ',
'齉' => ' nàng ',
'齊' => ' qí ',
'齋' => ' zhāi ',
'齌' => ' jì ',
'齍' => ' zī ',
'齎' => ' jī ',
'齏' => ' jī ',
'齐' => ' qí ',
'齑' => ' jī ',
'齒' => ' chǐ ',
'齓' => ' chèn ',
'齔' => ' chèn ',
'齕' => ' hé ',
'齖' => ' yá ',
'齗' => ' yín ',
'齘' => ' xiè ',
'齙' => ' bāo ',
'齚' => ' zé ',
'齛' => ' xiè ',
'齜' => ' chái ',
'齝' => ' chī ',
'齞' => ' yǎn ',
'齟' => ' jǔ ',
'齠' => ' tiáo ',
'齡' => ' líng ',
'齢' => ' líng ',
'齣' => ' chū ',
'齤' => ' quán ',
'齥' => ' xiè ',
'齦' => ' kěn ',
'齧' => ' niè ',
'齨' => ' jiù ',
'齩' => ' yǎo ',
'齪' => ' chuò ',
'齫' => ' yǔn ',
'齬' => ' yǔ ',
'齭' => ' chǔ ',
'齮' => ' yǐ ',
'齯' => ' ní ',
'齰' => ' zé ',
'齱' => ' zōu ',
'齲' => ' qǔ ',
'齳' => ' yǔn ',
'齴' => ' yǎn ',
'齵' => ' óu ',
'齶' => ' è ',
'齷' => ' wò ',
'齸' => ' yì ',
'齹' => ' cī ',
'齺' => ' zōu ',
'齻' => ' diān ',
'齼' => ' chǔ ',
'齽' => ' jìn ',
'齾' => ' yà ',
'齿' => ' chǐ ',
'龀' => ' chèn ',
'龁' => ' hé ',
'龂' => ' yín ',
'龃' => ' jǔ ',
'龄' => ' líng ',
'龅' => ' bāo ',
'龆' => ' tiáo ',
'龇' => ' zī ',
'龈' => ' kěn ',
'龉' => ' yǔ ',
'龊' => ' chuò ',
'龋' => ' qǔ ',
'龌' => ' wò ',
'龍' => ' lóng ',
'龎' => ' páng ',
'龏' => ' gōng ',
'龐' => ' páng ',
'龑' => ' yǎn ',
'龒' => ' lóng ',
'龓' => ' lǒng ',
'龔' => ' gōng ',
'龕' => ' kān ',
'龖' => ' dá ',
'龗' => ' líng ',
'龘' => ' dá ',
'龙' => ' lóng ',
'龚' => ' gōng ',
'龛' => ' kān ',
'龜' => ' guī ',
'龝' => ' qiū ',
'龞' => ' biē ',
'龟' => ' guī ',
'龠' => ' yuè ',
'龡' => ' chuī ',
'龢' => ' hé ',
'龣' => ' jué ',
'龤' => ' xié ',
'龥' => ' yù ',
'鿃' => ' shǎn ',
'鿍' => ' gàng ',
'鿎' => ' tǎ ',
'鿏' => ' mài ',
'鿔' => ' gē ',
'鿕' => ' dān ',
'鿫' => ' ào ',
'鿬' => ' tián ',
'鿭' => ' nǐ ',
'' => ' yè ',
'' => ' zuǒ ',
'' => ' gǔn ',
'' => ' zhòu ',
'' => ' zhòu ',
'' => ' jié ',
'' => ' wāi ',
'' => ' hǎn ',
'' => ' hǎn ',
'' => ' zhòu ',
'' => ' zhòu ',
'' => ' shǒu ',
'' => ' gāng ',
'' => ' kuǎi ',
'' => ' sǒng ',
'' => ' sǒng ',
'' => ' fēng ',
'' => ' gòng ',
'' => ' gāng ',
'' => ' huì ',
'' => ' tà ',
'' => ' jiān ',
'' => ' ēn ',
'' => ' xiǎo ',
'' => ' lóu ',
'' => ' cǎn ',
'' => ' zhú ',
'' => ' chōu ',
'' => ' wǎng ',
'' => ' yáng ',
'' => ' zāi ',
'' => ' bà ',
'' => ' bà ',
'' => ' zhuān ',
'' => ' qióng ',
'' => ' kuì ',
'' => ' kuì ',
'' => ' juǎn ',
'' => ' xīn ',
'' => ' yàn ',
'' => ' qíng ',
'' => ' qíng ',
'' => ' shàn ',
'' => ' yé ',
'' => ' pō ',
'' => ' shàn ',
'' => ' zhuō ',
'' => ' shàn ',
'' => ' jué ',
'' => ' chuài ',
'' => ' zhèng ',
'' => ' chuài ',
'' => ' zhèng ',
'' => ' zhuó ',
'' => ' yíng ',
'' => ' yú ',
'' => ' yìn ',
'' => ' chūn ',
'' => ' qiū ',
'' => ' yú ',
'' => ' téng ',
'' => ' shī ',
'' => ' jiāo ',
'' => ' liè ',
'' => ' jīng ',
'' => ' jú ',
'' => ' tī ',
'' => ' pì ',
'' => ' yǎn ',
'' => ' luán ',
'礼' => ' lǐ ',
'⺁' => ' fǎn ',
);
# 基准测试指南
## 运行基准测试
```bash
php benchmark/run.php
```
## 输出说明
### Strategy Comparison 表格
这个表格对比了三种转换策略的性能:
```
Method Memory Cached Smart Fastest Speedup
sentence 20.5 ms 8.2 ms 12.3 ms Cached 2.5x
fullSentence 18.3 ms 7.5 ms 11.2 ms Cached 2.4x
...
────────────────────────────────────────────────
TOTAL 190.26ms 119.33ms 194.38ms Cached 1.6x
```
#### 列说明:
- **Method**: 测试的方法名称
- **Memory**: 内存优化策略的执行时间
- **Cached**: 缓存策略的执行时间
- **Smart**: 智能策略的执行时间
- **Fastest**: 该方法最快的策略名称(用颜色高亮)
- **Speedup**: 最慢与最快之间的速度差异倍数
#### TOTAL 行:
- 使用分隔线与数据行区分
- 显示所有方法的总执行时间
- 最快的策略会用对应颜色高亮显示
- Speedup 显示总体的加速比
### Performance Summary
性能总结部分提供了策略之间的直观对比:
```
📊 Performance Summary:
• Cached strategy is 1.59x faster than Memory Optimized
• Smart strategy is 0.98x faster than Memory, 1.63x slower than Cached
```
这让您可以快速了解:
- Cached 策略比 Memory Optimized 快多少倍
- Smart 策略相对于其他两种策略的性能表现
### Memory Usage 表格
显示每种策略的内存使用情况:
```
Strategy Peak Memory Description
Memory Optimized 2.5 MB Minimal memory, loads on demand
Cached 15.8 MB All data cached, fastest repeated access
Smart 8.2 MB Adaptive loading based on text complexity
```
## 如何解读结果
1. **选择合适的策略**
- 如果内存有限(如 Web 请求):选择 Memory Optimized
- 如果需要批量处理大量文本:选择 Cached
- 如果需要平衡性能和内存:选择 Smart
2. **Speedup 值的含义**
- 1.5x = 快 50%
- 2.0x = 快 1 倍(速度是原来的 2 倍)
- 3.0x = 快 2 倍(速度是原来的 3 倍)
3. **TOTAL 行的重要性**
- 这是所有方法执行的总时间
- 最能反映实际使用场景的整体性能
- 用于评估策略的整体效果
\ No newline at end of file
parameters:
level: 9
paths:
- src
inferPrivatePropertyTypeFromConstructor: true
checkMissingIterableValueType: false
\ No newline at end of file
<?php
namespace Overtrue\Pinyin;
use ArrayAccess;
use JsonSerializable;
use Stringable;
use function array_map;
use function implode;
use function is_array;
class Collection implements ArrayAccess, JsonSerializable, Stringable
{
public function __construct(protected $items = []) {}
public function join(string $separator = ' '): string
{
return implode($separator, array_map(
fn ($item) => is_array($item) ? '['.implode(', ', $item).']' : $item,
$this->items
));
}
public function map(callable $callback): Collection
{
return new static(array_map($callback, $this->all()));
}
public function all(): array
{
return $this->items;
}
public function toArray(): array
{
return $this->all();
}
public function toJson(int $options = 0): string
{
return json_encode($this->all(), $options);
}
public function __toString(): string
{
return $this->join();
}
public function offsetExists(mixed $offset): bool
{
return isset($this->items[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->items[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
public function offsetUnset(mixed $offset): void
{
unset($this->items[$offset]);
}
public function jsonSerialize(): array
{
return $this->items;
}
}
<?php
namespace Overtrue\Pinyin\Contracts;
use Overtrue\Pinyin\Collection;
use Overtrue\Pinyin\ToneStyle;
interface ConverterInterface
{
public function convert(string $string): Collection;
public function heteronym(bool $asList = false): static;
public function surname(): static;
public function noWords(): static;
public function noCleanup(): static;
public function onlyHans(): static;
public function noAlpha(): static;
public function noNumber(): static;
public function noPunctuation(): static;
public function withToneStyle(string|ToneStyle $toneStyle): static;
public function noTone(): static;
public function useNumberTone(): static;
public function yuToV(): static;
public function yuToU(): static;
public function yuToYu(): static;
public function when(bool $condition, callable $callback): static;
}
<?php
namespace Overtrue\Pinyin;
use InvalidArgumentException;
use Overtrue\Pinyin\Contracts\ConverterInterface;
use Overtrue\Pinyin\Converters\CachedConverter;
use Overtrue\Pinyin\Converters\MemoryOptimizedConverter;
use Overtrue\Pinyin\Converters\SmartConverter;
/**
* Converter 工厂类
*
* 提供不同策略的 Converter 实例
*/
class ConverterFactory
{
public const MEMORY_OPTIMIZED = 'memory';
public const CACHED = 'cached';
public const SMART = 'smart';
/**
* 默认策略
*/
private static string $defaultStrategy = self::MEMORY_OPTIMIZED;
/**
* 创建 Converter 实例
*
* @param string|null $strategy 策略名称,null 则使用默认策略
*/
public static function make(?string $strategy = null): ConverterInterface
{
$strategy = $strategy ?? self::$defaultStrategy;
return match ($strategy) {
self::MEMORY_OPTIMIZED => new MemoryOptimizedConverter,
self::CACHED => new CachedConverter,
self::SMART => new SmartConverter,
default => throw new InvalidArgumentException("Unknown converter strategy: {$strategy}")
};
}
/**
* 设置默认策略
*/
public static function setDefaultStrategy(string $strategy): void
{
if (! in_array($strategy, [self::MEMORY_OPTIMIZED, self::CACHED, self::SMART])) {
throw new InvalidArgumentException("Invalid strategy: {$strategy}");
}
self::$defaultStrategy = $strategy;
}
/**
* 获取当前默认策略
*/
public static function getDefaultStrategy(): string
{
return self::$defaultStrategy;
}
/**
* 根据场景推荐策略
*
* @param array $context 场景上下文
* @return string 推荐的策略
*/
public static function recommend(array $context = []): string
{
// Web 请求场景
if (($context['sapi'] ?? php_sapi_name()) === 'fpm-fcgi') {
return self::MEMORY_OPTIMIZED;
}
// CLI 批处理场景
if (($context['batch'] ?? false) === true) {
return self::CACHED;
}
// 内存限制场景
$memoryLimit = ini_get('memory_limit');
if ($memoryLimit !== '-1' && self::parseBytes($memoryLimit) < 128 * 1024 * 1024) {
return self::MEMORY_OPTIMIZED;
}
// 默认使用智能策略
return self::SMART;
}
/**
* 获取所有可用策略的信息
*/
public static function getStrategiesInfo(): array
{
return [
self::MEMORY_OPTIMIZED => [
'name' => '内存优化',
'class' => MemoryOptimizedConverter::class,
'memory' => '~400KB',
'speed' => '中等',
'use_case' => 'Web请求、内存受限环境',
],
self::CACHED => [
'name' => '全缓存',
'class' => CachedConverter::class,
'memory' => '~4MB',
'speed' => '最快',
'use_case' => '批处理、长时运行进程',
],
self::SMART => [
'name' => '智能',
'class' => SmartConverter::class,
'memory' => '600KB-1.5MB',
'speed' => '快',
'use_case' => '通用场景、自动优化',
],
];
}
private static function parseBytes(string $value): int
{
$value = trim($value);
$last = strtolower($value[-1]);
$value = (int) $value;
return match ($last) {
'g' => $value * 1024 * 1024 * 1024,
'm' => $value * 1024 * 1024,
'k' => $value * 1024,
default => $value,
};
}
}
<?php
namespace Overtrue\Pinyin\Converters;
use Overtrue\Pinyin\Collection;
use Overtrue\Pinyin\Contracts\ConverterInterface;
use Overtrue\Pinyin\ToneStyle;
use function array_values;
use function implode;
use function preg_replace;
use function sprintf;
use function str_contains;
use function str_replace;
abstract class AbstractConverter implements ConverterInterface
{
protected const SEGMENTS_COUNT = 10;
protected const WORDS_PATH = __DIR__.'/../../data/words-%s.php';
protected const CHARS_PATH = __DIR__.'/../../data/chars.php';
protected const SURNAMES_PATH = __DIR__.'/../../data/surnames.php';
protected bool $heteronym = false;
protected bool $heteronymAsList = false;
protected bool $asSurname = false;
protected bool $noWords = false;
protected bool $cleanup = true;
protected string $yuTo = 'v';
protected ToneStyle $toneStyle = ToneStyle::SYMBOL;
protected array $regexps = [
'separator' => '\p{Z}',
'mark' => '\p{M}',
'tab' => "\t",
'number' => '0-9',
'alphabet' => 'a-zA-Z',
'hans' => '\x{3007}\x{2E80}-\x{2FFF}\x{3100}-\x{312F}\x{31A0}-\x{31EF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}',
'punctuation' => '\p{P}',
];
abstract public function convert(string $string): Collection;
public function heteronym(bool $asList = false): static
{
$this->heteronym = true;
$this->heteronymAsList = $asList;
return $this;
}
public function surname(): static
{
$this->asSurname = true;
return $this;
}
public function noWords(): static
{
$this->noWords = true;
return $this;
}
public function noCleanup(): static
{
$this->cleanup = false;
return $this;
}
public function onlyHans(): static
{
$this->regexps['hans'] = '\x{3007}\x{2E80}-\x{2FFF}\x{3100}-\x{312F}\x{31A0}-\x{31EF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FFF}\x{F900}-\x{FAFF}';
unset($this->regexps['alphabet']);
unset($this->regexps['number']);
unset($this->regexps['punctuation']);
return $this;
}
public function noAlpha(): static
{
unset($this->regexps['alphabet']);
return $this;
}
public function noNumber(): static
{
unset($this->regexps['number']);
return $this;
}
public function noPunctuation(): static
{
unset($this->regexps['punctuation']);
return $this;
}
/**
* 设置声调风格
*
* @param string|ToneStyle $toneStyle 声调风格,可以是字符串或 ToneStyle 枚举
*/
public function withToneStyle(string|ToneStyle $toneStyle): static
{
$this->toneStyle = $toneStyle instanceof ToneStyle ? $toneStyle : ToneStyle::from($toneStyle);
return $this;
}
public function noTone(): static
{
$this->toneStyle = ToneStyle::NONE;
return $this;
}
public function useNumberTone(): static
{
$this->toneStyle = ToneStyle::NUMBER;
return $this;
}
public function yuToV(): static
{
$this->yuTo = 'v';
return $this;
}
public function yuToU(): static
{
$this->yuTo = 'u';
return $this;
}
public function yuToYu(): static
{
$this->yuTo = 'yu';
return $this;
}
public function when(bool $condition, callable $callback): static
{
if ($condition) {
$callback($this);
}
return $this;
}
// 共享的辅助方法
protected function preprocessString(string $string): string
{
// 把原有的数字和汉字分离,避免拼音转换时被误作声调
$string = preg_replace_callback('~[a-z0-9_-]+~i', function ($matches) {
return "\t".$matches[0];
}, $string);
// 过滤掉不保留的字符
if ($this->cleanup) {
$string = preg_replace(sprintf('~[^%s]~u', implode($this->regexps)), '', $string);
}
return $string;
}
protected function split(string $item): Collection
{
$items = array_values(array_filter(preg_split('/\s+/i', $item)));
foreach ($items as $index => $item) {
$items[$index] = $this->formatTone($item, $this->toneStyle->value);
}
return new Collection($items);
}
protected function formatTone(string $pinyin, string $style): string
{
if ($style === ToneStyle::SYMBOL->value) {
return $pinyin;
}
// @formatter:off
$replacements = [
'ɑ' => ['a', 5], 'ü' => ['v', 5], 'üē' => ['ue', 1], 'üé' => ['ue', 2], 'üě' => ['ue', 3], 'üè' => ['ue', 4],
'ā' => ['a', 1], 'ē' => ['e', 1], 'ī' => ['i', 1], 'ō' => ['o', 1], 'ū' => ['u', 1], 'ǖ' => ['v', 1],
'á' => ['a', 2], 'é' => ['e', 2], 'í' => ['i', 2], 'ó' => ['o', 2], 'ú' => ['u', 2], 'ǘ' => ['v', 2],
'ǎ' => ['a', 3], 'ě' => ['e', 3], 'ǐ' => ['i', 3], 'ǒ' => ['o', 3], 'ǔ' => ['u', 3], 'ǚ' => ['v', 3],
'à' => ['a', 4], 'è' => ['e', 4], 'ì' => ['i', 4], 'ò' => ['o', 4], 'ù' => ['u', 4], 'ǜ' => ['v', 4],
];
// @formatter:on
foreach ($replacements as $unicode => $replacement) {
if (str_contains($pinyin, $unicode)) {
$umlaut = $replacement[0];
if ($this->yuTo !== 'v' && $umlaut === 'v') {
$umlaut = $this->yuTo;
}
$pinyin = str_replace($unicode, $umlaut, $pinyin);
if ($style === ToneStyle::NUMBER->value) {
$pinyin .= $replacement[1];
}
}
}
return $pinyin;
}
}
<?php
namespace Overtrue\Pinyin\Converters;
use Overtrue\Pinyin\Collection;
use function array_map;
use function mb_strlen;
use function mb_substr;
use function str_starts_with;
/**
* 缓存版本的转换器
*
* 特点:
* - 缓存所有词典数据
* - 更快的重复转换速度
* - 适合批处理和长时运行的进程
* - 内存占用较高(~4MB)
*/
class CachedConverter extends AbstractConverter
{
private static ?array $charsCache = null;
private static ?array $surnamesCache = null;
private static array $wordsCache = [];
private static ?array $fullDictionary = null;
public function convert(string $string): Collection
{
$string = $this->preprocessString($string);
return $this->determineConversionStrategy($string);
}
private function determineConversionStrategy(string $string): Collection
{
// 多音字处理
if ($this->heteronym) {
return $this->convertAsChars($string, true);
}
// 仅字符转换
if ($this->noWords) {
return $this->convertAsChars($string);
}
// 替换姓氏
if ($this->asSurname) {
$string = $this->convertSurname($string);
}
// 使用缓存的完整词典
$dictionary = $this->getFullDictionary();
$string = strtr($string, $dictionary);
return $this->split($string);
}
private function getFullDictionary(): array
{
if (self::$fullDictionary === null) {
self::$fullDictionary = [];
// 按顺序加载,保证长词优先
for ($i = 0; $i < self::SEGMENTS_COUNT; $i++) {
self::$fullDictionary += $this->loadWordsSegment($i);
}
}
return self::$fullDictionary;
}
private function loadWordsSegment(int $index): array
{
if (! isset(self::$wordsCache[$index])) {
self::$wordsCache[$index] = require sprintf(self::WORDS_PATH, $index);
}
return self::$wordsCache[$index];
}
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
{
self::$charsCache ??= require self::CHARS_PATH;
$chars = mb_str_split($string);
$items = [];
foreach ($chars as $char) {
if (isset(self::$charsCache[$char])) {
if ($polyphonic) {
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), self::$charsCache[$char]);
if ($this->heteronymAsList) {
$items[] = [$char => $pinyin];
} else {
$items[$char] = $pinyin;
}
} else {
$items[$char] = $this->formatTone(self::$charsCache[$char][0], $this->toneStyle->value);
}
}
}
return new Collection($items);
}
protected function convertSurname(string $name): string
{
self::$surnamesCache ??= require self::SURNAMES_PATH;
foreach (self::$surnamesCache as $surname => $pinyin) {
if (str_starts_with($name, $surname)) {
return $pinyin.mb_substr($name, mb_strlen($surname));
}
}
return $name;
}
/**
* 清理缓存(可选)
*/
public static function clearCache(): void
{
self::$charsCache = null;
self::$surnamesCache = null;
self::$wordsCache = [];
self::$fullDictionary = null;
}
}
<?php
namespace Overtrue\Pinyin\Converters;
use Overtrue\Pinyin\Collection;
use function array_map;
use function mb_strlen;
use function mb_substr;
use function str_starts_with;
/**
* 内存优化版本的转换器
*
* 特点:
* - 最小内存占用(峰值 ~400KB)
* - 每次加载一个段,用完即释放
* - 适合 Web 请求和内存受限环境
*/
class MemoryOptimizedConverter extends AbstractConverter
{
public function convert(string $string): Collection
{
$string = $this->preprocessString($string);
return $this->determineConversionStrategy($string);
}
private function determineConversionStrategy(string $string): Collection
{
// 多音字处理
if ($this->heteronym) {
return $this->convertAsChars($string, true);
}
// 仅字符转换
if ($this->noWords) {
return $this->convertAsChars($string);
}
// 替换姓氏
if ($this->asSurname) {
$string = $this->convertSurname($string);
}
// 按顺序加载词典段(长词优先)
for ($i = 0; $i < self::SEGMENTS_COUNT; $i++) {
$string = strtr($string, require sprintf(self::WORDS_PATH, $i));
}
return $this->split($string);
}
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
{
$map = require self::CHARS_PATH;
$chars = mb_str_split($string);
$items = [];
foreach ($chars as $char) {
if (isset($map[$char])) {
if ($polyphonic) {
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), $map[$char]);
if ($this->heteronymAsList) {
$items[] = [$char => $pinyin];
} else {
$items[$char] = $pinyin;
}
} else {
$items[$char] = $this->formatTone($map[$char][0], $this->toneStyle->value);
}
}
}
return new Collection($items);
}
protected function convertSurname(string $name): string
{
static $surnames = null;
$surnames ??= require self::SURNAMES_PATH;
foreach ($surnames as $surname => $pinyin) {
if (str_starts_with($name, $surname)) {
return $pinyin.mb_substr($name, mb_strlen($surname));
}
}
return $name;
}
}
<?php
namespace Overtrue\Pinyin\Converters;
use Overtrue\Pinyin\Collection;
use function array_map;
use function mb_strlen;
use function mb_substr;
use function str_starts_with;
/**
* 智能版本的转换器
*
* 特点:
* - 根据文本长度智能选择策略
* - 短文本跳过不必要的长词词典
* - 缓存小型常用数据
* - 平衡内存和性能
*/
class SmartConverter extends AbstractConverter
{
private static ?array $surnamesCache = null;
private static array $segmentCache = [];
private static ?array $fullDictionary = null;
private const MAX_CACHE_SEGMENTS = 3;
public function convert(string $string): Collection
{
$string = $this->preprocessString($string);
return $this->determineConversionStrategy($string);
}
private function determineConversionStrategy(string $string): Collection
{
// 多音字处理
if ($this->heteronym) {
return $this->convertAsChars($string, true);
}
// 仅字符转换
if ($this->noWords) {
return $this->convertAsChars($string);
}
// 替换姓氏
if ($this->asSurname) {
$string = $this->convertSurname($string);
}
// 智能加载词典
$string = $this->smartConvert($string);
return $this->split($string);
}
private function smartConvert(string $string): string
{
// 使用和CachedConverter相同的逻辑,但保持缓存机制
$dictionary = $this->getFullDictionary();
return strtr($string, $dictionary);
}
private function getFullDictionary(): array
{
if (self::$fullDictionary === null) {
self::$fullDictionary = [];
// 按顺序加载,保证长词优先
for ($i = 0; $i < self::SEGMENTS_COUNT; $i++) {
self::$fullDictionary += $this->loadSegmentWithCache($i);
}
}
return self::$fullDictionary;
}
private function loadSegmentWithCache(int $index): array
{
if (! isset(self::$segmentCache[$index])) {
// 缓存数量限制
if (count(self::$segmentCache) >= self::MAX_CACHE_SEGMENTS) {
// 移除最早的缓存(简单的FIFO)
array_shift(self::$segmentCache);
}
self::$segmentCache[$index] = require sprintf(self::WORDS_PATH, $index);
}
return self::$segmentCache[$index];
}
protected function convertAsChars(string $string, bool $polyphonic = false): Collection
{
// 字符表太大,不缓存
$map = require self::CHARS_PATH;
$chars = mb_str_split($string);
$items = [];
foreach ($chars as $char) {
if (isset($map[$char])) {
if ($polyphonic) {
$pinyin = array_map(fn ($pinyin) => $this->formatTone($pinyin, $this->toneStyle->value), $map[$char]);
if ($this->heteronymAsList) {
$items[] = [$char => $pinyin];
} else {
$items[$char] = $pinyin;
}
} else {
$items[$char] = $this->formatTone($map[$char][0], $this->toneStyle->value);
}
}
}
return new Collection($items);
}
protected function convertSurname(string $name): string
{
// 姓氏表很小,可以缓存
self::$surnamesCache ??= require self::SURNAMES_PATH;
foreach (self::$surnamesCache as $surname => $pinyin) {
if (str_starts_with($name, $surname)) {
return $pinyin.mb_substr($name, mb_strlen($surname));
}
}
return $name;
}
/**
* 清理缓存
*/
public static function clearCache(): void
{
self::$surnamesCache = null;
self::$segmentCache = [];
self::$fullDictionary = null;
}
}
<?php
namespace Overtrue\Pinyin;
use InvalidArgumentException;
use Overtrue\Pinyin\Contracts\ConverterInterface;
use Overtrue\Pinyin\Converters\CachedConverter;
use Overtrue\Pinyin\Converters\SmartConverter;
use function is_numeric;
use function mb_substr;
use function method_exists;
/**
* @method static ConverterInterface surname()
* @method static ConverterInterface noWords()
* @method static ConverterInterface onlyHans()
* @method static ConverterInterface noAlpha()
* @method static ConverterInterface noNumber()
* @method static ConverterInterface noCleanup()
* @method static ConverterInterface noPunctuation()
* @method static ConverterInterface noTone()
* @method static ConverterInterface useNumberTone()
* @method static ConverterInterface yuToV()
* @method static ConverterInterface yuToU()
* @method static ConverterInterface withToneStyle(string|ToneStyle $toneStyle = 'symbol')
* @method static Collection convert(string $string, callable $beforeSplit = null)
*/
class Pinyin
{
/**
* 当前使用的转换策略
*/
private static ?string $converterStrategy = null;
public static function name(string $name, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::converter()->surname()->withToneStyle($toneStyle)->convert($name);
}
public static function passportName(string $name, string|ToneStyle $toneStyle = ToneStyle::NONE): Collection
{
return self::converter()->surname()->yuToYu()->withToneStyle($toneStyle)->convert($name);
}
public static function phrase(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::converter()->noPunctuation()->withToneStyle($toneStyle)->convert($string);
}
public static function sentence(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::converter()->withToneStyle($toneStyle)->convert($string);
}
public static function fullSentence(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::converter()->noCleanup()->withToneStyle($toneStyle)->convert($string);
}
public static function heteronym(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL, bool $asList = false): Collection
{
return self::converter()->heteronym($asList)->withToneStyle($toneStyle)->convert($string);
}
public static function heteronymAsList(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::heteronym($string, $toneStyle, true);
}
public static function chars(string $string, string|ToneStyle $toneStyle = ToneStyle::SYMBOL): Collection
{
return self::converter()->onlyHans()->noWords()->withToneStyle($toneStyle)->convert($string);
}
public static function permalink(string $string, string $delimiter = '-'): string
{
if (! in_array($delimiter, ['_', '-', '.', ''], true)) {
throw new InvalidArgumentException("Delimiter must be one of: '_', '-', '', '.'.");
}
return self::converter()->noPunctuation()->noTone()->convert($string)->join($delimiter);
}
public static function nameAbbr(string $string): Collection
{
return self::abbr($string, true);
}
public static function abbr(string $string, bool $asName = false, bool $preserveEnglishWords = false): Collection
{
return self::converter()->noTone()
->noPunctuation()
->when($asName, fn ($c) => $c->surname())
->convert($string)
->map(function ($pinyin) use ($string, $preserveEnglishWords) {
// 如果内容在原字符串中,则直接返回
if ($preserveEnglishWords && str_contains($string, $pinyin)) {
return $pinyin;
}
// 常用于电影名称入库索引处理,例如:《晚娘2012》-> WN2012
return is_numeric($pinyin) || preg_match('/\d{2,}/', $pinyin) ? $pinyin : mb_substr($pinyin, 0, 1);
});
}
/**
* 获取 Converter 实例
*
* @param string|null $strategy 指定策略,null 则使用默认策略
*/
public static function converter(?string $strategy = null): ConverterInterface
{
// 使用新的工厂模式
$strategy = $strategy ?? self::$converterStrategy;
return ConverterFactory::make($strategy);
}
/**
* 设置默认转换策略
*
* @param string $strategy 策略名称
*/
public static function setConverterStrategy(string $strategy): void
{
self::$converterStrategy = $strategy;
ConverterFactory::setDefaultStrategy($strategy);
}
/**
* 使用内存优化策略
*/
public static function useMemoryOptimized(): void
{
self::setConverterStrategy(ConverterFactory::MEMORY_OPTIMIZED);
}
/**
* 使用缓存策略
*/
public static function useCached(): void
{
self::setConverterStrategy(ConverterFactory::CACHED);
}
/**
* 使用智能策略
*/
public static function useSmart(): void
{
self::setConverterStrategy(ConverterFactory::SMART);
}
/**
* 根据运行环境自动选择策略
*/
public static function useAutoStrategy(): void
{
$strategy = ConverterFactory::recommend();
self::setConverterStrategy($strategy);
}
/**
* 清理所有转换器的缓存
*/
public static function clearCache(): void
{
CachedConverter::clearCache();
SmartConverter::clearCache();
}
public static function __callStatic(string $name, array $arguments)
{
$converter = self::converter();
if (method_exists($converter, $name)) {
return $converter->$name(...$arguments);
}
throw new InvalidArgumentException("Method {$name} does not exist.");
}
}
<?php
namespace Overtrue\Pinyin;
/**
* 拼音声调风格枚举
*/
enum ToneStyle: string
{
/**
* 符号风格:zhōng
*/
case SYMBOL = 'symbol';
/**
* 数字风格:zhong1
*/
case NUMBER = 'number';
/**
* 无声调:zhong
*/
case NONE = 'none';
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论