This commit is contained in:
Ethanfly 2026-01-09 11:00:31 +08:00
commit 98e2950b2e
49 changed files with 11015 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode

202
README.md Normal file
View File

@ -0,0 +1,202 @@
# 智能周报生成器
基于阿里云千问Max大模型的智能周报生成应用使用 Laravel 框架开发。
## ✨ 功能特性
- 📝 **任务管理**:动态添加/删除任务列表
- 🖼️ **图片上传**:支持为每个任务上传截图(可选)
- 🤖 **AI生成**调用千问Max模型智能生成专业周报
- 📄 **Markdown预览**:实时预览生成的周报内容
- ⬇️ **多格式下载**:支持 Markdown 和 PDF 格式下载
- 🎨 **精美界面**:现代化深色主题,响应式设计
## 🛠️ 技术栈
- **后端**PHP 8.0+ / Laravel 8
- **前端**:原生 HTML/CSS/JavaScript
- **AI模型**:阿里云通义千问 Max (qwen-max)
- **PDF生成**DomPDF
## 📋 环境要求
- PHP >= 8.0
- Composer
- 以下 PHP 扩展:
- BCMath
- Ctype
- cURL
- DOM
- Fileinfo
- JSON
- Mbstring
- OpenSSL
- PDO
- Tokenizer
- XML
## 🚀 快速开始
### Windows 用户
1. **双击运行安装脚本**
```
install.bat
```
2. **启动开发服务器**
```
start.bat
```
3. **访问应用**
打开浏览器访问http://localhost:8000
### 手动安装
1. **克隆项目**
```bash
cd E:\Workspace\weekreport
```
2. **安装依赖**
```bash
composer install
```
3. **配置环境**
```bash
# 复制环境配置文件
copy env.txt .env
# 或在 Linux/Mac 上
cp env.txt .env
```
4. **生成应用密钥**
```bash
php artisan key:generate
```
5. **创建存储链接**
```bash
php artisan storage:link
```
6. **启动服务器**
```bash
php artisan serve
```
## ⚙️ 配置说明
### API 配置
`.env` 文件中配置千问 API
```env
# 千问Max API配置
QWEN_API_KEY=sk-7ea0fac260574e4ea22c9de728e7bff9
QWEN_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
QWEN_MODEL=qwen-max
```
### 数据库配置(可选)
默认使用 SQLite如需使用 MySQL
```env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=weekreport
DB_USERNAME=your_username
DB_PASSWORD=your_password
```
## 📖 使用指南
1. **设置周报信息**
- 选择周报的开始和结束日期
- 填写作者姓名(可选)
2. **添加任务**
- 点击"添加新任务"按钮
- 填写任务描述
- 可选:上传任务相关截图
3. **生成周报**
- 点击"生成周报"按钮
- 等待 AI 生成专业周报
4. **预览与下载**
- 在右侧预览区查看生成结果
- 点击下载按钮获取 Markdown 或 PDF 格式
## 📁 项目结构
```
weekreport/
├── app/
│ ├── Http/
│ │ └── Controllers/
│ │ └── WeekReportController.php # 主控制器
│ └── Providers/
├── config/
│ ├── app.php
│ ├── services.php # API配置
│ └── ...
├── public/
│ └── index.php # 入口文件
├── resources/
│ └── views/
│ └── weekreport/
│ └── index.blade.php # 前端页面
├── routes/
│ └── web.php # 路由定义
├── storage/
├── .env # 环境配置
├── composer.json
├── install.bat # Windows安装脚本
├── start.bat # Windows启动脚本
└── README.md
```
## 🔧 API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/` | 显示周报生成页面 |
| POST | `/upload` | 上传任务图片 |
| POST | `/generate` | 生成周报 |
| POST | `/download/markdown` | 下载 Markdown 格式 |
| POST | `/download/pdf` | 下载 PDF 格式 |
## 🐛 常见问题
### Q: 生成周报时报错 "API Key 未配置"
确保 `.env` 文件中正确配置了 `QWEN_API_KEY`
### Q: 图片上传后不显示
运行以下命令创建存储链接:
```bash
php artisan storage:link
```
### Q: PDF 中文乱码
确保系统安装了中文字体DomPDF 会自动使用系统字体。
## 📄 许可证
MIT License
## 🙏 致谢
- [阿里云百炼](https://bailian.console.aliyun.com/) - 提供千问大模型 API
- [Laravel](https://laravel.com/) - PHP Web 框架
- [DomPDF](https://github.com/dompdf/dompdf) - PDF 生成库
- [Remix Icon](https://remixicon.com/) - 图标库

21
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
//
}
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@ -0,0 +1,429 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Barryvdh\DomPDF\Facade\Pdf;
use Parsedown;
class WeekReportController extends Controller
{
/**
* 显示周报生成页面
*/
public function index()
{
return view('weekreport.index');
}
/**
* 上传任务图片
*/
public function uploadImage(Request $request)
{
$request->validate([
'image' => 'required|image|mimes:jpeg,png,jpg,gif,webp|max:10240',
]);
try {
$file = $request->file('image');
$filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
// 存储到public目录
$path = $file->storeAs('uploads', $filename, 'public');
return response()->json([
'success' => true,
'path' => $path,
'url' => asset('storage/' . $path),
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => '图片上传失败: ' . $e->getMessage(),
], 500);
}
}
/**
* 生成周报
*/
public function generate(Request $request)
{
$request->validate([
'tasks' => 'required|array|min:1',
'tasks.*.description' => 'required|string|max:2000',
'tasks.*.image' => 'nullable|string',
'week_start' => 'nullable|date',
'week_end' => 'nullable|date',
'author' => 'nullable|string|max:100',
'next_week_tasks' => 'nullable|array',
'next_week_tasks.*' => 'nullable|string|max:2000',
]);
$tasks = $request->input('tasks');
$weekStart = $request->input('week_start') ?? date('Y-m-d', strtotime('monday this week'));
$weekEnd = $request->input('week_end') ?? date('Y-m-d', strtotime('sunday this week'));
$author = (string) ($request->input('author') ?? '');
$nextWeekTasks = $request->input('next_week_tasks', []);
// 构建提示词
$taskList = $this->buildTaskList($tasks);
$nextWeekList = $this->buildNextWeekTaskList($nextWeekTasks);
$prompt = $this->buildPrompt($taskList, $weekStart, $weekEnd, $author, $nextWeekList);
try {
// 调用千问API
$reportContent = $this->callQwenApi($prompt);
// 插入图片到报告中
$reportWithImages = $this->insertImagesToReport($reportContent, $tasks);
return response()->json([
'success' => true,
'report' => $reportWithImages,
'raw_report' => $reportContent,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => '生成周报失败: ' . $e->getMessage(),
], 500);
}
}
/**
* 构建任务列表文本
*/
private function buildTaskList(array $tasks): string
{
$list = [];
foreach ($tasks as $index => $task) {
$num = $index + 1;
$desc = trim($task['description']);
$hasImage = !empty($task['image']) ? '(有截图)' : '';
$list[] = "{$num}. {$desc}{$hasImage}";
}
return implode("\n", $list);
}
/**
* 构建下周任务列表文本
*/
private function buildNextWeekTaskList(array $tasks): string
{
if (empty($tasks)) {
return '';
}
$list = [];
foreach ($tasks as $index => $desc) {
if (!empty(trim($desc))) {
$num = $index + 1;
$list[] = "{$num}. " . trim($desc);
}
}
return implode("\n", $list);
}
/**
* 构建完整提示词
*/
private function buildPrompt(string $taskList, string $weekStart, string $weekEnd, string $author, string $nextWeekList = ''): string
{
$authorInfo = $author ? "作者:{$author}" : "";
// 根据是否有下周计划来调整要求
$nextWeekSection = '';
$nextWeekRequirement = '';
if (!empty(trim($nextWeekList))) {
$nextWeekSection = "\n\n下周计划任务(用户提供):\n{$nextWeekList}";
$nextWeekRequirement = "\n8. 添加\"下周工作计划\"部分,根据用户提供的下周任务列表进行专业化描述和扩展";
} else {
$nextWeekRequirement = "\n8. 不要添加下周工作计划部分(用户未提供)";
}
return <<<PROMPT
你是一位专业的周报撰写助手。请根据以下任务列表生成一份专业、详细的Markdown格式工作周报。
周报时间范围:{$weekStart} {$weekEnd}
{$authorInfo}
本周完成的任务:
{$taskList}{$nextWeekSection}
要求:
1. 使用Markdown格式输出
2. 包含周报标题(使用一级标题)
3. 包含时间范围和作者信息(如果提供)
4. 对每个任务进行合理的分类和扩展描述
5. 每个任务需要包含:
- 任务名称
- 任务详细描述(根据原始描述进行适当扩展和专业化表述)
- 完成情况
6. 添加一个"本周工作总结"部分
7. 【重要】关于图片占位符:只有任务描述中明确标注了"(有截图)"的任务,才在该任务描述末尾单独一行添加占位符"[图片占位符-任务X]"X为任务序号。没有标注"(有截图)"的任务,绝对不要添加任何图片占位符{$nextWeekRequirement}
9. 语言专业、简洁、条理清晰
请直接输出Markdown格式的周报内容不要有任何额外说明。
PROMPT;
}
/**
* 调用千问API
*/
private function callQwenApi(string $prompt): string
{
$apiKey = config('services.qwen.api_key');
$apiUrl = config('services.qwen.api_url');
$model = config('services.qwen.model');
if (empty($apiKey)) {
throw new \Exception('API Key 未配置');
}
$response = Http::timeout(120)
->withoutVerifying()
->withHeaders([
'Authorization' => "Bearer {$apiKey}",
'Content-Type' => 'application/json',
])
->post($apiUrl, [
'model' => $model,
'messages' => [
[
'role' => 'system',
'content' => '你是一位专业的工作周报撰写助手,擅长将简单的任务描述转化为专业、详细的周报内容。'
],
[
'role' => 'user',
'content' => $prompt
]
],
'temperature' => 0.7,
'max_tokens' => 4000,
]);
if (!$response->successful()) {
$error = $response->json('error.message') ?? $response->body();
throw new \Exception("API调用失败: {$error}");
}
$data = $response->json();
if (!isset($data['choices'][0]['message']['content'])) {
throw new \Exception('API返回数据格式错误');
}
return $data['choices'][0]['message']['content'];
}
/**
* 在报告中插入图片
*/
private function insertImagesToReport(string $report, array $tasks): string
{
foreach ($tasks as $index => $task) {
$num = $index + 1;
// 支持多种占位符格式
$placeholders = [
"[图片占位符-任务{$num}]",
"[图片占位符-{$num}]",
"[图片占位符{$num}]",
"【图片占位符-任务{$num}",
"【图片占位符-{$num}",
"【图片占位符{$num}",
];
if (!empty($task['image'])) {
$imageUrl = asset('storage/' . $task['image']);
$imageMarkdown = "\n\n![任务{$num}截图]({$imageUrl})\n";
foreach ($placeholders as $placeholder) {
$report = str_replace($placeholder, $imageMarkdown, $report);
}
} else {
// 没有图片时移除所有占位符
foreach ($placeholders as $placeholder) {
$report = str_replace($placeholder, '', $report);
}
}
}
// 清理所有未使用的占位符(各种格式)
$report = preg_replace('/\[图片占位符[-]?任务?\d*\]/', '', $report);
$report = preg_replace('/【图片占位符[-]?任务?\d*】/', '', $report);
$report = preg_replace('/\[?图片占位符[-—]?\d+\]?/', '', $report);
// 清理可能产生的多余空行
$report = preg_replace('/\n{3,}/', "\n\n", $report);
return $report;
}
/**
* 下载Markdown格式
*/
public function downloadMarkdown(Request $request)
{
$request->validate([
'content' => 'required|string',
'filename' => 'nullable|string|max:100',
]);
$content = $request->input('content');
$filename = $request->input('filename', '周报_' . date('Y-m-d')) . '.md';
return response($content)
->header('Content-Type', 'text/markdown; charset=utf-8')
->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
}
/**
* 下载PDF格式
*/
public function downloadPdf(Request $request)
{
$request->validate([
'content' => 'required|string',
'filename' => 'nullable|string|max:100',
]);
$markdownContent = $request->input('content');
$filename = $request->input('filename', '周报_' . date('Y-m-d')) . '.pdf';
// 将Markdown转换为HTML
$parsedown = new Parsedown();
$htmlContent = $parsedown->text($markdownContent);
// 添加样式
$styledHtml = $this->wrapHtmlWithStyle($htmlContent);
// 生成PDF - 启用远程字体和图片
$pdf = Pdf::loadHTML($styledHtml);
$pdf->setPaper('A4', 'portrait');
$pdf->setOption('isRemoteEnabled', true);
$pdf->setOption('defaultFont', 'sans-serif');
return $pdf->download($filename);
}
/**
* 为HTML添加样式
*/
private function wrapHtmlWithStyle(string $html): string
{
return <<<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
@font-face {
font-family: 'NotoSansSC';
src: url('https://fonts.gstatic.com/s/notosanssc/v36/k3kCo84MPvpLmixcA63oeAL7Iqp5IZJF9bmaG9_FnYg.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'NotoSansSC', 'SimHei', 'Microsoft YaHei', sans-serif;
font-size: 14px;
line-height: 1.8;
color: #333;
padding: 40px;
max-width: 800px;
margin: 0 auto;
}
h1 {
font-size: 24px;
color: #1a1a1a;
border-bottom: 3px solid #4a90d9;
padding-bottom: 10px;
margin-bottom: 20px;
}
h2 {
font-size: 18px;
color: #2c3e50;
margin-top: 25px;
margin-bottom: 15px;
padding-left: 10px;
border-left: 4px solid #4a90d9;
}
h3 {
font-size: 16px;
color: #34495e;
margin-top: 20px;
margin-bottom: 10px;
}
p {
margin-bottom: 12px;
text-align: justify;
}
ul, ol {
margin-left: 25px;
margin-bottom: 15px;
}
li {
margin-bottom: 8px;
}
img {
max-width: 100%;
height: auto;
margin: 15px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
code {
background-color: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
font-family: Consolas, monospace;
}
pre {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin-bottom: 15px;
}
blockquote {
border-left: 4px solid #ddd;
padding-left: 15px;
color: #666;
margin: 15px 0;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #f5f5f5;
}
hr {
border: none;
border-top: 1px solid #ddd;
margin: 20px 0;
}
</style>
</head>
<body>
{$html}
</body>
</html>
HTML;
}
}

59
app/Http/Kernel.php Normal file
View File

@ -0,0 +1,59 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null ...$guards
* @return mixed
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
//
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
public const HOME = '/';
public function boot(): void
{
$this->configureRateLimiting();
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
protected function configureRateLimiting(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
}

19
artisan Normal file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
$kernel->terminate($input, $status);
exit($status);

24
bootstrap/app.php Normal file
View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Foundation\Application;
$app = new Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;

2
bootstrap/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

67
composer.json Normal file
View File

@ -0,0 +1,67 @@
{
"name": "weekreport/app",
"type": "project",
"description": "周报生成应用 - 基于千问Max模型",
"keywords": ["laravel", "weekreport", "qwen"],
"license": "MIT",
"require": {
"php": "^8.0",
"guzzlehttp/guzzle": "^7.5",
"laravel/framework": "^8.83",
"laravel/tinker": "^2.7",
"barryvdh/laravel-dompdf": "^2.0",
"erusev/parsedown": "^1.7"
},
"require-dev": {
"fakerphp/faker": "^1.21",
"mockery/mockery": "^1.5",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
},
"audit": {
"block-insecure": false
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

7790
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

84
config/app.php Normal file
View File

@ -0,0 +1,84 @@
<?php
return [
'name' => env('APP_NAME', '周报生成器'),
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false),
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
'timezone' => 'Asia/Shanghai',
'locale' => 'zh_CN',
'fallback_locale' => 'en',
'faker_locale' => 'zh_CN',
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
'providers' => [
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
// Application Service Providers
App\Providers\AppServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
'Blade' => Illuminate\Support\Facades\Blade::class,
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class,
'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class,
'Date' => Illuminate\Support\Facades\Date::class,
'DB' => Illuminate\Support\Facades\DB::class,
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
'Event' => Illuminate\Support\Facades\Event::class,
'File' => Illuminate\Support\Facades\File::class,
'Gate' => Illuminate\Support\Facades\Gate::class,
'Hash' => Illuminate\Support\Facades\Hash::class,
'Http' => Illuminate\Support\Facades\Http::class,
'Js' => Illuminate\Support\Js::class,
'Lang' => Illuminate\Support\Facades\Lang::class,
'Log' => Illuminate\Support\Facades\Log::class,
'Mail' => Illuminate\Support\Facades\Mail::class,
'Notification' => Illuminate\Support\Facades\Notification::class,
'Password' => Illuminate\Support\Facades\Password::class,
'Queue' => Illuminate\Support\Facades\Queue::class,
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
'Redirect' => Illuminate\Support\Facades\Redirect::class,
'Request' => Illuminate\Support\Facades\Request::class,
'Response' => Illuminate\Support\Facades\Response::class,
'Route' => Illuminate\Support\Facades\Route::class,
'Schema' => Illuminate\Support\Facades\Schema::class,
'Session' => Illuminate\Support\Facades\Session::class,
'Storage' => Illuminate\Support\Facades\Storage::class,
'Str' => Illuminate\Support\Str::class,
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'PDF' => Barryvdh\DomPDF\Facade\Pdf::class,
],
];

19
config/cache.php Normal file
View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Support\Str;
return [
'default' => env('CACHE_DRIVER', 'file'),
'stores' => [
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'array' => [
'driver' => 'array',
'serialize' => false,
],
],
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

12
config/cors.php Normal file
View File

@ -0,0 +1,12 @@
<?php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

17
config/database.php Normal file
View File

@ -0,0 +1,17 @@
<?php
use Illuminate\Support\Str;
return [
'default' => env('DB_CONNECTION', 'sqlite'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
],
'migrations' => 'migrations',
];

284
config/dompdf.php Normal file
View File

@ -0,0 +1,284 @@
<?php
return array(
/*
|--------------------------------------------------------------------------
| Settings
|--------------------------------------------------------------------------
|
| Set some default values. It is possible to add all defines that can be set
| in dompdf_config.inc.php. You can also override the entire config file.
|
*/
'show_warnings' => false, // Throw an Exception on warnings from dompdf
'public_path' => null, // Override the public path if needed
/*
* Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show and £.
*/
'convert_entities' => true,
'options' => array(
/**
* The location of the DOMPDF font directory
*
* The location of the directory where DOMPDF will store fonts and font metrics
* Note: This directory must exist and be writable by the webserver process.
* *Please note the trailing slash.*
*
* Notes regarding fonts:
* Additional .afm font metrics can be added by executing load_font.php from command line.
*
* Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must
* be embedded in the pdf file or the PDF may not display correctly. This can significantly
* increase file size unless font subsetting is enabled. Before embedding a font please
* review your rights under the font license.
*
* Any font specification in the source HTML is translated to the closest font available
* in the font directory.
*
* The pdf standard "Base 14 fonts" are:
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats.
*/
"font_dir" => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/**
* The location of the DOMPDF font cache directory
*
* This directory contains the cached font metrics for the fonts used by DOMPDF.
* This directory can be the same as DOMPDF_FONT_DIR
*
* Note: This directory must exist and be writable by the webserver process.
*/
"font_cache" => storage_path('fonts'),
/**
* The location of a temporary directory.
*
* The directory specified must be writeable by the webserver process.
* The temporary directory is required to download remote images and when
* using the PDFLib back end.
*/
"temp_dir" => sys_get_temp_dir(),
/**
* ==== IMPORTANT ====
*
* dompdf's "chroot": Prevents dompdf from accessing system files or other
* files on the webserver. All local files opened by dompdf must be in a
* subdirectory of this directory. DO NOT set it to '/' since this could
* allow an attacker to use dompdf to read any files on the server. This
* should be an absolute path.
* This is only checked on command line call by dompdf.php, but not by
* direct class use like:
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
*/
"chroot" => realpath(base_path()),
/**
* Protocol whitelist
*
* Protocols and PHP wrappers allowed in URIs, and the validation rules
* that determine if a resouce may be loaded. Full support is not guaranteed
* for the protocols/wrappers specified
* by this array.
*
* @var array
*/
'allowed_protocols' => [
"file://" => ["rules" => []],
"http://" => ["rules" => []],
"https://" => ["rules" => []]
],
/**
* @var string
*/
'log_output_file' => null,
/**
* Whether to enable font subsetting or not.
*/
"enable_font_subsetting" => true,
/**
* The PDF rendering backend to use
*
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
* fall back on CPDF. 'GD' renders PDFs to graphic files. {@link
* Canvas_Factory} ultimately determines which rendering class to instantiate
* based on this setting.
*
* Both PDFLib & CPDF rendering backends provide sufficient rendering
* capabilities for dompdf, however additional features (e.g. object,
* image and font support, etc.) differ between backends. Please see
* {@link PDFLib_Adapter} for more information on the PDFLib backend
* and {@link CPDF_Adapter} and lib/class.pdf.php for more information
* on CPDF. Also see the documentation for each backend at the links
* below.
*
* The GD rendering backend is a little different than PDFLib and
* CPDF. Several features of CPDF and PDFLib are not supported or do
* not make any sense when creating image files. For example,
* multiple pages are not supported, nor are PDF 'objects'. Have a
* look at {@link GD_Adapter} for more information. GD support is
* experimental, so use it at your own risk.
*
* @link http://www.pdflib.com
* @link http://www.ros.co.nz/pdf
* @link http://www.php.net/image
*/
"pdf_backend" => "CPDF",
/**
* PDFlib license key
*
* If you are using a licensed, commercial version of PDFlib, specify
* your license key here. If you are using PDFlib-Lite or are evaluating
* the commercial version of PDFlib, comment out this setting.
*
* @link http://www.pdflib.com
*
* If pdflib present in web server and auto or selected explicitely above,
* a real license code must exist!
*/
//"DOMPDF_PDFLIB_LICENSE" => "your license key here",
/**
* html target media view which should be rendered into pdf.
* List of types and parsing rules for future extensions:
* http://www.w3.org/TR/REC-html40/types.html
* screen, tty, tv, projection, handheld, print, braille, aural, all
* Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3.
* Note, even though the generated pdf file is intended for print output,
* the desired content might be different (e.g. screen or projection view of html file).
* Therefore allow specification of content here.
*/
"default_media_type" => "screen",
/**
* The default paper size.
*
* North America standard is "letter"; other countries generally "a4"
*
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
*/
"default_paper_size" => "a4",
/**
* The default paper orientation.
*
* The orientation of the page (portrait or landscape).
*
* @var string
*/
'default_paper_orientation' => "portrait",
/**
* The default font family
*
* Used if no suitable fonts can be found. This must exist in the font folder.
* @var string
*/
"default_font" => "serif",
/**
* Image DPI setting
*
* This setting determines the default DPI setting for images and fonts. The
* DPI may be overridden for inline images by explictly setting the
* image's width & height style attributes (i.e. if the image's native
* width is 600 pixels and you specify the image's width as 72 points,
* the image will have a DPI of 600 in the rendered PDF. The DPI of
* background images can not be overridden and is controlled entirely
* via this parameter.
*
* For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
* If a size in html is given as px (or without unit as image size),
* this tells the corresponding size in pt.
* This adjusts the relative sizes to be similar to the rendering of the
* html page in a reference browser.
*
* In pdf, always 1 pt = 1/72 inch
*
* Rendering resolution of various browsers in px per inch:
* Windows Firefox and Internet Explorer:
* SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:?
* Linux Firefox:
* about:config *resolution: Default:96
* (xorg screen dimension in mm and Desktop font dpi settings are ignored)
*
* Take care about extra font/image zoom factor of browser.
*
* In images, <img> size in pixel attribute, img css style, are overriding
* the real image dimension in px for rendering.
*
* @var int
*/
"dpi" => 96,
/**
* Enable inline PHP
*
* If this setting is set to true then DOMPDF will automatically evaluate
* inline PHP contained within <script type="text/php"> ... </script> tags.
*
* Enabling this for documents you do not trust (e.g. arbitrary remote html
* pages) is a security risk. Set this option to false if you wish to process
* untrusted documents.
*
* @var bool
*/
"enable_php" => false,
/**
* Enable inline Javascript
*
* If this setting is set to true then DOMPDF will automatically insert
* JavaScript code contained within <script type="text/javascript"> ... </script> tags.
*
* @var bool
*/
"enable_javascript" => true,
/**
* Enable remote file access
*
* If this setting is set to true, DOMPDF will access remote sites for
* images and CSS files as required.
* This is required for part of test case www/test/image_variants.html through www/examples.php
*
* Attention!
* This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and
* allowing remote access to dompdf.php or on allowing remote html code to be passed to
* $dompdf = new DOMPDF(, $dompdf->load_html(...,
* This allows anonymous users to download legally doubtful internet content which on
* tracing back appears to being downloaded by your server, or allows malicious php code
* in remote html pages to be executed by your server with your account privileges.
*
* @var bool
*/
"enable_remote" => true,
/**
* A ratio applied to the fonts height to be more like browsers' line height
*/
"font_height_ratio" => 1.1,
/**
* Use the HTML5 Lib parser
*
* @deprecated This feature is now always on in dompdf 2.x
* @var bool
*/
"enable_html5_parser" => true,
),
);

22
config/filesystems.php Normal file
View File

@ -0,0 +1,22 @@
<?php
return [
'default' => env('FILESYSTEM_DISK', 'local'),
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
],
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

35
config/logging.php Normal file
View File

@ -0,0 +1,35 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
'default' => env('LOG_CHANNEL', 'stack'),
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => false,
],
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
],
];

9
config/services.php Normal file
View File

@ -0,0 +1,9 @@
<?php
return [
'qwen' => [
'api_key' => env('QWEN_API_KEY'),
'api_url' => env('QWEN_API_URL', 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'),
'model' => env('QWEN_MODEL', 'qwen-max'),
],
];

24
config/session.php Normal file
View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Support\Str;
return [
'driver' => env('SESSION_DRIVER', 'file'),
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
'encrypt' => false,
'files' => storage_path('framework/sessions'),
'connection' => env('SESSION_CONNECTION'),
'table' => 'sessions',
'store' => env('SESSION_STORE'),
'lottery' => [2, 100],
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
'path' => '/',
'domain' => env('SESSION_DOMAIN'),
'secure' => env('SESSION_SECURE_COOKIE'),
'http_only' => true,
'same_site' => 'lax',
];

11
config/view.php Normal file
View File

@ -0,0 +1,11 @@
<?php
return [
'paths' => [
resource_path('views'),
],
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];

0
database/database.sqlite Normal file
View File

24
env.txt Normal file
View File

@ -0,0 +1,24 @@
APP_NAME="周报生成器"
APP_ENV=local
APP_KEY=base64:rNxPFnFb3oK5WrCqOuPEkgXAKAZFLKSvGdIcfPpPaYQ=
APP_DEBUG=true
APP_URL=http://localhost:8000
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
DB_DATABASE=database/database.sqlite
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
# 千问Max API配置
QWEN_API_KEY=sk-7ea0fac260574e4ea22c9de728e7bff9
QWEN_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
QWEN_MODEL=qwen-max

60
install.bat Normal file
View File

@ -0,0 +1,60 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 智能周报生成器 - 安装脚本
echo ========================================
echo.
REM 检查 PHP
where php >nul 2>nul
if %errorlevel% neq 0 (
echo [错误] 未找到 PHP请先安装 PHP 8.1 或更高版本
pause
exit /b 1
)
REM 检查 Composer
where composer >nul 2>nul
if %errorlevel% neq 0 (
echo [错误] 未找到 Composer请先安装 Composer
pause
exit /b 1
)
echo [1/4] 安装 PHP 依赖...
call composer install
if %errorlevel% neq 0 (
echo [错误] Composer 安装失败
pause
exit /b 1
)
echo.
echo [2/4] 配置环境文件...
if not exist ".env" (
copy env.txt .env >nul
echo 已创建 .env 文件
) else (
echo .env 文件已存在,跳过
)
echo.
echo [3/4] 生成应用密钥...
php artisan key:generate --ansi
echo.
echo [4/4] 创建存储软链接...
php artisan storage:link
echo.
echo ========================================
echo 安装完成!
echo ========================================
echo.
echo 启动开发服务器:
echo php artisan serve
echo.
echo 然后访问http://localhost:8000
echo.
pause

21
public/.htaccess Normal file
View File

@ -0,0 +1,21 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

25
public/index.php Normal file
View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true));
// 检查是否处于维护模式
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// 自动加载
require __DIR__.'/../vendor/autoload.php';
// 启动应用
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle(
$request = Request::capture()
)->send();
$kernel->terminate($request, $response);

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

File diff suppressed because it is too large Load Diff

5
routes/api.php Normal file
View File

@ -0,0 +1,5 @@
<?php
use Illuminate\Support\Facades\Route;
// API routes can be added here if needed

8
routes/console.php Normal file
View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

17
routes/web.php Normal file
View File

@ -0,0 +1,17 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\WeekReportController;
// 主页面
Route::get('/', [WeekReportController::class, 'index'])->name('home');
// 上传图片
Route::post('/upload', [WeekReportController::class, 'uploadImage'])->name('upload');
// 生成周报
Route::post('/generate', [WeekReportController::class, 'generate'])->name('generate');
// 下载周报
Route::post('/download/markdown', [WeekReportController::class, 'downloadMarkdown'])->name('download.markdown');
Route::post('/download/pdf', [WeekReportController::class, 'downloadPdf'])->name('download.pdf');

12
start.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 智能周报生成器 - 启动服务
echo ========================================
echo.
echo 正在启动开发服务器...
echo 访问地址: http://localhost:8000
echo.
echo 按 Ctrl+C 停止服务器
echo.
php artisan serve --host=localhost --port=8000

3
storage/app/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!public/
!.gitignore

2
storage/app/public/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

3
storage/framework/cache/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*
!data/
!.gitignore

View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/sessions/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/framework/views/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
storage/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore