项目初始化

main
milo 2025-03-30 10:36:39 +08:00
commit dbccb35764
1223 changed files with 227135 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/runtime
/.idea
/.vscode
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache
ssl/

1
.htaccess Executable file
View File

@ -0,0 +1 @@

1
.user.ini Normal file
View File

@ -0,0 +1 @@
open_basedir=/Users/milo/code/php/work/:/tmp/

16
404.html Executable file
View File

@ -0,0 +1,16 @@
<html>
<style>
.btlink {
color: #20a53a;
text-decoration: none;
}
</style>
<meta charset="UTF-8">
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr>
<div style="text-align: center;font-size: 15px" >Power by <a class="btlink" href="https://www.bt.cn/?from=404" target="_blank">堡塔 (免费,高效和安全的托管控制面板)</a></div>
</body>
</html>

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
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.

57
README.md Normal file
View File

@ -0,0 +1,57 @@
<div style="padding:18px;max-width: 1024px;margin:0 auto;background-color:#fff;color:#333">
<h1>webman</h1>
基于<a href="https://www.workerman.net" target="__blank">workerman</a>开发的超高性能PHP框架
<h1>学习</h1>
<ul>
<li>
<a href="https://www.workerman.net/webman" target="__blank">主页 / Home page</a>
</li>
<li>
<a href="https://webman.workerman.net" target="__blank">文档 / Document</a>
</li>
<li>
<a href="https://www.workerman.net/doc/webman/install.html" target="__blank">安装 / Install</a>
</li>
<li>
<a href="https://www.workerman.net/questions" target="__blank">问答 / Questions</a>
</li>
<li>
<a href="https://www.workerman.net/apps" target="__blank">市场 / Apps</a>
</li>
<li>
<a href="https://www.workerman.net/sponsor" target="__blank">赞助 / Sponsors</a>
</li>
<li>
<a href="https://www.workerman.net/doc/webman/thanks.html" target="__blank">致谢 / Thanks</a>
</li>
</ul>
<div style="float:left;padding-bottom:30px;">
<h1>赞助商</h1>
<h4>特别赞助</h4>
<a href="https://www.crmeb.com/?form=workerman" target="__blank">
<img src="https://www.workerman.net/img/sponsors/6429/20230719111500.svg" width="200">
</a>
<h4>铂金赞助</h4>
<a href="https://www.fadetask.com/?from=workerman" target="__blank"><img src="https://www.workerman.net/img/sponsors/1/20230719084316.png" width="200"></a>
<a href="https://www.yilianyun.net/?from=workerman" target="__blank" style="margin-left:20px;"><img src="https://www.workerman.net/img/sponsors/6218/20230720114049.png" width="200"></a>
</div>
<div style="clear: both">
<h1>LICENSE</h1>
The webman is open-sourced software licensed under the MIT.
</div>
</div>

View File

@ -0,0 +1,314 @@
<?php
namespace app\controller;
use support\Request;
use app\model\Book;
use app\model\Category;
use app\model\Reading;
use app\model\ReadingBook;
use app\model\BookLog;
class BookController
{
//书籍列表
public function index(Request $request)
{
$name = $request->get('name', '');
$categoryID = $request->get('category_id', 0);
$categoryList = Category::getList();
return view('book/index', ['name' => $name, 'categoryID' => $categoryID, 'categoryList' => $categoryList]);
}
public function add(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$categoryID = $request->post('category_id');
$author = $request->post('author');
$remark = $request->post('remark');
$old = Book::getByName($name);
if(!$old->isEmpty()){
return json(['code' => 1, 'msg' => '已存在此书']);
}
$bookModel = new Book;
$bookModel->save(['name' => $name, 'category_id' => $categoryID, 'author' => $author, 'remark' => $remark]);
return json(['code' => 0, 'msg' => 'ok']);
}
$categoryList = Category::getList();
return view('book/add', ['categoryList' => $categoryList]);
}
public function edit(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$categoryID = $request->post('category_id');
$author = $request->post('author');
$remark = $request->post('remark');
$id = $request->post('id');
Book::updateByID($id, ['name' => $name, 'category_id' => $categoryID, 'author' => $author, 'remark' => $remark]);
return json(['code' => 0, 'msg' => 'ok']);
}
$id = $request->get('id');
$book = Book::getByID($id);
$categoryList = Category::getList();
return view('book/edit', ['book' => $book, 'categoryList' => $categoryList]);
}
//读书清单
public function reading(Request $request)
{
$name = $request->get('name', '');
return view('book/reading', ['name' => $name]);
}
public function addReading(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$readingModel = new Reading;
$readingModel->save(['name' => $name]);
return json(['code' => 0, 'msg' => 'ok']);
}
return view('book/addReading');
}
public function editReading(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$id = $request->post('id');
Reading::updateByID($id, ['name' => $name]);
return json(['code' => 0, 'msg' => 'ok']);
}
$id = $request->get('id');
$reading = Reading::getByID($id);
return view('book/editReading', ['reading' => $reading]);
}
public function readingBook(Request $request)
{
$id = $request->get('id', 0);
$reading = Reading::getByID($id);
return view('book/readingBook', ['reading' => $reading]);
}
public function addReadingBook(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$readingID = $request->post('reading_id');
$bookID = $request->post('book_id');
$status = $request->post('status');
$start = $request->post('start');
$end = $request->post('end');
$remark = $request->post('remark');
ReadingBook::create([
'reading_id' => $readingID,
'book_id' => $bookID,
'status' => $status,
'start' => $start,
'end' => $end,
'remark' => $remark
]);
return json(['code' => 0, 'msg' => 'ok']);
}
$readingID = $request->get('reading_id', 0);
$reading = Reading::getByID($readingID);
$categoryList = Category::getList();
$bookList = Book::getPageList(0, '', 50);
return view('book/addReadingBook', ['readingID' => $readingID, 'reading' => $reading, 'categoryList' => $categoryList, 'bookList' => $bookList]);
}
public function editReadingBook(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$id = $request->post('id');
$bookID = $request->post('book_id');
$status = $request->post('status');
$start = $request->post('start', '');
$end = $request->post('end', '');
$remark = $request->post('remark');
ReadingBook::updateByID($id, [
'book_id' => $bookID,
'status' => $status,
'start' => $start,
'end' => $end,
'remark' => $remark
]);
return json(['code' => 0, 'msg' => 'ok']);
}
$readingBookID = $request->get('id', 0);
$readingBook = ReadingBook::getByID($readingBookID);
$reading = Reading::getByID($readingBook->reading_id);
$categoryList = Category::getList();
$bookList = Book::getPageList(0, '', 50);
$book = Book::getByID($readingBook->book_id);
return view('book/editReadingBook', ['reading' => $reading, 'categoryList' => $categoryList, 'bookList' => $bookList, 'readingBook' => $readingBook, 'book' => $book]);
}
public function apiGetList(Request $request)
{
$name = $request->get('name', '');
$categoryID = $request->get('category_id', 0);
$pageSize = $request->get('size', 20);
$bookList = Book::getPageList($categoryID, $name, $pageSize);
return json(['code' => 0, 'msg' => 'ok', 'bookList' => $bookList]);
}
public function apiGetReadingList(Request $request)
{
$name = $request->get('name', '');
$readingList = Reading::getPageList($name);
return json(['code' => 0, 'msg' => 'ok', 'readingList' => $readingList]);
}
public function apiGetReadingBookList(Request $request)
{
$readingID = $request->get('id', 0);
$bookList = ReadingBook::getList($readingID);
return json(['code' => 0, 'msg' => 'ok', 'bookList' => $bookList]);
}
//排序
public function apiSortReadingBook(Request $request)
{
$id = $request->post('id');
$num = $request->post('num');
$sort = 'down';
if($num < 0) $sort = 'up';
$num = abs($num);
$item = ReadingBook::getByID($id);
$whereMap = [];
$whereMap[] = ['reading_id', '=', $item['reading_id']];
if ($sort == 'up'){
$whereMap[] = ['sort', '<', $item['sort']];
$order = "sort desc";
}else{
$whereMap[] = ['sort', '>', $item['sort']];
$order = "sort asc";
}
$forSortItems = ReadingBook::getListByWhereAndOrder($whereMap, $order, $num);
if (!empty($forSortItems)){
$updateData = [];
$forSortCount = count($forSortItems);
for ($i = 0; $i < $forSortCount; $i++){
if ($i == 0){
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $item['sort']
];
}else{
$updateData[] = [
'id' => $forSortItems[$i]['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
}
}
$updateData[] = [
'id' => $item['id'],
'sort' => $forSortItems[$i - 1]['sort']
];
if (!empty($updateData)){
$model = new ReadingBook();
$model->saveAll($updateData);
return json(['code' => 0, 'msg' => 'ok']);
}
}
return json(['code' => 1, 'msg' => '无须调整排序!']);
}
public function apiDelReadingBook(Request $request)
{
$id = $request->get('id');
$readingBook = ReadingBook::getByID($id);
if(empty($readingBook)) return json(['code' => 1, 'msg' => '清单中无此书!']);
ReadingBook::destroy($id);
return json(['code' => 0, 'msg' => 'ok']);
}
public function apiStartReadingBook(Request $request)
{
$id = $request->post('id');
$readingBook = ReadingBook::getByID($id);
if(empty($readingBook)) return json(['code' => 1, 'msg' => '清单中无此书!']);
if(!empty($readingBook->start)) return json(['code' => 2, 'msg' => '已经开始,无需重新设置']);
ReadingBook::updateByID($id, [
'status' => 'reading',
'start' => date('Y-m-d')
]);
return json(['code' => 0, 'msg' => 'ok']);
}
public function apiEndReadingBook(Request $request)
{
$id = $request->post('id');
$readingBook = ReadingBook::getByID($id);
if(empty($readingBook)) return json(['code' => 1, 'msg' => '清单中无此书!']);
if($readingBook->status == 'finished') return json(['code' => 2, 'msg' => '已经结束,无需重新设置']);
$end = date('Y-m-d');
ReadingBook::updateByID($id, ['end' => $end, 'status' => 'finished']);
BookLog::create([
'book_id' => $readingBook->book_id,
'start' => $readingBook->start,
'end' => $end
]);
return json(['code' => 0, 'msg' => 'ok']);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace app\controller;
use support\Request;
use app\model\Category;
class CategoryController
{
public function index(Request $request)
{
return view('category/index');
}
public function add(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$categoryModel = new Category;
$categoryModel->save(['name' => $name]);
return json(['code' => 0, 'msg' => 'ok']);
}
return view('category/add');
}
public function apiGetList()
{
$list = Category::getList();
return json(['code' => 0, 'msg' => 'ok', 'list' => $list]);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace app\controller;
use support\Request;
use app\model\zdoo\Customer;
class CustomerController
{
public function index(Request $request)
{
$statusList = $request->get('statusList', '');
$name = $request->get('name', '');
$statusListArray = explode(',', $statusList);
$statusListSelected = [];
foreach(Customer::$statusList as $k => $v){
$tmpItem = [
'name' => $v,
'value' => $k
];
if(in_array($k, $statusListArray)){
$tmpItem['selected'] = true;
}
$statusListSelected[] = $tmpItem;
}
return view('customer/index', ['statusList' => $statusList, 'name' => $name, 'statusListSelected' => json_encode($statusListSelected)]);
}
public function apiGetList(Request $request)
{
$statusList = $request->get('statusList', '');
$name = $request->get('name', '');
$list = Customer::getPageList($statusList, $name);
return json(['code' => 0, 'msg' => 'ok', 'list' => $list]);
}
}

View File

@ -0,0 +1,328 @@
<?php
namespace app\controller;
use support\Request;
use app\model\Goods;
use app\model\GoodsReceipt;
use app\model\SaleLog;
use think\facade\Db;
class GoodsController
{
//商品列表
public function index(Request $request)
{
$name = $request->get('name', '');
$order = $request->get('order', 'stock_desc');
return view('goods/index', ['name' => $name, 'order' => $order]);
}
//添加商品
public function add(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$price = $request->post('price');
$remark = $request->post('remark');
$stock = $request->post('stock');
$stock_small = $request->post('stock_small');
$sales = $request->post('sales');
$sales_small = $request->post('sales_small');
$split_to_small = $request->post('split_to_small');
$old = Goods::getByName($name);
if(!$old->isEmpty()){
return json(['code' => 1, 'msg' => '已存在此商品']);
}
$goodsModel = new Goods;
$goodsModel->save([
'name' => $name,
'price' => $price,
'stock' => $stock,
'stock_small' => $stock_small,
'sales' => $sales,
'sales_small' => $sales_small,
'split_to_small' => $split_to_small,
'remark' => $remark
]);
return json(['code' => 0, 'msg' => 'ok']);
}
return view('goods/add', []);
}
//编辑商品
public function edit(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$name = $request->post('name');
$price = $request->post('price');
$remark = $request->post('remark');
$stock = $request->post('stock');
$stock_small = $request->post('stock_small');
$sales = $request->post('sales');
$sales_small = $request->post('sales_small');
$split_to_small = $request->post('split_to_small');
$id = $request->post('id');
// return json([
// 'id' => $id,
// 'name' => $name,
// 'price' => $price,
// 'stock' => $stock,
// 'stock_small' => $stock_small,
// 'sales' => $sales,
// 'sales_small' => $sales_small,
// 'split_to_small' => $split_to_small,
// 'remark' => $remark
// ]);
try{
Goods::updateByID($id,
[
'name' => $name,
'price' => $price,
'stock' => $stock,
'stock_small' => $stock_small,
'sales' => $sales,
'sales_small' => $sales_small,
'split_to_small' => $split_to_small,
'remark' => $remark
]);
return json(['code' => 0, 'msg' => 'ok']);
}catch(\Exception $e){
return json(['code' => 1, 'msg' => $e->getMessage()]);
}
}
$id = $request->get('id');
$goods = Goods::getByID($id);
return view('goods/edit', ['goods' => $goods]);
}
//商品入库日志
public function receipt(Request $request)
{
return view('goods/receipt', []);
}
public function addReceipt(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
Db::startTrans();
try {
$goodsID = $request->post('goods_id');
$boughtAt = $request->post('bought_at');
$num = $request->post('num');
$totalPrice = $request->post('total_price');
$remark = $request->post('remark');
if($goodsID <= 0){
return json(['code' => 1, 'msg' => '请选择商品']);
}
if($totalPrice <= 0){
return json(['code' => 2, 'msg' => '进货总价必须大于0']);
}
if($boughtAt == ''){
return json(['code' => 3, 'msg' => '请选择进货日期']);
}
$goods = Goods::getByID($goodsID);
if($goods->isEmpty()){
return json(['code' => 4, 'msg' => '商品不存在']);
}
$splitToSmall = $goods['split_to_small'] ?: 1;
$costPrice = bcdiv($totalPrice, $num, 2); //计算大包单价
$smallCostPrice = bcdiv($costPrice, $splitToSmall, 2); //计算小包单价
GoodsReceipt::create([
'goods_id' => $goodsID,
'num' => $num,
'total_price' => $totalPrice,
'cost_price' => $costPrice,
'small_cost_price' => $smallCostPrice,
'bought_at' => $boughtAt,
'split_to_small' => $splitToSmall,
'created_at' => date('Y-m-d H:i:s'),
'remark' => $remark
]);
Goods::updateByID($goodsID, ['stock' => $goods['stock'] + $num]);
Db::commit();
return json(['code' => 0, 'msg' => 'ok']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 5, 'msg' => $e->getMessage()]);
}
}
$goodsList = Goods::getPageList('', 100);
return view('goods/addReceipt', ['goodsList' => $goodsList]);
}
//商品销售日志
public function sale(Request $request)
{
$goodsID = $request->get('id');
$goods = Goods::getByID($goodsID);
$goodsList = Goods::getPageList('', 100);
return view('goods/sale', ['goods' => $goods, 'goodsID' => $goodsID, 'goodsList' => $goodsList]);
}
public function addSale(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
Db::startTrans();
try {
$goodsID = $request->post('goods_id');
$sold_at = $request->post('sold_at');
$num = $request->post('num', 0);
$num_small = $request->post('num_small', 0);
$totalPrice = $request->post('total_price');
$remark = $request->post('remark');
if($goodsID <= 0){
return json(['code' => 1, 'msg' => '请选择商品']);
}
if($totalPrice <= 0){
return json(['code' => 2, 'msg' => '销售总价必须大于0']);
}
if($sold_at == ''){
return json(['code' => 3, 'msg' => '请选择销售时间']);
}
if (empty($num) && empty($num_small)) {
return json(['code' => 4, 'msg' => '大包销量和小包销量不能同时为空']);
}
if(empty($num)){
$num = 0;
}
if(empty($num_small)){
$num_small = 0;
}
$goods = Goods::getByID($goodsID);
if($goods->isEmpty()){
return json(['code' => 5, 'msg' => '商品不存在']);
}
// 小包库存不足时,没有自动拆分大包的操作
if ($goods->stock < $num || $goods->stock_small < $num_small) {
return json(['code' => 6, 'msg' => '商品库存不足']);
}
SaleLog::create([
'goods_id' => $goodsID,
'num' => $num,
'num_small' => $num_small,
'total_price' => $totalPrice,
'sold_at' => $sold_at,
'remark' => $remark
]);
Goods::updateByID($goodsID, [
'sales' => $goods->sales + $num,
'sales_small' => $goods->sales_small + $num_small,
'stock' => $goods->stock - $num,
'stock_small' => $goods->stock_small - $num_small
]);
Db::commit();
return json(['code' => 0, 'msg' => 'ok']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 7, 'msg' => $e->getMessage()]);
}
}
$goodsList = Goods::getPageList('', 100);
return view('goods/addSale', ['goodsList' => $goodsList]);
}
//获取商品列表
public function apiGetList(Request $request)
{
$name = $request->get('name', '');
$order = $request->get('order', 'stock_desc');
$pageSize = $request->get('size', 20);
$goodsList = Goods::getPageList($name, $pageSize, str_replace('_', ' ', $order));
return json(['code' => 0, 'msg' => 'ok', 'goodsList' => $goodsList]);
}
//获取进货列表
public function apiGetReceiptList(Request $request)
{
$pageSize = $request->get('size', 20);
$receiptList = GoodsReceipt::getPageList($pageSize);
return json(['code' => 0, 'msg' => 'ok', 'receiptList' => $receiptList]);
}
public function apiGetSaleList(Request $request)
{
$pageSize = $request->get('size', 20);
$saleList = SaleLog::getPageList($pageSize);
return json(['code' => 0, 'msg' => 'ok', 'saleList' => $saleList]);
}
public function apiDelReceipt(Request $request)
{
$method = $request->method();
if($method == 'POST')
{
$id = $request->post('id');
$goodsReceipt = GoodsReceipt::getByID($id);
if($goodsReceipt->isEmpty()){
return json(['code' => 1, 'msg' => '进货记录不存在']);
}
Db::startTrans();
try {
$goodsID = $goodsReceipt['goods_id'];
$num = $goodsReceipt['num'];
$goods = Goods::getByID($goodsID);
if($goods->isEmpty()){
return json(['code' => 2, 'msg' => '商品不存在']);
}
GoodsReceipt::destroy($id);
Goods::updateByID($goodsID, ['stock' => $goods['stock'] - $num]);
Db::commit();
return json(['code' => 0, 'msg' => 'ok']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 3, 'msg' => $e->getMessage()]);
}
}
return json(['code' => 4, 'msg' => '请求方式错误']);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace app\controller;
use support\Request;
class IndexController
{
public function index(Request $request)
{
static $readme;
return 'AAA';
if (!$readme) {
$readme = file_get_contents(base_path('README.md'));
}
return $readme;
}
public function view(Request $request)
{
return view('index/view', ['name' => 'webman']);
}
public function json(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace app\controller;
use support\Request;
use PhpOffice\PhpSpreadsheet\IOFactory;
//use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class JglController
{
public function index(Request $request)
{
$publicPath = public_path();
ini_set('memory_limit', '1G');
$file = $publicPath . '/data/data.xls';
$savePath = $publicPath . '/data/data2.xls';
$spreadsheet = IOFActory::load($file);
// 获取第一个工作表
$worksheet = $spreadsheet->getActiveSheet();
// 获取最大行数和列数
$maxRow = $worksheet->getHighestRow();
//$maxColumn = $worksheet->getHighestColumn();
//$saveSpreadsheet = new Spreadsheet();
//$saveActiveWorksheet = $saveSpreadsheet->getActiveSheet();
// 遍历每一行和每一列
//$stuList = [];
for ($row = 4; $row <= $maxRow; $row++)
{
$project = $worksheet->getCell('F' . $row)->getValue();
if(substr($project, 0, 1) == '='){
$project = $worksheet->getCell('E' . $row)->getValue();
}
$subProject = $this->getProject($project);
$worksheet->setCellValue('G' . $row, $subProject);
}
//$activeWorksheet = $spreadsheet->getActiveSheet();
//$activeWorksheet->setCellValue('A1', 'Hello World !');
$writer = new Xlsx($spreadsheet);
$writer->save($savePath);
exit;
}
private function getProject($project)
{
$projects = ['田径','篮球','足球','排球','武术','体操'];
$k = mt_rand(0, 5);
$tmpProject = $projects[$k];
while($tmpProject == $project){
$k = mt_rand(0, 5);
$tmpProject = $projects[$k];
echo "$tmpProject ==== $project\r\n";
}
echo "--------------------------------\r\n";
return $tmpProject;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace app\controller;
use support\Request;
class SalaryController
{
public function index(Request $request)
{
$month = "2024年11月";
$social = 469.14; //个人社保部分
$staffs = [
[
"name" => "徐从伟", //姓名
"wages" => 6000, //工资 //试用期90%正式工资5500
"late_num" => 0, //迟到次数
"card_num" => 0, //缺卡次数
"leave" => 1, //请假天数
"commission" => 0, //提成
"haveSocial" => 1, //是否有社保10无,
],
[
"name" => "李禹江", //姓名
"wages" => 3000, //工资,无社保
"late_num" => 0, //迟到次数
"card_num" => 0, //缺卡次数
"leave" => 6.5, //请假天数
"commission" => 0, //提成
"haveSocial" => 0, //是否有社保10
"collection" => 900, //收款金额
],
[
"name" => "尹财波", //姓名
"wages" => 7500, //工资,无社保
"late_num" => 0, //迟到次数
"card_num" => 0, //缺卡次数
"leave" => 0, //请假天数
"commission" => 0, //提成
"haveSocial" => 0, //是否有社保10
],
[
"name" => "郑继领", //姓名
"wages" => 3000, //工资,无社保
"late_num" => 0, //迟到次数
"card_num" => 0, //缺卡次数
"leave" => 0, //请假天数
"commission" => 0, //提成
"haveSocial" => 0, //是否有社保10
"collection" => 31268, //收款金额
],
];
$total = 0; //所有金额
$socialNumber = 0; //社保人数
$str = $month . "工资明细:<br /><br /><br />";
foreach ($staffs as $s) {
$haveCommission = true; //是否计算提成
$perTotal = $s["wages"]; //个人合计
$str .= $s["name"] . "{$month} 工资明细:<br />";
$str .= "基本工资:" . $s["wages"] . "<br /><br />";
$num = $s["late_num"] + $s["card_num"] - 3;
$dayWage = $s["wages"] / 22;
$hourlyWage = $dayWage / 8;
if ($num > 0) {
$deduction = round($hourlyWage * $num, 2);
} else {
$deduction = 0;
}
//$wagesStr = '应发工资(' . $s['wages'];
$wagesStr = "应发工资(" . $perTotal;
$str .= "迟到次数:" .
$s["late_num"] .
"<br />缺卡次数:" .
$s["card_num"] .
"<br />";
$str .= "请假天数:" . $s["leave"] . "<br />";
if ($s["leave"] > 0) {
$deduction += round($dayWage * $s["leave"], 2);
}
if ($deduction > 0) {
$str .= "扣款:" . $deduction . "元<br />";
$wagesStr .= " - " . $deduction;
$perTotal = $perTotal - $deduction;
}
if ($s["haveSocial"]) {
$str .= "<br />";
$str .= "社保扣款:" . $social . "元<br />";
$str .= "<br />";
$wagesStr .= " - " . $social;
$perTotal = $perTotal - $social;
$socialNumber++;
}
//销售收款计算提成
if (isset($s["collection"]) && $s["collection"] > 0) {
$commission = 0;
if ($s["collection"] > 80000) {
$commission = $s["collection"] * 0.15;
} elseif ($s["collection"] > 60000) {
$commission = $s["collection"] * 0.14;
} elseif ($s["collection"] > 40000) {
$commission = $s["collection"] * 0.12;
} elseif ($s["collection"] > 20000) {
$commission = $s["collection"] * 0.1;
} else {
$commission = $s["collection"] * 0.08;
}
if ($commission > 0) {
$s["commission"] = $commission;
}
}
if ($s["commission"] > 0) {
$str .= "收款:" . $s["collection"] . "<br />";
$str .= "提成:" . $s["commission"] . "<br />";
if ($haveCommission) {
$wagesStr .= " + " . $s["commission"];
$perTotal = $perTotal + $s["commission"];
} else {
$str .= "提成不计算按保底90%计算工资<br />";
}
$str .= "<br />";
}
$perTotal = round($perTotal, 2);
$wagesStr .= ") = " . $perTotal . "";
$str .= $wagesStr . "<br />";
$str .= "<br /><br /><br /><br />";
$total += $perTotal;
}
$socialTotal = ($socialNumber + 2) * 1505.63;
$str .= "社保总金额:" . $socialTotal . " 元<br /><br />";
$str .= "工资总金额:" . $total . " 元 <br /><br />";
$str .= "总金额:" . $total + $socialTotal . "";
return $str;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace app\controller;
use support\Request;
use app\model\SaleLog;
class SalesController
{
//日销量额统计
public function index(Request $request)
{
return view('sales/index');
}
//商品销量统计
public function goods(Request $request)
{
return view('sales/goods');
}
//获取日销额统计
public function apiGetSales(Request $request)
{
$start = $request->get('start', '');
$end = $request->get('end', '');
if(empty($start)){
$start = date('Y-m-d', strtotime('-30 days'));
}
if(empty($end)){
$end = date('Y-m-d');
}
$data = SaleLog::getSales($start, $end);
$soldAt = $data->column('sold_at');
$totalSales = $data->column('total_sales');
return json(['code' => 0, 'data' => ['sold_at' => $soldAt, 'total_sales' => $totalSales]]);
}
public function apiGetSalesGoods(Request $request)
{
$start = $request->get('start', '');
$end = $request->get('end', '');
$order = $request->get('order', 'total_sales');
if(empty($start)){
$start = date('Y-m-d', strtotime('-30 days'));
}
if(empty($end)){
$end = date('Y-m-d');
}
$data = SaleLog::getSalesGoods($start, $end, $order);
$goodsName = $data->column('goods_name');
$totalSales = $data->column('total_sales');
$totalNum = $data->column('total_num');
$totalNumSmall = $data->column('total_num_small');
return json(['code' => 0, 'data' => [
'goods_name' => $goodsName,
'total_sales' => $totalSales,
'total_num' => $totalNum,
'total_num_small' => $totalNumSmall
]
]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace app\controller;
use support\Request;
use app\model\pms\Task;
class TaskController
{
public function index(Request $request)
{
return view('task/index');
}
public function apiGetList(Request $request)
{
$taskList = Task::getList();
foreach($taskList as $task){
$task->statusName = Task::$statusList[$task->status];
}
return json(['code' => 0, 'msg' => 'ok', 'taskList' => $taskList]);
}
}

4
app/functions.php Normal file
View File

@ -0,0 +1,4 @@
<?php
/**
* Here is your custom functions.
*/

View File

@ -0,0 +1,42 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class StaticFile
* @package app\middleware
*/
class StaticFile implements MiddlewareInterface
{
public function process(Request $request, callable $next): Response
{
// Access to files beginning with. Is prohibited
if (strpos($request->path(), '/.') !== false) {
return response('<h1>403 forbidden</h1>', 403);
}
/** @var Response $response */
$response = $next($request);
// Add cross domain HTTP header
/*$response->withHeaders([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Credentials' => 'true',
]);*/
return $response;
}
}

51
app/model/Base.php Normal file
View File

@ -0,0 +1,51 @@
<?php
namespace app\model;
use think\Model;
class Base extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
//protected $table = 'test';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
public static function getByID($id)
{
if($id <= 0) return null;
return self::where('id', $id)->findOrEmpty();
}
//根据ID更新数据
public static function updateByID($id, $data)
{
return self::where('id', $id)->update($data);
}
//根据where条件和排序获取记录
public static function getListByWhereAndOrder($where, $order, $limit = 1)
{
return self::where($where)
->order($order)
->limit($limit)
->select();
}
}

34
app/model/Book.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace app\model;
class Book extends Base
{
protected $createTime = 'create_time';
protected $updateTime = false;
public static function getPageList($categoryID = 0, $name = '', $size = 20)
{
return self::alias('b')
->leftJoin('category c', 'b.category_id = c.id')
->field('b.*,c.name as category_name')
->when(!empty($categoryID) && $categoryID > 0, function($query) use($categoryID){
$query->where('b.category_id', $categoryID);
})
->when(!empty($name), function($query) use($name){
$query->where('b.name', 'like', "%$name%");
})
->order('b.id desc')
->paginate([
'list_rows'=> $size,
'var_page' => 'page',
]);
}
public static function getByName($name)
{
$name = trim($name);
return self::where('name', $name)->findOrEmpty();
}
}

10
app/model/BookLog.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace app\model;
class BookLog extends Base
{
protected $createTime = 'create_time';
protected $updateTime = false;
}

13
app/model/Category.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace app\model;
class Category extends Base
{
protected $autoWriteTimestamp = false;
public static function getList()
{
return self::select();
}
}

27
app/model/Goods.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace app\model;
class Goods extends Base
{
protected $createTime = false;
protected $updateTime = false;
public static function getPageList($name = '', $size = 20, $order = 'id desc')
{
return self::when(!empty($name), function($query) use($name){
$query->where('name', 'like', "%$name%");
})
->order($order)
->paginate([
'list_rows'=> $size,
'var_page' => 'page',
]);
}
public static function getByName($name)
{
$name = trim($name);
return self::where('name', $name)->findOrEmpty();
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace app\model;
class GoodsReceipt extends Base
{
protected $createTime = false;
protected $updateTime = false;
protected $name = 'goods_receipt_log';
public static function getPageList($size = 20)
{
return self::alias('rl')
->join('goods g', 'g.id = rl.goods_id')
->order('rl.id desc')
->field('rl.*, g.name as goods_name')
->paginate([
'list_rows'=> $size,
'var_page' => 'page',
]);
}
}

20
app/model/Reading.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace app\model;
class Reading extends Base
{
protected $autoWriteTimestamp = false;
public static function getPageList($name = '')
{
return self::when(!empty($name), function($query) use($name){
$query->where('name', 'like', "%$name%");
})
->order('id desc')
->paginate([
'list_rows'=> 20,
'var_page' => 'page',
]);
}
}

36
app/model/ReadingBook.php Normal file
View File

@ -0,0 +1,36 @@
<?php
namespace app\model;
class ReadingBook extends Base
{
protected $table = 'reading_book';
protected $autoWriteTimestamp = false;
//阶段
protected static $status = [
'to-read' => '待阅读',
'reading' => '阅读中',
'finished' => '已阅读'
];
public static function getList($readingID)
{
$list = self::alias('rb')
->leftJoin('book b', 'rb.book_id = b.id')
->leftJoin('category c', 'b.category_id = c.id')
->field('rb.*,b.name as book_name,b.author,c.name as category_name')
->where('rb.reading_id', $readingID)
->order('rb.sort', 'asc')
->select();
return $list->each(function($item){
$item->statusStr = self::$status[$item['status']];
});
}
public static function onAfterInsert($model)
{
$model->sort = $model->id;
$model->save();
}
}

53
app/model/SaleLog.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace app\model;
class SaleLog extends Base
{
protected $createTime = false;
protected $updateTime = false;
public static function getPageList($size = 20)
{
return self::alias('sl')
->join('goods g', 'g.id = sl.goods_id')
->order('sl.id desc')
->field('sl.*, g.name as goods_name')
->paginate([
'list_rows'=> $size,
'var_page' => 'page',
]);
}
//获取销售金额统计
public static function getSales($start, $end)
{
if(empty($start) || empty($end)){
return [];
}
return self::alias('sl')
->join('goods g', 'g.id = sl.goods_id')
->where('sl.sold_at', '>=', $start)
->where('sl.sold_at', '<=', $end)
->field('sl.sold_at, sum(sl.total_price) as total_sales')
->group('sl.sold_at')
->select();
}
public static function getSalesGoods($start, $end, $order = 'total_sales')
{
if(empty($start) || empty($end)){
return [];
}
return self::alias('sl')
->join('goods g', 'g.id = sl.goods_id')
->where('sl.sold_at', '>=', $start)
->where('sl.sold_at', '<=', $end)
->field('g.name as goods_name, sum(sl.total_price) as total_sales, sum(sl.num) as total_num, sum(sl.num_small) as total_num_small')
->order("{$order} asc")
->group('sl.goods_id')
->select();
}
}

44
app/model/pms/Base.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace app\model\pms;
use think\Model;
class Base extends Model
{
// 设置当前模型的数据库连接
protected $connection = 'pms';
/**
* The table associated with the model.
*
* @var string
*/
//protected $table = 'test';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
public static function getByID($id)
{
if($id <= 0) return null;
return self::where('id', $id)->findOrEmpty();
}
//根据ID更新数据
public static function updateByID($id, $data)
{
return self::where('id', $id)->update($data);
}
}

34
app/model/pms/Task.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace app\model\pms;
class Task extends Base
{
//protected $table = 'crm_customer';
public static $statusList = [
'wait' => '未开始',
'doing' => '进行中',
'done' => '已完成',
'pause' => '已暂停',
'cancel' => '已取消',
'closed' => '已关闭',
];
public static function getList($size = 20)
{
return self::alias('t')
->leftJoin('user u', 't.assignedTo = u.account')
->leftjoin('user u2', 't.finishedBy = u2.account')
->leftJoin('project p', 't.project = p.id')
->leftJoin('project p2', 't.execution = p2.id')
->where('t.deleted', 0)
->where('t.status', 'not in', ['closed', 'cancel'])
->order('t.id desc')
->field('t.*, u.realname as assignedToName, u2.realname as finishedByName, p.name as projectName, p2.name as executionName')
->paginate([
'list_rows'=> $size,
'var_page' => 'page',
]);
}
}

44
app/model/zdoo/Base.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace app\model\zdoo;
use think\Model;
class Base extends Model
{
// 设置当前模型的数据库连接
protected $connection = 'zdoo';
/**
* The table associated with the model.
*
* @var string
*/
//protected $table = 'test';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
public static function getByID($id)
{
if($id <= 0) return null;
return self::where('id', $id)->findOrEmpty();
}
//根据ID更新数据
public static function updateByID($id, $data)
{
return self::where('id', $id)->update($data);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\model\zdoo;
//沟通记录
class Communication extends Base
{
protected $table = 'sys_action';
public static function getListByCustomerList($customerIDList)
{
return self::where('objectType', 'customer')
->whereIn('objectID', $customerIDList)
->where('comment', '<>','')
->order('date desc')
->select();
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace app\model\zdoo;
class Customer extends Base
{
protected $table = 'crm_customer';
//阶段
protected static $stages = [
'detail' => '客户录入',
'communicate' => '需求沟通',
'guide' => '价值引导',
'try' => '打包试用',
'sign' => '签约',
'return' => '回款',
'deliver' => '实施交付',
'serve' => '跟踪服务',
'renew' => '续签增购'
];
//状态
public static $statusList = [
'potential' => '潜在',
'intension' => '意向',
'negotiation' => '合同洽谈',
'payment-time' => '确认付款时间',
'signed' => '已签约',
'paid' => '已付款',
'failed' => '失败'
];
public static function getPageList($statusList = '', $name = '')
{
$list = self::alias('c')
->leftJoin('sys_user u', 'c.assignedTo = u.account')
->where('c.deleted', 0)
->when($statusList != '', function($query) use($statusList){
$query->whereIn('c.status', $statusList);
})
->when(!empty($name), function($query) use($name){
$query->where('c.name', 'like', "%$name%");
})
->field('c.*,u.realname as assignedToName')
->order('c.contactedDate desc, c.createdDate desc')
->paginate([
'list_rows'=> 50,
'var_page' => 'page',
]);
$customerIDList = [];
foreach($list as $item){
$item['stageStr'] = self::$stages[$item['stage']];
$item['statusStr'] = self::$statusList[$item['status']];
$item['contactedDateStr'] = substr($item['contactedDate'], 0, 10);
$item['createdDateStr'] = substr($item['createdDate'], 0, 10);
$customerIDList[] = $item['id'];
}
$communicationList = Communication::getListByCustomerList($customerIDList);
$customerCommunicationList = [];
foreach($communicationList as $c){
$customerCommunicationList[$c['objectID']][] = $c;
}
foreach($list as $item){
$tmpCommunicationStr = '';
if(!empty($customerCommunicationList[$item['id']])){
$tmpCommunicationList = $customerCommunicationList[$item['id']];
foreach($tmpCommunicationList as $t){
if(!empty($t['comment'])){
$tmpCommunicationStr .= '<span style="color:red;">' . substr($t['date'], 0, 10) . '</span>' . $t['comment'] . "<br />";
}
}
}
$item['communicationStr'] = $tmpCommunicationStr;
}
return $list;
}
}

38
app/view/book/add.html Normal file
View File

@ -0,0 +1,38 @@
<form class="layui-form eject-layuiBox" data-action="/book/add">
<div class="layui-form-item">
<label class="layui-form-label">书名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<select name="category_id">
{foreach $categoryList as $cate}
<option value="{$cate.id}">{$cate.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">作者</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="author" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>

View File

@ -0,0 +1,13 @@
<form class="layui-form eject-layuiBox" data-action="/book/addReading">
<div class="layui-form-item">
<label class="layui-form-label">书单名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>

View File

@ -0,0 +1,119 @@
<form class="layui-form eject-layuiBox" data-action="/book/addReadingBook">
<div class="layui-form-item">
<label class="layui-form-label">书籍分类</label>
<div class="layui-input-block">
<select lay-filter="category" id="category">
<option value="">全部</option>
{foreach $categoryList as $cate}
<option value="{$cate.id}">{$cate.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">书籍</label>
<div class="layui-input-block">
<div id="book"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option value="to-read">待阅读</option>
<option value="reading">阅读中</option>
<option value="finished">已阅读</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开始</label>
<div class="layui-input-block">
<input type="text" name="start" class="layui-input" id="start" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束</label>
<div class="layui-input-block">
<input type="text" name="end" class="layui-input" id="end" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input type="text" name="remark" placeholder="文本框" class="layui-input">
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
<input type="hidden" name="reading_id" value="{$readingID}" />
</form>
<script src="/js/xm-select.js"></script>
<script>
$(document).ready(function(){
var form = layui.form;
var laydate = layui.laydate;
var dropdown = layui.dropdown;
laydate.render({
elem: '#start'
});
laydate.render({
elem: '#end'
});
var book = xmSelect.render({
el: '#book',
filterable : true, //开启搜索
remoteSearch: true,
radio : true, //开启单选模式
clickClose : true, //是否点击选项后自动关闭下拉框
name: 'book_id', //表单提交时的name
paging: true,
data : [],
remoteMethod: function(val, cb, show){
var categoryID = $('#category').val();
$.get('/book/apiGetList', {'category_id': categoryID, 'name':val, 'size': 50}, function(data){
var bookList = [];
data.bookList.data.forEach(element => {
var name = element.name + '【' + element.category_name + '】';
if(element.author != ''){
name += '【' + element.author + '】';
}
bookList.push({
name: name,
value: element.id
})
});
cb(bookList);
})
}
});
form.on('select(category)',function(data){
var categoryID = data.value;
$.get('/book/apiGetList', {'category_id': categoryID, 'size': 50}, function(data){
var bookList = [];
data.bookList.data.forEach(element => {
var name = element.name + '【' + element.category_name + '】';
if(element.author != ''){
name += '【' + element.author + '】';
}
bookList.push({
name: name,
value: element.id
})
});
book.update({data: bookList});
})
})
});
</script>

39
app/view/book/edit.html Normal file
View File

@ -0,0 +1,39 @@
<form class="layui-form eject-layuiBox" data-action="/book/edit">
<div class="layui-form-item">
<label class="layui-form-label">书名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" value="{$book.name??''}" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类</label>
<div class="layui-input-block">
<select name="category_id">
{foreach $categoryList as $cate}
<option {if $cate.id == $book.category_id} selected {/if} value="{$cate.id}">{$cate.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">作者</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="author" value="{$book.author??''}" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" value="{$book.remark??''}" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
<input type="hidden" name="id" value="{$book.id}" />
</form>

View File

@ -0,0 +1,14 @@
<form class="layui-form eject-layuiBox" data-action="/book/editReading">
<div class="layui-form-item">
<label class="layui-form-label">书名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" value="{$reading.name??''}" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
<input type="hidden" name="id" value="{$reading.id}" />
</form>

View File

@ -0,0 +1,135 @@
<form class="layui-form eject-layuiBox" data-action="/book/editReadingBook">
<div class="layui-form-item">
<label class="layui-form-label">书籍分类</label>
<div class="layui-input-block">
<select lay-filter="category" id="category">
<option value="">全部</option>
{foreach $categoryList as $cate}
<option value="{$cate.id}">{$cate.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">书籍</label>
<div class="layui-input-block">
<div style="color:red;">{$book.name}</div>
<div id="book"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">状态</label>
<div class="layui-input-block">
<select name="status">
<option {if $readingBook.status == 'to-read'}selected{/if} value="to-read">待阅读</option>
<option {if $readingBook.status == 'reading'}selected{/if} value="reading">阅读中</option>
<option {if $readingBook.status == 'finished'}selected{/if} value="finished">已阅读</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开始</label>
<div class="layui-input-block">
<input type="text" class="layui-input" id="start" name="start" value="{$readingBook.start}" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束</label>
<div class="layui-input-block">
<input type="text" class="layui-input" id="end" name="end" value="{$readingBook.end}" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input type="text" name="remark" placeholder="文本框" class="layui-input" value="{$readingBook.remark}" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
<input type="hidden" name="id" value="{$readingBook.id}" />
</form>
<script src="/js/xm-select.js"></script>
<script>
$(document).ready(function(){
var form = layui.form;
var laydate = layui.laydate;
var dropdown = layui.dropdown;
var initValue = ["{$readingBook.book_id}"];
var bookID = {$readingBook.book_id};
console.log(bookID);
laydate.render({
elem: '#start'
});
laydate.render({
elem: '#end'
});
var book = xmSelect.render({
el: '#book',
filterable : true, //开启搜索
remoteSearch: true,
radio : true, //开启单选模式
clickClose : true, //是否点击选项后自动关闭下拉框
name: 'book_id', //表单提交时的name
paging: true,
data : [],
initValue: initValue,
remoteMethod: function(val, cb, show){
var categoryID = $('#category').val();
$.get('/book/apiGetList', {'category_id': categoryID, 'name':val, 'size': 500}, function(data){
var bookList = [];
data.bookList.data.forEach(element => {
var name = element.name + '【' + element.category_name + '】';
if(element.author != ''){
name += '【' + element.author + '】';
}
if(element.id == bookID){
bookList.push({
name: name,
value: element.id,
selected: true
});
}else{
bookList.push({
name: name,
value: element.id
});
}
});
cb(bookList);
})
}
});
form.on('select(category)',function(data){
var categoryID = data.value;
$.get('/book/apiGetList', {'category_id': categoryID, 'size': 50}, function(data){
var bookList = [];
data.bookList.data.forEach(element => {
var name = element.name + '【' + element.category_name + '】';
if(element.author != ''){
name += '【' + element.author + '】';
}
bookList.push({
name: name,
value: element.id
})
});
book.update({data: bookList});
})
})
});
</script>

363
app/view/book/index.html Normal file
View File

@ -0,0 +1,363 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<div class="layui-btn-container layui-col-md2">
<button class="layui-btn layui-btn-sm" lay-event="add">添加</button>
</div>
<form class="layui-form layui-col-space16" action="/book">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<select name="category_id">
<option {if $categoryID == 0}selected{/if} value="0">全部</option>
{foreach $categoryList as $cate}
<option {if $cate['id'] == $categoryID}selected{/if} value="{$cate.id}">{$cate.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-wrap">
<input type="text" name="name" placeholder="书名" value="{$name??''}" lay-affix="clear" class="layui-input">
</div>
</div>
<div class="layui-btn-container layui-col-md3 layui-col-xs12">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="more">
更多
<i class="layui-icon layui-icon-down"></i>
</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var name = "{$name}";
var categoryID = "{$categoryID}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/book/apiGetList?name=' + name + '&category_id=' + categoryID, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 20,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.bookList.total, //解析数据长度
"data": res.bookList.data //解析数据列表
};
},
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'name', width:300, title: '书名'},
{field: 'category_name', width:100, title: '分类'},
{field: 'author', width:300, title: '作者'},
{field: 'remark', width:300, title: '备注'},
{field: 'create_time', title:'加入时间', width: 200},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
dropdown.render({
elem: '#dropdownButton', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'add',
title: '添加'
},{
id: 'update',
title: '编辑'
},{
id: 'delete',
title: '删除'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'add':
layerAlert('get', '添加', '/book/add');
// layer.open({
// title: '添加',
// type: 2,
// area: ['80%','80%'],
// content: '/book/add'
// });
break;
case 'update':
console.log(data);
console.log(obj);
if(data.length !== 1) return layer.msg('请选择一行');
layer.open({
title: '编辑',
type: 1,
area: ['80%','80%'],
content: '<div style="padding: 16px;">自定义表单元素</div>'
});
break;
case 'delete':
if(data.length === 0){
return layer.msg('请选择一行');
}
layer.msg('delete event');
break;
}
}
});
// 重载测试
dropdown.render({
elem: '#reloadTest', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'reload',
title: '重载'
},{
id: 'reload-deep',
title: '重载 - 参数叠加'
},{
id: 'reloadData',
title: '仅重载数据'
},{
id: 'reloadData-deep',
title: '仅重载数据 - 参数叠加'
}],
// 菜单被点击的事件
click: function(obj){
switch(obj.id){
case 'reload':
// 重载 - 默认(参数重置)
table.reload('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
});
break;
case 'reload-deep':
// 重载 - 深度(参数叠加)
table.reload('test', {
where: {
abc: 123,
test: '新的 test1'
},
//defaultToolbar: ['print'], // 重载头部工具栏右侧图标
//cols: ins1.config.cols
}, true);
break;
case 'reloadData':
// 数据重载 - 参数重置
table.reloadData('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
scrollPos: 'fixed', // 保持滚动条位置不变 - v2.7.3 新增
height: 2000, // 测试无效参数(即与数据无关的参数设置无效,此处以 height 设置无效为例)
//url: '404',
//page: {curr: 1, limit: 30} // 重新指向分页
});
break;
case 'reloadData-deep':
// 数据重载 - 参数叠加
table.reloadData('test', {
where: {
abc: 123,
test: '新的 test1'
}
}, true);
break;
}
layer.msg('可观察 Network 请求参数的变化');
}
});
// 行模式
dropdown.render({
elem: '#rowMode',
data: [{
id: 'default-row',
title: '单行模式(默认)'
},{
id: 'multi-row',
title: '多行模式'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'default-row':
table.reload('test', {
lineStyle: null // 恢复单行
});
layer.msg('已设为单行');
break;
case 'multi-row':
table.reload('test', {
// 设置行样式,此处以设置多行高度为例。若为单行,则没必要设置改参数 - 注v2.7.0 新增
lineStyle: 'height: 95px;'
});
layer.msg('即通过设置 lineStyle 参数可开启多行');
break;
}
}
});
},
error: function(res, msg){
console.log(res, msg)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(layui.util.escape(JSON.stringify(data)));
break;
case 'getData':
var getData = table.getData(id);
console.log(getData);
layer.alert(layui.util.escape(JSON.stringify(getData)));
break;
case 'add':
layerAlert('get', '添加', '/book/add');
break;
};
});
// 表头自定义元素工具事件 --- 2.8.8+
table.on('colTool(test)', function(obj){
var event = obj.event;
console.log(obj);
if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项'
});
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'edit'){
layerAlert('get', '添加', '/book/edit?id=' + data.id);
} else if(obj.event === 'more'){
// 更多 - 下拉菜单
dropdown.render({
elem: this, // 触发事件的 DOM 对象
show: true, // 外部事件触发即显示
data: [{
title: '查看',
id: 'detail'
},{
title: '删除',
id: 'del'
}],
click: function(menudata){
if(menudata.id === 'detail'){
layer.msg('查看操作,当前行 ID:'+ data.id);
} else if(menudata.id === 'del'){
layer.confirm('真的删除行 [id: '+ data.id +'] 么', function(index){
obj.del(); // 删除对应行tr的DOM结构
layer.close(index);
// 向服务端发送删除指令
});
}
},
align: 'right', // 右对齐弹出
style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' // 设置额外样式
})
}
});
// 触发表格复选框选择
table.on('checkbox(test)', function(obj){
console.log(obj)
});
// 触发表格单选框选择
table.on('radio(test)', function(obj){
console.log(obj)
});
// 行单击事件
table.on('row(test)', function(obj){
//console.log(obj);
//layer.closeAll('tips');
});
// 行双击事件
table.on('rowDouble(test)', function(obj){
console.log(obj);
});
// 单元格编辑事件
table.on('edit(test)', function(obj){
var field = obj.field; // 得到字段
var value = obj.value; // 得到修改后的值
var data = obj.data; // 得到所在行所有键值
// 值的校验
if(field === 'email'){
if(!/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(obj.value)){
layer.tips('输入的邮箱格式不正确,请重新编辑', this, {tips: 1});
return obj.reedit(); // 重新编辑 -- v2.8.0 新增
}
}
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
// …
layer.msg('编辑成功', {icon: 1});
// 其他更新操作
var update = {};
update[field] = value;
obj.update(update);
});
});
</script>

315
app/view/book/reading.html Normal file
View File

@ -0,0 +1,315 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<div class="layui-btn-container layui-col-md2">
<button class="layui-btn layui-btn-sm" lay-event="add">添加</button>
</div>
<form class="layui-form layui-col-space16" action="/book/reading">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<input type="text" name="name" placeholder="书单名" value="{$name??''}" lay-affix="clear" class="layui-input">
</div>
</div>
<div class="layui-btn-container layui-col-md3 layui-col-xs12">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="readingBook">详情</a>
<a class="layui-btn layui-btn-xs" lay-event="more">
更多
<i class="layui-icon layui-icon-down"></i>
</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var name = "{$name}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/book/apiGetReadingList?name=' + name, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 20,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.readingList.total, //解析数据长度
"data": res.readingList.data //解析数据列表
};
},
cols: [[
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'name', width:300, title: '书单名'},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
dropdown.render({
elem: '#dropdownButton', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'add',
title: '添加'
},{
id: 'update',
title: '编辑'
},{
id: 'delete',
title: '删除'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'add':
layerAlert('get', '添加', '/book/add');
// layer.open({
// title: '添加',
// type: 2,
// area: ['80%','80%'],
// content: '/book/add'
// });
break;
case 'update':
console.log(data);
console.log(obj);
if(data.length !== 1) return layer.msg('请选择一行');
layer.open({
title: '编辑',
type: 1,
area: ['80%','80%'],
content: '<div style="padding: 16px;">自定义表单元素</div>'
});
break;
case 'delete':
if(data.length === 0){
return layer.msg('请选择一行');
}
layer.msg('delete event');
break;
}
}
});
// 重载测试
dropdown.render({
elem: '#reloadTest', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'reload',
title: '重载'
},{
id: 'reload-deep',
title: '重载 - 参数叠加'
},{
id: 'reloadData',
title: '仅重载数据'
},{
id: 'reloadData-deep',
title: '仅重载数据 - 参数叠加'
}],
// 菜单被点击的事件
click: function(obj){
switch(obj.id){
case 'reload':
// 重载 - 默认(参数重置)
table.reload('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
});
break;
case 'reload-deep':
// 重载 - 深度(参数叠加)
table.reload('test', {
where: {
abc: 123,
test: '新的 test1'
},
//defaultToolbar: ['print'], // 重载头部工具栏右侧图标
//cols: ins1.config.cols
}, true);
break;
case 'reloadData':
// 数据重载 - 参数重置
table.reloadData('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
scrollPos: 'fixed', // 保持滚动条位置不变 - v2.7.3 新增
height: 2000, // 测试无效参数(即与数据无关的参数设置无效,此处以 height 设置无效为例)
//url: '404',
//page: {curr: 1, limit: 30} // 重新指向分页
});
break;
case 'reloadData-deep':
// 数据重载 - 参数叠加
table.reloadData('test', {
where: {
abc: 123,
test: '新的 test1'
}
}, true);
break;
}
layer.msg('可观察 Network 请求参数的变化');
}
});
// 行模式
dropdown.render({
elem: '#rowMode',
data: [{
id: 'default-row',
title: '单行模式(默认)'
},{
id: 'multi-row',
title: '多行模式'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'default-row':
table.reload('test', {
lineStyle: null // 恢复单行
});
layer.msg('已设为单行');
break;
case 'multi-row':
table.reload('test', {
// 设置行样式,此处以设置多行高度为例。若为单行,则没必要设置改参数 - 注v2.7.0 新增
lineStyle: 'height: 95px;'
});
layer.msg('即通过设置 lineStyle 参数可开启多行');
break;
}
}
});
},
error: function(res, msg){
console.log(res, msg)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'add':
layerAlert('get', '添加', '/book/addReading');
break;
};
});
// 表头自定义元素工具事件 --- 2.8.8+
table.on('colTool(test)', function(obj){
var event = obj.event;
console.log(obj);
if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项'
});
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'edit'){
layerAlert('get', '添加', '/book/editReading?id=' + data.id);
} else if(obj.event === 'readingBook'){ //读书清单详情
location.href = '/book/readingBook?id=' + data.id;
}
});
// 触发表格复选框选择
table.on('checkbox(test)', function(obj){
console.log(obj)
});
// 触发表格单选框选择
table.on('radio(test)', function(obj){
console.log(obj)
});
// 行单击事件
table.on('row(test)', function(obj){
//console.log(obj);
//layer.closeAll('tips');
});
// 行双击事件
table.on('rowDouble(test)', function(obj){
console.log(obj);
});
// 单元格编辑事件
table.on('edit(test)', function(obj){
var field = obj.field; // 得到字段
var value = obj.value; // 得到修改后的值
var data = obj.data; // 得到所在行所有键值
// 值的校验
if(field === 'email'){
if(!/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(obj.value)){
layer.tips('输入的邮箱格式不正确,请重新编辑', this, {tips: 1});
return obj.reedit(); // 重新编辑 -- v2.8.0 新增
}
}
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
// …
layer.msg('编辑成功', {icon: 1});
// 其他更新操作
var update = {};
update[field] = value;
obj.update(update);
});
});
</script>

View File

@ -0,0 +1,126 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<div class="layui-btn-container layui-col-md2">
<span>{$reading.name}</span>
<button class="layui-btn layui-btn-sm" lay-event="add">添加</button>
</div>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="del">删除</a>
<a class="layui-btn layui-btn-xs" lay-event="start">开始</a>
<a class="layui-btn layui-btn-xs" lay-event="end">结束</a>
</div>
</script>
<!--script src="/layui/soulTable/soulTable.slim.js"></script-->
<script>
layui.config({
base: '/layui/', // 第三方模块所在目录
version: 'v1.9.0' // 插件版本号
}).extend({
soulTable: 'soulTable/soulTable.slim'
});
layui.use(['table', 'dropdown', 'soulTable'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var soulTable = layui.soulTable;
var readingID = "{$reading.id}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/book/apiGetReadingBookList?id=' + readingID, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter'],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
even: true,
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 50,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.bookList.length, //解析数据长度
"data": res.bookList //解析数据列表
};
},
cols: [[
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'book_name', width:300, title: '书名'},
{field: 'author', width:180, title: '作者'},
{field: 'category_name', width:100, title: '分类'},
{field: 'statusStr', width:100, title: '状态'},
{field: 'start', width:120, title: '开始'},
{field: 'end', width:120, title: '结束'},
{field: 'remark', title: '备注'},
{fixed: 'right', title:'操作', minWidth: 60, templet: '#toolDemo'}
]],
rowDrag:{done:function(obj){
//拖动排序
var num = obj.newIndex - obj.oldIndex;
$.post('/book/apiSortReadingBook', {id: obj.row.id, num: num}, function(data){
alert(data.msg);
});
}},
done: function() {
// 在 done 中开启
soulTable.render(this)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'add':
layerAlert('get', '添加', '/book/addReadingBook?reading_id=' + readingID);
break;
};
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'edit'){
layerAlert('get', '编辑', '/book/editReadingBook?id=' + data.id);
} else if(obj.event === 'del'){ //删除
$.get('/book/apiDelReadingBook', {id:data.id},function(data){
layui.layer.msg(data.msg);
});
} else if(obj.event === 'start'){ //开始
$.post('/book/apiStartReadingBook', {id: data.id}, function(data){
layui.layer.msg(data.msg);
});
} else if(obj.event === 'end'){ //结束
$.post('/book/apiEndReadingBook', {id: data.id}, function(data){
layui.layer.msg(data.msg);
});
}
});
});
</script>

View File

@ -0,0 +1,13 @@
<form class="layui-form eject-layuiBox" data-action="/category/add">
<div class="layui-form-item">
<label class="layui-form-label">类名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>

View File

@ -0,0 +1,305 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="getCheckData">获取选中行数据</button>
<button class="layui-btn layui-btn-sm" lay-event="getData">获取当前页数据</button>
<button class="layui-btn layui-btn-sm" id="dropdownButton">
下拉按钮
<i class="layui-icon layui-icon-down layui-font-12"></i>
</button>
<button class="layui-btn layui-btn-sm layui-bg-blue" id="reloadTest">
重载测试
<i class="layui-icon layui-icon-down layui-font-12"></i>
</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" id="rowMode">
<span>{{= d.lineStyle ? '多行' : '单行' }}模式</span>
<i class="layui-icon layui-icon-down layui-font-12"></i>
</button>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="del">删除</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
// 创建渲染实例
table.render({
elem: '#test',
url: '/category/apiGetList', // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
totalRow: true, // 开启合计行
page: true,
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.list.length, //解析数据长度
"data": res.list //解析数据列表
};
},
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'id', fixed: 'left', width:80, title: 'ID', sort: true, totalRow: '合计:'},
{field: 'name', width:300, title: '名称'},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
dropdown.render({
elem: '#dropdownButton', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'add',
title: '添加'
},{
id: 'update',
title: '编辑'
},{
id: 'delete',
title: '删除'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'add':
layerAlert('get', '添加', '/category/add');
break;
case 'update':
console.log(data);
console.log(obj);
if(data.length !== 1) return layer.msg('请选择一行');
layer.open({
title: '编辑',
type: 1,
area: ['80%','80%'],
content: '<div style="padding: 16px;">自定义表单元素</div>'
});
break;
case 'delete':
if(data.length === 0){
return layer.msg('请选择一行');
}
layer.msg('delete event');
break;
}
}
});
// 重载测试
dropdown.render({
elem: '#reloadTest', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'reload',
title: '重载'
},{
id: 'reload-deep',
title: '重载 - 参数叠加'
},{
id: 'reloadData',
title: '仅重载数据'
},{
id: 'reloadData-deep',
title: '仅重载数据 - 参数叠加'
}],
// 菜单被点击的事件
click: function(obj){
switch(obj.id){
case 'reload':
// 重载 - 默认(参数重置)
table.reload('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
});
break;
case 'reload-deep':
// 重载 - 深度(参数叠加)
table.reload('test', {
where: {
abc: 123,
test: '新的 test1'
},
//defaultToolbar: ['print'], // 重载头部工具栏右侧图标
//cols: ins1.config.cols
}, true);
break;
case 'reloadData':
// 数据重载 - 参数重置
table.reloadData('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
scrollPos: 'fixed', // 保持滚动条位置不变 - v2.7.3 新增
height: 2000, // 测试无效参数(即与数据无关的参数设置无效,此处以 height 设置无效为例)
//url: '404',
//page: {curr: 1, limit: 30} // 重新指向分页
});
break;
case 'reloadData-deep':
// 数据重载 - 参数叠加
table.reloadData('test', {
where: {
abc: 123,
test: '新的 test1'
}
}, true);
break;
}
layer.msg('可观察 Network 请求参数的变化');
}
});
// 行模式
dropdown.render({
elem: '#rowMode',
data: [{
id: 'default-row',
title: '单行模式(默认)'
},{
id: 'multi-row',
title: '多行模式'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'default-row':
table.reload('test', {
lineStyle: null // 恢复单行
});
layer.msg('已设为单行');
break;
case 'multi-row':
table.reload('test', {
// 设置行样式,此处以设置多行高度为例。若为单行,则没必要设置改参数 - 注v2.7.0 新增
lineStyle: 'height: 95px;'
});
layer.msg('即通过设置 lineStyle 参数可开启多行');
break;
}
}
});
},
error: function(res, msg){
console.log(res, msg)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(layui.util.escape(JSON.stringify(data)));
break;
case 'getData':
var getData = table.getData(id);
console.log(getData);
layer.alert(layui.util.escape(JSON.stringify(getData)));
break;
};
});
// 表头自定义元素工具事件 --- 2.8.8+
table.on('colTool(test)', function(obj){
var event = obj.event;
console.log(obj);
if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项'
});
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
if(obj.event === 'edit'){
layerAlert('get', '添加', '/book/edit?id=' + data.id);
}else if(obj.event === 'del'){
layer.confirm('真的删除行 [id: '+ data.id +'] 么', function(index){
obj.del(); // 删除对应行tr的DOM结构
layer.close(index);
// 向服务端发送删除指令
});
}
});
// 触发表格复选框选择
table.on('checkbox(test)', function(obj){
console.log(obj)
});
// 触发表格单选框选择
table.on('radio(test)', function(obj){
console.log(obj)
});
// 行单击事件
table.on('row(test)', function(obj){
//console.log(obj);
//layer.closeAll('tips');
});
// 行双击事件
table.on('rowDouble(test)', function(obj){
console.log(obj);
});
// 单元格编辑事件
table.on('edit(test)', function(obj){
var field = obj.field; // 得到字段
var value = obj.value; // 得到修改后的值
var data = obj.data; // 得到所在行所有键值
// 值的校验
if(field === 'email'){
if(!/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(obj.value)){
layer.tips('输入的邮箱格式不正确,请重新编辑', this, {tips: 1});
return obj.reedit(); // 重新编辑 -- v2.8.0 新增
}
}
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
// …
layer.msg('编辑成功', {icon: 1});
// 其他更新操作
var update = {};
update[field] = value;
obj.update(update);
});
});
</script>

View File

@ -0,0 +1,94 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<form class="layui-form layui-row layui-col-space16" action="/customer">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<div id="status"></div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-wrap">
<input type="text" name="name" placeholder="客户名称" lay-affix="clear" class="layui-input">
</div>
</div>
<div class="layui-btn-container layui-col-md3 layui-col-xs12">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</script>
<script src="/js/xm-select.js"></script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var statusList = "{$statusList}";
var name = "{$name}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/customer/apiGetList?statusList=' + statusList + '&name=' + name, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter'],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}',
'.layui-table-cell{height: auto; line-height: normal;white-space: normal;word-break: break-all;overflow: visible;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 50,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.list.total, //解析数据长度
"data": res.list.data //解析数据列表
};
},
cols: [[
{field: 'id', fixed: 'left', width:60, title: 'ID'},
{field: 'name', width:200, title: '客户名称'},
{field: 'assignedToName', width:80, title: '指派给'},
{field: 'stageStr', width:100, title: '阶段'},
{field: 'statusStr', width:120, title: '状态'},
{field: 'communicationStr', title: '沟通记录', templet: function(d) {
return d.communicationStr.replace(/<br \/>/g, '<br>');
}},
{field: 'contactedDateStr', title:'最后联系', width: 110},
{field: 'nextDate', title:'下次联系', width: 110},
{field: 'createdDateStr', title:'添加时间', width: 110}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
},
error: function(res, msg){
console.log(res, msg)
}
});
var customerStatus = xmSelect.render({
// 这里绑定css选择器
el: '#status',
name: 'statusList',
// 渲染的数据
data: {php}echo $statusListSelected;{/php},
})
});
</script>

63
app/view/goods/add.html Normal file
View File

@ -0,0 +1,63 @@
<form class="layui-form eject-layuiBox" data-action="/goods/add">
<div class="layui-form-item">
<label class="layui-form-label">商品</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" maxlength="60" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">平台价格</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="price" maxlength="60" />
<div class="layui-form-mid layui-word-aux">注:一般记录大包的价格</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包库存</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="stock" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">小包库存</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="stock_small" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="sales" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">小包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="sales_small" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包拆小包规格</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="split_to_small" maxlength="60" />
<div class="layui-form-mid layui-word-aux">每中包可拆分成多少个小包如一箱泡面24袋则填写24方便计算每一小包的进货价默认1即不可拆分</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>

View File

@ -0,0 +1,73 @@
<form class="layui-form eject-layuiBox" data-action="/goods/addReceipt">
<div class="layui-form-item">
<label class="layui-form-label">商品</label>
<div class="layui-input-block">
<div id="goods"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进货日期</label>
<div class="layui-input-block">
<input type="text" name="bought_at" class="layui-input" id="bought_at" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包数量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="num" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进货总价</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="total_price" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>
<script src="/js/xm-select.js"></script>
<script>
$(document).ready(function(){
var laydate = layui.laydate;
laydate.render({
elem: '#bought_at'
});
var book = xmSelect.render({
el: '#goods',
filterable : true, //开启搜索
remoteSearch: true,
radio : true, //开启单选模式
clickClose : true, //是否点击选项后自动关闭下拉框
name: 'goods_id', //表单提交时的name
paging: true,
data : [],
remoteMethod: function(val, cb, show){
$.get('/goods/apiGetList', {'name':val, 'size': 50}, function(data){
var goodsList = [];
data.goodsList.data.forEach(element => {
goodsList.push({
name: element.name,
value: element.id
})
});
cb(goodsList);
})
}
});
});
</script>

View File

@ -0,0 +1,81 @@
<form class="layui-form eject-layuiBox" data-action="/goods/addSale">
<div class="layui-form-item">
<label class="layui-form-label">商品</label>
<div class="layui-input-block">
<div id="goods"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">销售总价</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="total_price" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">销售时间</label>
<div class="layui-input-block">
<input type="text" name="sold_at" class="layui-input" id="sold_at" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="num" maxlength="60" value="0" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">小包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="num_small" maxlength="60" value="0" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>
<script src="/js/xm-select.js"></script>
<script>
$(document).ready(function(){
var laydate = layui.laydate;
laydate.render({
elem: '#sold_at'
});
var book = xmSelect.render({
el: '#goods',
filterable : true, //开启搜索
remoteSearch: true,
radio : true, //开启单选模式
clickClose : true, //是否点击选项后自动关闭下拉框
name: 'goods_id', //表单提交时的name
paging: true,
data : [],
remoteMethod: function(val, cb, show){
$.get('/goods/apiGetList', {'name':val, 'size': 50}, function(data){
var goodsList = [];
data.goodsList.data.forEach(element => {
goodsList.push({
name: element.name,
value: element.id
})
});
cb(goodsList);
})
}
});
});
</script>

64
app/view/goods/edit.html Normal file
View File

@ -0,0 +1,64 @@
<form class="layui-form eject-layuiBox" data-action="/goods/edit">
<div class="layui-form-item">
<label class="layui-form-label">商品</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" maxlength="60" value="{$goods.name??''}" />
<div class="layui-form-mid layui-word-aux">建议60个字符长度以内</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">平台价格</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="price" maxlength="60" value="{$goods.price??0}" />
<div class="layui-form-mid layui-word-aux">注:一般记录大包的价格</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包库存</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="stock" maxlength="60" value="{$goods.stock ?? 0}" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">小包库存</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="stock_small" maxlength="60" value="{$goods.stock_small ?? 0}" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="sales" maxlength="60" value="{$goods.sales ?? 0}" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">小包销量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="sales_small" maxlength="60" value="{$goods.sales_small ?? 0}" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包拆小包规格</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="split_to_small" maxlength="60" value="{$goods.split_to_small ?? 1}" />
<div class="layui-form-mid layui-word-aux">每大包可拆分成多少个小包如一箱泡面24袋则填写24方便计算每一小包的进货价默认1即不可拆分</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" vlaue="{$goods.remark ?? ''}" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
<input type="hidden" name="id" value="{$goods.id}" />
</form>

View File

@ -0,0 +1,54 @@
<form class="layui-form eject-layuiBox" data-action="/goods/editReceipt">
<div class="layui-form-item">
<label class="layui-form-label">商品</label>
<div class="layui-input-block">
<select lay-filter="goods" id="goods" name="goods_id">
{foreach $goodsList as $goods}
<option value="{$goods.id}">{$goods.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进货日期</label>
<div class="layui-input-block">
<input type="text" name="bought_at" class="layui-input" id="bought_at" placeholder="yyyy-MM-dd">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">大包数量</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="num" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">进货总价</label>
<div class="layui-input-block">
<input class="layui-input" type="number" name="total_price" maxlength="60" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">备注</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="remark" maxlength="200" />
</div>
</div>
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="submitAll">保存</button>
</div>
</form>
<script>
$(document).ready(function(){
var laydate = layui.laydate;
laydate.render({
elem: '#bought_at'
});
});
</script>

357
app/view/goods/index.html Normal file
View File

@ -0,0 +1,357 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<div class="layui-btn-container layui-col-md2">
<button class="layui-btn layui-btn-sm" lay-event="add">添加</button>
</div>
<form class="layui-form layui-col-space16" action="/goods">
<div class="layui-col-md2">
<div class="layui-input-wrap">
<input type="text" name="name" placeholder="商品名" value="{$name??''}" lay-affix="clear" class="layui-input">
</div>
</div>
<div class="layui-col-md2">
<div class="layui-input-wrap">
<select name="order">
<option {if $order == 'id_desc'}selected{/if} value="id_desc">ID降序</option>
<option {if $order == 'stock_desc'}selected{/if} value="stock_desc">大包库存降序</option>
<option {if $order == 'stock_asc'}selected{/if} value="stock_asc">大包库存升序</option>
</select>
</div>
</div>
<div class="layui-btn-container layui-col-md2">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="more">
更多
<i class="layui-icon layui-icon-down"></i>
</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var name = "{$name}";
var order = "{$order}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/goods/apiGetList?name=' + name + '&order=' + order, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 20,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.goodsList.total, //解析数据长度
"data": res.goodsList.data //解析数据列表
};
},
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'name', width:300, title: '商品'},
{field: 'price', width:90, title: '价格'},
{field: 'stock', width:90, title: '大包库存'},
{field: 'stock_small', width:90, title: '小包库存'},
{field: 'sales', width:90, title: '大包销量'},
{field: 'sales_small', width:90, title: '小包销量'},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
dropdown.render({
elem: '#dropdownButton', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'add',
title: '添加'
},{
id: 'update',
title: '编辑'
},{
id: 'delete',
title: '删除'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'add':
layerAlert('get', '添加', '/goods/add');
break;
case 'update':
console.log(data);
console.log(obj);
if(data.length !== 1) return layer.msg('请选择一行');
layer.open({
title: '编辑',
type: 1,
area: ['80%','80%'],
content: '<div style="padding: 16px;">自定义表单元素</div>'
});
break;
case 'delete':
if(data.length === 0){
return layer.msg('请选择一行');
}
layer.msg('delete event');
break;
}
}
});
// 重载测试
dropdown.render({
elem: '#reloadTest', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'reload',
title: '重载'
},{
id: 'reload-deep',
title: '重载 - 参数叠加'
},{
id: 'reloadData',
title: '仅重载数据'
},{
id: 'reloadData-deep',
title: '仅重载数据 - 参数叠加'
}],
// 菜单被点击的事件
click: function(obj){
switch(obj.id){
case 'reload':
// 重载 - 默认(参数重置)
table.reload('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
});
break;
case 'reload-deep':
// 重载 - 深度(参数叠加)
table.reload('test', {
where: {
abc: 123,
test: '新的 test1'
},
//defaultToolbar: ['print'], // 重载头部工具栏右侧图标
//cols: ins1.config.cols
}, true);
break;
case 'reloadData':
// 数据重载 - 参数重置
table.reloadData('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
scrollPos: 'fixed', // 保持滚动条位置不变 - v2.7.3 新增
height: 2000, // 测试无效参数(即与数据无关的参数设置无效,此处以 height 设置无效为例)
//url: '404',
//page: {curr: 1, limit: 30} // 重新指向分页
});
break;
case 'reloadData-deep':
// 数据重载 - 参数叠加
table.reloadData('test', {
where: {
abc: 123,
test: '新的 test1'
}
}, true);
break;
}
layer.msg('可观察 Network 请求参数的变化');
}
});
// 行模式
dropdown.render({
elem: '#rowMode',
data: [{
id: 'default-row',
title: '单行模式(默认)'
},{
id: 'multi-row',
title: '多行模式'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'default-row':
table.reload('test', {
lineStyle: null // 恢复单行
});
layer.msg('已设为单行');
break;
case 'multi-row':
table.reload('test', {
// 设置行样式,此处以设置多行高度为例。若为单行,则没必要设置改参数 - 注v2.7.0 新增
lineStyle: 'height: 95px;'
});
layer.msg('即通过设置 lineStyle 参数可开启多行');
break;
}
}
});
},
error: function(res, msg){
console.log(res, msg)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(layui.util.escape(JSON.stringify(data)));
break;
case 'getData':
var getData = table.getData(id);
console.log(getData);
layer.alert(layui.util.escape(JSON.stringify(getData)));
break;
case 'add':
layerAlert('get', '添加', '/goods/add');
break;
};
});
// 表头自定义元素工具事件 --- 2.8.8+
table.on('colTool(test)', function(obj){
var event = obj.event;
console.log(obj);
if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项'
});
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'edit'){
layerAlert('get', '编辑', '/goods/edit?id=' + data.id);
} else if(obj.event === 'more'){
// 更多 - 下拉菜单
dropdown.render({
elem: this, // 触发事件的 DOM 对象
show: true, // 外部事件触发即显示
data: [{
title: '查看',
id: 'detail'
},{
title: '删除',
id: 'del'
}],
click: function(menudata){
if(menudata.id === 'detail'){
layer.msg('查看操作,当前行 ID:'+ data.id);
} else if(menudata.id === 'del'){
layer.confirm('真的删除行 [id: '+ data.id +'] 么', function(index){
obj.del(); // 删除对应行tr的DOM结构
layer.close(index);
// 向服务端发送删除指令
});
}
},
align: 'right', // 右对齐弹出
style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' // 设置额外样式
})
}
});
// 触发表格复选框选择
table.on('checkbox(test)', function(obj){
console.log(obj)
});
// 触发表格单选框选择
table.on('radio(test)', function(obj){
console.log(obj)
});
// 行单击事件
table.on('row(test)', function(obj){
//console.log(obj);
//layer.closeAll('tips');
});
// 行双击事件
table.on('rowDouble(test)', function(obj){
console.log(obj);
});
// 单元格编辑事件
table.on('edit(test)', function(obj){
var field = obj.field; // 得到字段
var value = obj.value; // 得到修改后的值
var data = obj.data; // 得到所在行所有键值
// 值的校验
if(field === 'email'){
if(!/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(obj.value)){
layer.tips('输入的邮箱格式不正确,请重新编辑', this, {tips: 1});
return obj.reedit(); // 重新编辑 -- v2.8.0 新增
}
}
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
// …
layer.msg('编辑成功', {icon: 1});
// 其他更新操作
var update = {};
update[field] = value;
obj.update(update);
});
});
</script>

258
app/view/goods/receipt.html Normal file
View File

@ -0,0 +1,258 @@
{layout name="layout" /}
<div class="layui-row layui-col-space12">
<div class="layui-col-xs12 layui-col-md12">
<div class="layuimini-container location-index-page">
<div class="layuimini-main">
<fieldset class="table-search-fieldset">
<legend>搜索信息</legend>
<div style="margin: 10px 10px 10px 10px">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-input-block">
<input class="layui-input" name="keyword" placeholder="支持模糊查询">
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
<button type="reset" class="layui-btn layui-btn-primary" ><i class="layui-icon"></i> 重 置</button>
</div>
</div>
<div class="layui-word-aux">
1. 添加日志,直接新增对应商品库存<br>
2. 删除日志,不会扣除已添加库存<br>
3. 修改日志,只能修改总价和进货日期<br>
4. 大包可拆分小包数量=当时商品的可拆分数量如仅记录当时1大包可以拆分成10小包后期改包装后1大包可以拆分20包不影响<br>
5. 小包进货单价=大包单价➗大包可拆分小包数量<br>
</div>
</form>
</div>
</fieldset>
<div class="image-table">
<table id="table-container" class="layui-table" data-url="/goods/apiGetReceiptList" lay-filter="table-container-filter"></table>
</div>
</div>
</div>
</div>
</div>
<!-- 隐藏列 -->
<!-- 编辑单元格提交url -->
<input type="hidden" id="row-modify" data-url="/manager.goodsReceiptLog/modify">
<!-- 操作列 -->
<script type="text/html" id="row-operate">
<a class="layui-btn layui-btn-primary layui-btn-xs" data-href="/goods/editReceipt?id={{d.id}}" data-title="编辑" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" data-href="/goods/delReceipt.html" lay-event="del">删除</a>
</script>
<!-- toolbar -->
<script type="text/html" id="toolbar-tpl">
<a class="layui-btn layui-btn-primary layui-btn-sm" data-table-refresh lay-event="refresh"><i class="fa fa-refresh"></i></a>
<a class="layui-btn layui-btn-normal layui-btn-sm" data-href="/manager.goodsReceiptLog/add.html" data-title="添加" lay-event="add">添加</a>
<a class="layui-btn layui-btn-danger layui-btn-sm" data-href="/manager.goodsReceiptLog/del.html" lay-event="del">删除</a>
</script>
<script>
layui.use(['laytpl', 'table', 'jquery', 'form', 'laydate'], function () {
let $ = layui.jquery,
table = layui.table,
laydate = layui.laydate,
form = layui.form;
/**** index begin ***/
if ($('.location-index-page').length > 0) {
// 渲染表格
let listUrl = $('#table-container').data('url');
let insTb = table.render({
elem: '#table-container',
title: '列表',
defaultToolbar: ['filter', 'exports', {
title: '搜索' //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
, layEvent: 'search'
, icon: 'layui-icon-search'
}],
toolbar: '#toolbar-tpl',
//method: 'POST',
url: listUrl,
page: true,
limit: 20,
limits: [20,50,100,200,500,1000],
request: {
pageName: 'page',
limitName: 'size',
},
parseData: function (res) {
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.receiptList.total, //解析数据长度
"data": res.receiptList.data //解析数据列表
};
},
cols: [[
{type: 'checkbox'},
{field: 'id', title: '#'},
{field: 'goods_name', title: '商品名称'},
{field: 'total_price', title: '总价'},
{field: 'num', title: '大包数量'},
{field: 'cost_price', title: '大包进货单价'},
{field: 'small_cost_price', title: '小包进货单价'},
{field: 'split_to_small', title: '大包可拆分小包数量'},
{field: 'bought_at', title: '进货日期'}
,{templet: '#row-operate', minWidth: 180, align: 'center', title: '操作'}
]],
done: function () {
//Tools.setInsTb(insTb);
}
});
//监听工具条 注意区别toolbar和tool toolbar是表头上的工具条 tool是行中的工具条
table.on('toolbar(table-container-filter)', function (obj) {
let layEvent = obj.event;
//let insTb = Tools.getInsTb();
let url = $($(this).context).data('href')
let title = $($(this).context).data('title')
let width = $($(this).context).data('width') ? $($(this).context).data('width') : '100%';
let height = $($(this).context).data('height') ? $($(this).context).data('height') : '100%';
let checkStatus = table.checkStatus('table-container');
let selected = checkStatus.data;
let ids = [];
switch (layEvent) {
// toolbar 删除
case 'del':
if (checkStatus.data.length <= 0) {
layer.msg('请先选择数据');
return false;
}
// let selected = checkStatus.data;
// let ids = [];
$.each(selected, function (index, val) {
ids.push(val.id);
})
//delRow(url, ids, insTb);
return false;
// toolbar 刷新
case 'refresh':
//refreshTab(insTb);
return false;
// toolbar 搜索
case 'search':
let search = $('.table-search-fieldset');
if (search.hasClass('div-show')) {
search.css('display', 'none').removeClass('div-show');
} else {
search.css('display', 'block').addClass('div-show');
}
return false;
// 其他 默认为打开弹出层
default:
if (layEvent !== 'LAYTABLE_COLS' && layEvent !== 'LAYTABLE_EXPORT') {
layerAlert('get', '添加', '/goods/addReceipt');
//openLayer(url, title, width, height);
return false;
}
}
});
//监听行工具条
table.on('tool(table-container-filter)', function (obj) {
let data = obj.data;
let layEvent = obj.event;
let url = $($(this).context).data('href');
console.log($(this).data('href'));
console.log(data.id);
let title = $($(this).context).data('title');
let width = $($(this).context).data('width') ? $($(this).context).data('width') : '100%';
let height = $($(this).context).data('height') ? $($(this).context).data('height') : '100%';
//let insTb = Tools.getInsTb();
switch (layEvent) {
// 行 删除
case 'del':
let url = '/goods/apiDelReceipt';
$.post(url, {id: data.id}, function (res) {
if (res.code === 0) {
insTb.reload();
}
layer.msg(res.msg);
});
return false;
//其他 默认为打开弹出层
default:
layerAlert('get', title, url);
//openLayer(url, title, width, height);
return false;
}
});
// 监听
let modifyUrl = $('#row-modify').data('url');
table.on('edit(table-container)', function (obj) {
let id = obj.data.id;
$.ajax(modifyUrl, {
data: {"id": id, "field": obj.field, "value": obj.value}
,dataType : 'json'
,type: 'POST'
})
.done(function (res) {
if (res.code === 0) {
insTb.reload();
}
})
});
// switch变更
function changeSwitch(filter) {
form.on('switch(' + filter + ')', function (obj) {
let val = obj.elem.checked ? 1 : 0;
$.post(modifyUrl, {id: this.value, field: this.name, value: val}, function (res) {
layer.msg(res.msg)
if (res.code !== 0) {
//操作不成功则刷新页面
insTb.reload();
}
})
});
}
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
//执行搜索重载
table.reload('table-container', {
page: {curr: 1}
, where: data.field
}, 'data');
return false;
});
}
/*** index end ***/
if ($('.location-operate-page').length > 0) {
let parentCategory = $('#parent-category');
let categoryList = parentCategory.data('list') ? parentCategory.data('list') : [];
laydate.render({
elem: '#published-at',
type: 'datetime',
});
laydate.render({
elem: '#bought_at'
});
}
});
</script>

357
app/view/goods/sale.html Normal file
View File

@ -0,0 +1,357 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<div class="layui-btn-container layui-col-md2">
<button class="layui-btn layui-btn-sm" lay-event="add">添加</button>
</div>
<form class="layui-form layui-col-space16" action="/goods/sale">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<select name="goods_id">
<option {if $goodsID == 0}selected{/if} value="0">全部</option>
{foreach $goodsList as $goods}
<option {if $goods['id'] == $goodsID}selected{/if} value="{$goods.id}">{$goods.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="layui-btn-container layui-col-md3 layui-col-xs12">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-xs" lay-event="more">
更多
<i class="layui-icon layui-icon-down"></i>
</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
// 创建渲染实例
table.render({
elem: '#test',
url: '/goods/apiGetSaleList', // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 20,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.saleList.total, //解析数据长度
"data": res.saleList.data //解析数据列表
};
},
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'goods_name', width:300, title: '商品'},
{field: 'total_price', width:100, title: '销售总价'},
{field: 'num', width:100, title: '大包销量'},
{field: 'num_small', width:100, title: '小包销量'},
{field: 'sold_at', title:'销售时间', width: 150},
{field: 'remark', title:'备注', width: 200},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
var id = this.id;
// 下拉按钮测试
dropdown.render({
elem: '#dropdownButton', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'add',
title: '添加'
},{
id: 'update',
title: '编辑'
},{
id: 'delete',
title: '删除'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'add':
layerAlert('get', '添加', '/goods/addSale');
// layer.open({
// title: '添加',
// type: 2,
// area: ['80%','80%'],
// content: '/book/add'
// });
break;
case 'update':
console.log(data);
console.log(obj);
if(data.length !== 1) return layer.msg('请选择一行');
layer.open({
title: '编辑',
type: 1,
area: ['80%','80%'],
content: '<div style="padding: 16px;">自定义表单元素</div>'
});
break;
case 'delete':
if(data.length === 0){
return layer.msg('请选择一行');
}
layer.msg('delete event');
break;
}
}
});
// 重载测试
dropdown.render({
elem: '#reloadTest', // 可绑定在任意元素中,此处以上述按钮为例
data: [{
id: 'reload',
title: '重载'
},{
id: 'reload-deep',
title: '重载 - 参数叠加'
},{
id: 'reloadData',
title: '仅重载数据'
},{
id: 'reloadData-deep',
title: '仅重载数据 - 参数叠加'
}],
// 菜单被点击的事件
click: function(obj){
switch(obj.id){
case 'reload':
// 重载 - 默认(参数重置)
table.reload('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
});
break;
case 'reload-deep':
// 重载 - 深度(参数叠加)
table.reload('test', {
where: {
abc: 123,
test: '新的 test1'
},
//defaultToolbar: ['print'], // 重载头部工具栏右侧图标
//cols: ins1.config.cols
}, true);
break;
case 'reloadData':
// 数据重载 - 参数重置
table.reloadData('test', {
where: {
abc: '123456',
//test: '新的 test2',
//token: '新的 token2'
},
scrollPos: 'fixed', // 保持滚动条位置不变 - v2.7.3 新增
height: 2000, // 测试无效参数(即与数据无关的参数设置无效,此处以 height 设置无效为例)
//url: '404',
//page: {curr: 1, limit: 30} // 重新指向分页
});
break;
case 'reloadData-deep':
// 数据重载 - 参数叠加
table.reloadData('test', {
where: {
abc: 123,
test: '新的 test1'
}
}, true);
break;
}
layer.msg('可观察 Network 请求参数的变化');
}
});
// 行模式
dropdown.render({
elem: '#rowMode',
data: [{
id: 'default-row',
title: '单行模式(默认)'
},{
id: 'multi-row',
title: '多行模式'
}],
// 菜单被点击的事件
click: function(obj){
var checkStatus = table.checkStatus(id)
var data = checkStatus.data; // 获取选中的数据
switch(obj.id){
case 'default-row':
table.reload('test', {
lineStyle: null // 恢复单行
});
layer.msg('已设为单行');
break;
case 'multi-row':
table.reload('test', {
// 设置行样式,此处以设置多行高度为例。若为单行,则没必要设置改参数 - 注v2.7.0 新增
lineStyle: 'height: 95px;'
});
layer.msg('即通过设置 lineStyle 参数可开启多行');
break;
}
}
});
},
error: function(res, msg){
console.log(res, msg)
}
});
// 工具栏事件
table.on('toolbar(test)', function(obj){
var id = obj.config.id;
var checkStatus = table.checkStatus(id);
var othis = lay(this);
switch(obj.event){
case 'getCheckData':
var data = checkStatus.data;
layer.alert(layui.util.escape(JSON.stringify(data)));
break;
case 'getData':
var getData = table.getData(id);
console.log(getData);
layer.alert(layui.util.escape(JSON.stringify(getData)));
break;
case 'add':
layerAlert('get', '添加', '/goods/addSale');
break;
};
});
// 表头自定义元素工具事件 --- 2.8.8+
table.on('colTool(test)', function(obj){
var event = obj.event;
console.log(obj);
if(event === 'email-tips'){
layer.alert(layui.util.escape(JSON.stringify(obj.col)), {
title: '当前列属性配置项'
});
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'edit'){
layerAlert('get', '添加', '/book/edit?id=' + data.id);
} else if(obj.event === 'more'){
// 更多 - 下拉菜单
dropdown.render({
elem: this, // 触发事件的 DOM 对象
show: true, // 外部事件触发即显示
data: [{
title: '查看',
id: 'detail'
},{
title: '删除',
id: 'del'
}],
click: function(menudata){
if(menudata.id === 'detail'){
layer.msg('查看操作,当前行 ID:'+ data.id);
} else if(menudata.id === 'del'){
layer.confirm('真的删除行 [id: '+ data.id +'] 么', function(index){
obj.del(); // 删除对应行tr的DOM结构
layer.close(index);
// 向服务端发送删除指令
});
}
},
align: 'right', // 右对齐弹出
style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' // 设置额外样式
})
}
});
// 触发表格复选框选择
table.on('checkbox(test)', function(obj){
console.log(obj)
});
// 触发表格单选框选择
table.on('radio(test)', function(obj){
console.log(obj)
});
// 行单击事件
table.on('row(test)', function(obj){
//console.log(obj);
//layer.closeAll('tips');
});
// 行双击事件
table.on('rowDouble(test)', function(obj){
console.log(obj);
});
// 单元格编辑事件
table.on('edit(test)', function(obj){
var field = obj.field; // 得到字段
var value = obj.value; // 得到修改后的值
var data = obj.data; // 得到所在行所有键值
// 值的校验
if(field === 'email'){
if(!/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(obj.value)){
layer.tips('输入的邮箱格式不正确,请重新编辑', this, {tips: 1});
return obj.reedit(); // 重新编辑 -- v2.8.0 新增
}
}
// 编辑后续操作,如提交更新请求,以完成真实的数据更新
// …
layer.msg('编辑成功', {icon: 1});
// 其他更新操作
var update = {};
update[field] = value;
obj.update(update);
});
});
</script>

14
app/view/index/view.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico"/>
<title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>

95
app/view/layout.html Normal file
View File

@ -0,0 +1,95 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>layout 管理界面大布局示例 - Layui</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/layui/css/layui.css" rel="stylesheet">
<script src="/js/jquery-3.7.1.min.js"></script>
<script src="/layui/layui.js"></script>
<script src="/js/work.js"></script>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">layout demo</div>
<!-- 头部区域可配合layui 已有的水平导航) -->
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域可配合layui已有的垂直导航 -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">个人生活</a>
<dl class="layui-nav-child">
<dd><a href="/book/">书籍</a></dd>
<dd><a href="/category/">分类</a></dd>
<dd><a href="/book/reading">书单</a></dd>
<dd><a href="javascript:;">the links</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">工作相关</a>
<dl class="layui-nav-child">
<dd><a href="/customer/">客户跟进</a></dd>
<dd><a href="/salary/">工资计算</a></dd>
<dd><a href="/task/">项目任务</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">有机壹家</a>
<dl class="layui-nav-child">
<dd><a href="/goods/">商品</a></dd>
<dd><a href="/goods/receipt/">进货日志</a></dd>
<dd><a href="/goods/sale/">销售日志</a></dd>
<dd><a href="/sales/index/">销量统计</a></dd>
<dd><a href="/sales/goods/">商品销售统计</a></dd>
</dl>
</li>
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
{__CONTENT__}
</div>
<div class="layui-footer">
<!-- 底部固定区域 -->
底部固定区域
</div>
</div>
<script>
//JS
layui.use(['element', 'layer', 'util'], function(){
var element = layui.element;
var layer = layui.layer;
var util = layui.util;
var $ = layui.$;
//头部事件
util.event('lay-header-event', {
menuLeft: function(othis){ // 左侧菜单事件
layer.msg('展开左侧菜单的操作', {icon: 0});
},
menuRight: function(){ // 右侧菜单事件
layer.open({
type: 1,
title: '更多',
content: '<div style="padding: 15px;">处理右侧面板的操作</div>',
area: ['260px', '100%'],
offset: 'rt', // 右上角
anim: 'slideLeft', // 从右侧抽屉滑出
shadeClose: true,
scrollbar: false
});
}
});
});
</script>
</body>
</html>

124
app/view/sales/goods.html Normal file
View File

@ -0,0 +1,124 @@
{layout name="layout" /}
<div style="padding: 16px;">
<div class="layui-row">
<div class="layui-col-md3" >
<label class="layui-form-label">开始时间</label>
<div class="layui-input-block">
<input type="text" name="start" class="layui-input" id="start" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-col-md3">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-block">
<input type="text" name="end" class="layui-input" id="end" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-block">
<select lay-filter="order" id="order" name="order">
<option value="total_sales">total_sales</option>
<option value="total_num">total_num</option>
<option value="total_num_small">total_num_small</option>
</select>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" id="search">搜索</button>
</div>
</div>
</div>
<div id="sales" style="width: 100%;height:1500px;"></div>
</div>
<script src="/js/echarts.min.js"></script>
<script type="text/javascript">
var laydate = layui.laydate;
laydate.render({
elem: '#start'
});
laydate.render({
elem: '#end'
});
$('#search').click(function() {
var start = $('#start').val();
var end = $('#end').val();
var order = $('#order').val();
getData(start, end, order);
});
getData();
// 基于准备好的dom初始化echarts实例
var myChart = echarts.init(document.getElementById('sales'));
function getData(start = '', end = '', order = 'total_sales'){
$.get('/sales/apiGetSalesGoods', {start: start, end: end, order: order}).done(function(data) {
// 指定图表的配置项和数据
var option = {
title: {
text: '产品销售统计'
},
tooltip: {},
legend: {
data: ['产品销售额', '大包销量', '小包销量']
},
xAxis: {
// data: data.data.goods_name.map(function(item) {
// return item;
// })
},
yAxis: {
//type: 'value'
data: data.data.goods_name.map(function(item) {
return item;
})
},
series: [
{
name: '产品销售额',
type: 'bar',
data: data.data.total_sales.map(function(item) {
return item;
}),
label: {
normal: {
show: true,
position: 'top'
}
}
},{
name: '大包销量',
type: 'bar',
data: data.data.total_num.map(function(item) {
return item;
}),
label: {
normal: {
show: true,
position: 'top'
}
}
},{
name: '小包销量',
type: 'bar',
data: data.data.total_num_small.map(function(item) {
return item;
}),
label: {
normal: {
show: true,
position: 'top'
}
}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
});
}
</script>

128
app/view/sales/index.html Normal file
View File

@ -0,0 +1,128 @@
{layout name="layout" /}
<div style="padding: 16px;">
<div class="layui-row">
<div class="layui-col-md3" >
<label class="layui-form-label">开始时间</label>
<div class="layui-input-block">
<input type="text" name="start" class="layui-input" id="start" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-col-md3">
<label class="layui-form-label">结束时间</label>
<div class="layui-input-block">
<input type="text" name="end" class="layui-input" id="end" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-col-md3">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" id="search">搜索</button>
</div>
</div>
</div>
<div id="sales" style="width: 100%;height:400px;"></div>
</div>
<script src="/js/echarts.min.js"></script>
<script type="text/javascript">
var laydate = layui.laydate;
laydate.render({
elem: '#start'
});
laydate.render({
elem: '#end'
});
$('#search').click(function() {
var start = $('#start').val();
var end = $('#end').val();
getData(start, end);
});
getData();
// 基于准备好的dom初始化echarts实例
var myChart = echarts.init(document.getElementById('sales'));
function getData(start = '', end = ''){
$.get('/sales/apiGetSales', {start: start, end: end}).done(function(data) {
// 指定图表的配置项和数据
var option = {
title: {
text: '销售统计'
},
tooltip: {},
legend: {
data: ['日销额']
},
xAxis: {
data: data.data.sold_at.map(function(item) {
return item;
})
},
yAxis: {
//type: 'value'
},
series: [
{
name: '日销额',
type: 'line',
data: data.data.total_sales.map(function(item) {
return item;
}),
label: {
normal: {
show: true,
position: 'top'
}
}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
});
}
/*
$.get('/statistics/apiGetSales').done(function(data) {
// 指定图表的配置项和数据
var option = {
title: {
text: '销售统计'
},
tooltip: {},
legend: {
data: ['日销额']
},
xAxis: {
data: data.data.sold_at.map(function(item) {
return item;
})
},
yAxis: {
//type: 'value'
},
series: [
{
name: '日销额',
type: 'line',
data: data.data.total_sales.map(function(item) {
return item;
}),
label: {
normal: {
show: true,
position: 'top'
}
}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
});
*/
</script>

100
app/view/task/index.html Normal file
View File

@ -0,0 +1,100 @@
{layout name="layout" /}
<div style="padding: 16px;">
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-row">
<form class="layui-form layui-col-space16" action="/task">
<div class="layui-col-md3">
<div class="layui-input-wrap">
<input type="text" name="name" placeholder="任务名" value="{$name??''}" lay-affix="clear" class="layui-input">
</div>
</div>
<div class="layui-btn-container layui-col-md3 layui-col-xs12">
<button class="layui-btn" lay-submit lay-filter="demo-table-search">搜索</button>
<button type="reset" class="layui-btn layui-btn-primary">Clear</button>
</div>
</form>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="detail">详情</a>
</div>
</script>
<script>
layui.use(['table', 'dropdown'], function(){
var table = layui.table;
var dropdown = layui.dropdown;
var name = "{$name ?? ''}";
// 创建渲染实例
table.render({
elem: '#test',
url: '/task/apiGetList?name=' + name, // 此处为静态模拟数据,实际使用时需换成真实接口
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print', { // 右上角工具图标
title: '提示',
layEvent: 'LAYTABLE_TIPS',
icon: 'layui-icon-tips',
onClick: function(obj) { // 2.9.12+
layer.alert('自定义工具栏图标按钮');
}
}],
height: 'full-135', // 最大高度减去其他容器已占有的高度差
css: [ // 重设当前表格样式
'.layui-table-tool-temp{padding-right: 145px;}'
].join(''),
cellMinWidth: 80,
//totalRow: true, // 开启合计行
//page: true,
page: { // 支持传入 laypage 组件的所有参数某些参数除外jump/elem - 详见文档
layout: ['limit', 'count', 'prev', 'page', 'next', 'skip'], //自定义分页布局
//curr: 5, //设定初始在第 5 页
limit: 20,
//groups: 1, //只显示 1 个连续页码
//first: false, //不显示首页
//last: false //不显示尾页
},
// 将原始数据解析成 table 组件所规定的数据格式
parseData: function(res){
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.taskList.total, //解析数据长度
"data": res.taskList.data //解析数据列表
};
},
cols: [[
{type: 'checkbox', fixed: 'left'},
{field: 'id', fixed: 'left', width:80, title: 'ID'},
{field: 'name', width:300, title: '任务'},
{field: 'projectName', width:150, title: '项目'},
{field: 'executionName', width:150, title: '执行'},
{field: 'statusName', width:80, title: '状态'},
{field: 'assignedToName', width:80, title: '指派给'},
{field: 'finishedByName', width:80, title: '完成者'},
{field: 'finishedDate', title:'完成时间', width: 180},
{fixed: 'right', title:'操作', minWidth: 125, templet: '#toolDemo'}
]],
done: function(){
},
error: function(res, msg){
console.log(res, msg)
}
});
// 触发单元格工具事件
table.on('tool(test)', function(obj){ // 双击 toolDouble
var data = obj.data; // 获得当前行数据
// console.log(obj)
if(obj.event === 'detail'){
//http://pms.scdxtc.cn/index.php?m=task&f=edit&taskID=57
window.open('http://pms.scdxtc.cn/index.php?m=task&f=edit&taskID=57', '_blank');
//window.open('http://pms.scdxtc.cn/index.php?m=task&f=edit&taskID=' + data.id, '_blank');
}
});
});
</script>

59
composer.json Normal file
View File

@ -0,0 +1,59 @@
{
"name": "workerman/webman",
"type": "project",
"keywords": [
"high performance",
"http service"
],
"homepage": "https://www.workerman.net",
"license": "MIT",
"description": "High performance HTTP Service Framework.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/webman/issues",
"forum": "https://wenda.workerman.net/",
"wiki": "https://workerman.net/doc/webman",
"source": "https://github.com/walkor/webman"
},
"require": {
"php": ">=7.2",
"workerman/webman-framework": "^1.5.0",
"monolog/monolog": "^2.0",
"topthink/think-template": "^3.0",
"webman/think-orm": "^1.1",
"phpoffice/phpspreadsheet": "^3.6"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {
"": "./",
"app\\": "./app",
"App\\": "./app",
"app\\View\\Components\\": "./app/view/components"
},
"files": [
"./support/helpers.php"
]
},
"scripts": {
"post-package-install": [
"support\\Plugin::install"
],
"post-package-update": [
"support\\Plugin::install"
],
"pre-package-uninstall": [
"support\\Plugin::uninstall"
]
}
}

1187
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
config/app.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Request;
return [
'debug' => true,
'error_reporting' => E_ALL,
'default_timezone' => 'Asia/Shanghai',
'request_class' => Request::class,
'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
'controller_suffix' => 'Controller',
'controller_reuse' => false,
];

21
config/autoload.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'files' => [
base_path() . '/app/functions.php',
base_path() . '/support/Request.php',
base_path() . '/support/Response.php',
]
];

19
config/bootstrap.php Normal file
View File

@ -0,0 +1,19 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
support\bootstrap\Session::class,
support\bootstrap\LaravelDb::class,
Webman\ThinkOrm\ThinkOrm::class,
];

15
config/container.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return new Webman\Container;

15
config/database.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

15
config/dependence.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

17
config/exception.php Normal file
View File

@ -0,0 +1,17 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'' => support\exception\Handler::class,
];

32
config/log.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'handlers' => [
[
'class' => Monolog\Handler\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/webman.log',
7, //$maxFiles
Monolog\Logger::DEBUG,
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];

15
config/middleware.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [];

42
config/process.php Normal file
View File

@ -0,0 +1,42 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
global $argv;
return [
// File update detection and automatic reload
'monitor' => [
'handler' => process\Monitor::class,
'reloadable' => false,
'constructor' => [
// Monitor these directories
'monitorDir' => array_merge([
app_path(),
config_path(),
base_path() . '/process',
base_path() . '/support',
base_path() . '/resource',
base_path() . '/.env',
], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')),
// Files with these suffixes will be monitored
'monitorExtensions' => [
'php', 'html', 'htm', 'env'
],
'options' => [
'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/',
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
]
]
]
];

22
config/redis.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
],
];

21
config/route.php Normal file
View File

@ -0,0 +1,21 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Route;

31
config/server.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
return [
'listen' => 'http://0.0.0.0:8787',
'transport' => 'tcp',
'context' => [],
'name' => 'webman',
'count' => cpu_count() * 4,
'user' => '',
'group' => '',
'reusePort' => false,
'event_loop' => '',
'stop_timeout' => 2,
'pid_file' => runtime_path() . '/webman.pid',
'status_file' => runtime_path() . '/webman.status',
'stdout_file' => runtime_path() . '/logs/stdout.log',
'log_file' => runtime_path() . '/logs/workerman.log',
'max_package_size' => 10 * 1024 * 1024
];

65
config/session.php Normal file
View File

@ -0,0 +1,65 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Webman\Session\FileSessionHandler;
use Webman\Session\RedisSessionHandler;
use Webman\Session\RedisClusterSessionHandler;
return [
'type' => 'file', // or redis or redis_cluster
'handler' => FileSessionHandler::class,
'config' => [
'file' => [
'save_path' => runtime_path() . '/sessions',
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'auth' => '',
'timeout' => 2,
'database' => '',
'prefix' => 'redis_session_',
],
'redis_cluster' => [
'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
'timeout' => 2,
'auth' => '',
'prefix' => 'redis_session_',
]
],
'session_name' => 'PHPSID',
'auto_update_timestamp' => false,
'lifetime' => 7*24*60*60,
'cookie_lifetime' => 365*24*60*60,
'cookie_path' => '/',
'domain' => '',
'http_only' => true,
'secure' => false,
'same_site' => '',
'gc_probability' => [1, 1000],
];

23
config/static.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Static file settings
*/
return [
'enable' => true,
'middleware' => [ // Static file Middleware
//app\middleware\StaticFile::class,
],
];

88
config/thinkorm.php Normal file
View File

@ -0,0 +1,88 @@
<?php
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'work',
// 数据库用户名
'username' => 'work',
// 数据库密码
'password' => 'WRzcKzDwZEyK5F85',
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => '',
// 断线重连
'break_reconnect' => true,
// 自定义分页类
'bootstrap' => ''
],
'zdoo' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '183.221.101.89',
// 数据库名
'database' => 'zdoo',
// 数据库用户名
'username' => 'zdoo',
// 数据库密码
'password' => 'nJHitct3eHLtnyPe',
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => '',
// 断线重连
'break_reconnect' => true,
// 自定义分页类
'bootstrap' => ''
],
'pms' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '183.221.101.89',
// 数据库名
'database' => 'pms20',
// 数据库用户名
'username' => 'pms20',
// 数据库密码
'password' => 'cTpkyr4KyT6XScAY',
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'zt_',
// 断线重连
'break_reconnect' => true,
// 自定义分页类
'bootstrap' => ''
],
],
];

25
config/translation.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Multilingual configuration
*/
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => base_path() . '/resource/translations',
];

23
config/view.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\view\ThinkPHP;
return [
'handler' => ThinkPHP::class,
'options' => [
//'layout_on' => true,
'layout_name' => 'layout',
]
];

39
index.html Executable file
View File

@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>恭喜,站点创建成功!</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h1>恭喜, 站点创建成功!</h1>
<h3>这是默认index.html本页面由系统自动生成</h3>
<ul>
<li>本页面在FTP根目录下的index.html</li>
<li>您可以修改、删除或覆盖本页面</li>
<li>FTP相关信息请到“面板系统后台 > FTP” 查看</li>
</ul>
</div>
</body>
</html>

258
process/Monitor.php Normal file
View File

@ -0,0 +1,258 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace process;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Workerman\Timer;
use Workerman\Worker;
/**
* Class FileMonitor
* @package process
*/
class Monitor
{
/**
* @var array
*/
protected $paths = [];
/**
* @var array
*/
protected $extensions = [];
/**
* @var array
*/
protected $loadedFiles = [];
/**
* @var string
*/
public static $lockFile = __DIR__ . '/../runtime/monitor.lock';
/**
* Pause monitor
* @return void
*/
public static function pause()
{
file_put_contents(static::$lockFile, time());
}
/**
* Resume monitor
* @return void
*/
public static function resume(): void
{
clearstatcache();
if (is_file(static::$lockFile)) {
unlink(static::$lockFile);
}
}
/**
* Whether monitor is paused
* @return bool
*/
public static function isPaused(): bool
{
clearstatcache();
return file_exists(static::$lockFile);
}
/**
* FileMonitor constructor.
* @param $monitorDir
* @param $monitorExtensions
* @param array $options
*/
public function __construct($monitorDir, $monitorExtensions, array $options = [])
{
static::resume();
$this->paths = (array)$monitorDir;
$this->extensions = $monitorExtensions;
foreach (get_included_files() as $index => $file) {
$this->loadedFiles[$file] = $index;
if (strpos($file, 'webman-framework/src/support/App.php')) {
break;
}
}
if (!Worker::getAllWorkers()) {
return;
}
$disableFunctions = explode(',', ini_get('disable_functions'));
if (in_array('exec', $disableFunctions, true)) {
echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
} else {
if ($options['enable_file_monitor'] ?? true) {
Timer::add(1, function () {
$this->checkAllFilesChange();
});
}
}
$memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null);
if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) {
Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]);
}
}
/**
* @param $monitorDir
* @return bool
*/
public function checkFilesChange($monitorDir): bool
{
static $lastMtime, $tooManyFilesCheck;
if (!$lastMtime) {
$lastMtime = time();
}
clearstatcache();
if (!is_dir($monitorDir)) {
if (!is_file($monitorDir)) {
return false;
}
$iterator = [new SplFileInfo($monitorDir)];
} else {
// recursive traversal directory
$dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS);
$iterator = new RecursiveIteratorIterator($dirIterator);
}
$count = 0;
foreach ($iterator as $file) {
$count ++;
/** var SplFileInfo $file */
if (is_dir($file->getRealPath())) {
continue;
}
// check mtime
if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) {
$lastMtime = $file->getMTime();
if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) {
echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n";
continue;
}
$var = 0;
exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
if ($var) {
continue;
}
echo $file . " updated and reload\n";
// send SIGUSR1 signal to master process for reload
if (DIRECTORY_SEPARATOR === '/') {
posix_kill(posix_getppid(), SIGUSR1);
} else {
return true;
}
break;
}
}
if (!$tooManyFilesCheck && $count > 1000) {
echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n";
$tooManyFilesCheck = 1;
}
return false;
}
/**
* @return bool
*/
public function checkAllFilesChange(): bool
{
if (static::isPaused()) {
return false;
}
foreach ($this->paths as $path) {
if ($this->checkFilesChange($path)) {
return true;
}
}
return false;
}
/**
* @param $memoryLimit
* @return void
*/
public function checkMemory($memoryLimit)
{
if (static::isPaused() || $memoryLimit <= 0) {
return;
}
$ppid = posix_getppid();
$childrenFile = "/proc/$ppid/task/$ppid/children";
if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) {
return;
}
foreach (explode(' ', $children) as $pid) {
$pid = (int)$pid;
$statusFile = "/proc/$pid/status";
if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) {
continue;
}
$mem = 0;
if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
$mem = $match[1];
}
$mem = (int)($mem / 1024);
if ($mem >= $memoryLimit) {
posix_kill($pid, SIGINT);
}
}
}
/**
* Get memory limit
* @return float
*/
protected function getMemoryLimit($memoryLimit)
{
if ($memoryLimit === 0) {
return 0;
}
$usePhpIni = false;
if (!$memoryLimit) {
$memoryLimit = ini_get('memory_limit');
$usePhpIni = true;
}
if ($memoryLimit == -1) {
return 0;
}
$unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]);
if ($unit === 'g') {
$memoryLimit = 1024 * (int)$memoryLimit;
} else if ($unit === 'm') {
$memoryLimit = (int)$memoryLimit;
} else if ($unit === 'k') {
$memoryLimit = ((int)$memoryLimit / 1024);
} else {
$memoryLimit = ((int)$memoryLimit / (1024 * 1024));
}
if ($memoryLimit < 30) {
$memoryLimit = 30;
}
if ($usePhpIni) {
$memoryLimit = (int)(0.8 * $memoryLimit);
}
return $memoryLimit;
}
}

1
public/.htaccess Executable file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
7JeNCSvvuIluBzxPtEGpPt5qMMfieZ1X

12
public/404.html Normal file
View File

@ -0,0 +1,12 @@
<html>
<head>
<title>404 Not Found - webman</title>
</head>
<body>
<center>
<h1>404 Not Found</h1>
</center>
<hr>
<center>webman</center>
</body>
</html>

BIN
public/data/data.xls Normal file

Binary file not shown.

BIN
public/data/data2.xls Normal file

Binary file not shown.

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

39
public/index.html Executable file
View File

@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>恭喜,站点创建成功!</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h1>恭喜, 站点创建成功!</h1>
<h3>这是默认index.html本页面由系统自动生成</h3>
<ul>
<li>本页面在FTP根目录下的index.html</li>
<li>您可以修改、删除或覆盖本页面</li>
<li>FTP相关信息请到“面板系统后台 > FTP” 查看</li>
</ul>
</div>
</body>
</html>

45
public/js/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
public/js/jquery-3.7.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

119
public/js/work.js Normal file
View File

@ -0,0 +1,119 @@
layui.use(['layer', 'form', 'laypage'], function(){
var form = layui.form;
// alert弹窗方法
window.layerAlert = function (type, text, href, width, state,id)
{
if(width == undefined || width == '') width = 700;
if(id) href = href + id;
var load = layer.load(2);
$.ajax({
type : type,
url : href,
data : {},
headers: {
//'X-CSRF-TOKEN': _token
},
success: function(data){
if (data.code !== undefined && !isNaN(data.code) && data.code != 0)
{
layer.close(load);
layer.msg(data.msg, {time: 2000})
}
else
{
layer.open({
type : 1,
title : text,
shadeClose: true,
area : ''+width+'px', //宽高
maxmin : true,
move : false,
content : data,
success : function(layero, index) {
layer.close(load);
//renderMultiSelect();
form.render(); //重新渲染form
//tinymceEditor(); //文本编辑器
//判断弹框内容高度
if($(window).height() - 180 < $('.layui-layer-content .layui-card-body:first').height())
{
layer.style(index, {
height: $(window).height() - 80,
top : 40
})
$('.layui-layer-content').height($(window).height()-123)
$('.layui-layer-content').css('max-height','85vh');
$('.eject-layuiBox').height($(window).height()-123)
$('.eject-layuiBox').css('max-height','85vh');
}
else
{
$('.layui-layer-content').css('max-height','79vh;padding-bottom: 10px;');
$('.eject-layuiBox').css('max-height','79vh');
}
},
end:function(){
if(state == 'edit') $('.layui-edit-btn').remove();
}
});
}
}
});
}
//编辑或者添加按钮
$(document).on('click','a.layui-btn',function()
{
var text = $(this).attr('title');
var href = $(this).data('href');
var width = $(this).data('width');
var state = $(this).data('state');
var id = $(this).data('id');
if(href && text) layerAlert('get', text, href, width, state,id);
});
form.on('submit(submitAll)', function(data){
var url = $(data.elem).parents('form').data('action');
formSubmit(url,data.field);
return false;
});
//表单ajax提交
function formSubmit(url, fromdata)
{
$.ajax(url, {
data : fromdata,
dataType: 'json', //服务器返回json格式数据
type : 'post', //HTTP请求类型
timeout : 10000, //超时时间设置为10秒
async : false,
headers : {
//'X-CSRF-TOKEN': _token
},
success:function(data){
if(data.code == 0)
{
layer.msg('提交成功!')
setTimeout(function(){
parent.layer.closeAll();
parent.location.reload()
}, 500)
}
else
{
layer.msg(data.msg)
}
},
error:function(xhr,type,errorThrown){
}
});
}
});

8
public/js/xm-select.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
public/layui/layui.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,788 @@
/** 自定义字体 **/
/** 这里使用在线字体,如果需要离线包,请看 font/README.md **/
@font-face {
font-family: 'soul-icon'; /* project id 677836 */
src: url('//at.alicdn.com/t/font_677836_jwq362m0tt.eot');
src: url('//at.alicdn.com/t/font_677836_jwq362m0tt.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_677836_jwq362m0tt.woff2') format('woff2'),
url('//at.alicdn.com/t/font_677836_jwq362m0tt.woff') format('woff'),
url('//at.alicdn.com/t/font_677836_jwq362m0tt.ttf') format('truetype'),
url('//at.alicdn.com/t/font_677836_jwq362m0tt.svg#iconfont') format('svg');
}
.soul-icon {
font-family:"soul-icon" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.soul-icon-filter:before { content: "\e60b"; }
.soul-icon-filter-asc:before { content: "\e768"; }
.soul-icon-filter-desc:before { content: "\e767"; }
.soul-icon-asc:before { content: "\e6af"; }
.soul-icon-desc:before { content: "\e6ae"; }
.soul-icon-all-check:before { content: "\e670"; }
.soul-icon-invert-check:before { content: "\e614"; }
.soul-icon-fold:before { content: "\e760"; }
.soul-icon-unfold:before { content: "\e611"; }
.soul-icon-delete:before { content: "\e600"; }
.soul-icon-download:before { content: "\e601"; }
.soul-icon-drop-list:before { content: "\e6a3"; }
.soul-icon-query:before { content: "\e66d"; }
/* 全选*/
.soul-icon-quanxuan:before { content: "\e623"; }
.soul-icon-qingkong:before { content: "\e63e"; }
.soul-icon-autoColumnWidth
:before { content: "\e614"; }
/*最大化*/
.soul-icon-min:before { content: "\e656"; }
.soul-icon-max:before { content: "\e61b"; }
/* 配色方案*/
.layui-red {
color: #FF5722
}
.layui-orange {
color: #FFB800
}
.layui-green {
color: #009688
}
.layui-cyan {
color: #2F4056
}
.layui-blue {
color: #1E9FFF
}
.layui-black {
color: #393D49
}
.layui-gray {
color: #eee
}
.layui-firebrick {
color: firebrick;
}
.layui-deeppink {
color: deeppink;
}
.layui-blueviolet {
color: blueviolet;
}
.soul-condition [class*="layui-col-"] {
margin-top: 10px;
}
/* 表格排序样式*/
.soul-edge {
display: inline-block;
width: 0;
height: 0;
border-width: 6px;
border-style: dashed;
border-color: transparent;
overflow: hidden
}
.soul-table-sort {
width: 10px;
height: 20px;
margin-left: 5px;
cursor: pointer !important;
position: relative;
display: inline-block;
}
.soul-table-sort .soul-edge {
position: absolute;
left: 5px;
border-width: 5px
}
.soul-table-sort .soul-table-sort-asc {
top: 10px;
border-top: none;
border-bottom-style: solid;
border-bottom-color: #b2b2b2
}
.soul-table-sort .soul-table-sort-asc:hover {
border-bottom-color: #666
}
.soul-table-sort .soul-table-sort-desc {
bottom: -2px;
border-bottom: none;
border-top-style: solid;
border-top-color: #b2b2b2
}
.soul-table-sort .soul-table-sort-desc:hover {
border-top-color: #666
}
.soul-table-sort[soul-sort=asc] .soul-table-sort-asc {
border-bottom-color: #000
}
.soul-table-sort[soul-sort=desc] .soul-table-sort-desc {
border-top-color: #000
}
.multiOption {
display: inline-block;
padding: 0 5px;
cursor: pointer;
color: #999;
}
/*表格筛选*/
.soul-table-filter {
line-height: 20px;
color: #b2b2b2;
cursor: pointer;
margin-left: 5px;
}
.soul-table-filter .soul-icon-filter-asc,.soul-table-filter .soul-icon-filter-desc {
display: none;
}
.soul-table-filter[lay-sort="asc"] .soul-icon-filter-asc{
display: block;
color: #000000;
}
.soul-table-filter[lay-sort="asc"] .soul-icon-filter,.soul-table-filter[lay-sort="asc"] .soul-icon-filter-desc{
display: none;
}
.soul-table-filter[lay-sort="desc"] .soul-icon-filter-desc{
display: block;
color: #000000;
}
.soul-table-filter[lay-sort="desc"] .soul-icon-filter,.soul-table-filter[lay-sort="desc"] .soul-icon-filter-asc{
display: none;
}
.soul-table-filter[soul-filter="true"] i {
color: #009688!important;
}
[id^=main-list], [id^=soul-columns], [id^=soul-dropList], [id^=soul-condition], [id^=soul-bf-prefix], [id^=soul-bf-column], [id^=soul-bf-type], [id^=soul-bf-cond2] {
display: inline-block;
position: absolute;
z-index: 2147483647;
background-color: white;
max-height: 200px;
min-width: 160px;
max-width: 300px;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 5px;
box-shadow: 2px 2px 4px -2px rgba(0,0,0,.2);
}
[id^=main-list] {
max-height: initial;
}
[id^=soul-condition] {
overflow-y: visible;
max-height: initial;
min-width: 285px;
padding: 5px;
}
[id^=soul-condition] .layui-laydate-header {
padding: 4px 70px 5px
}
[id^=soul-condition] hr{
margin: 5px 0;
}
[id^=soul-condition].soul-bf{
min-width: 150px;
}
[id^=soul-filter-list] ul li {
padding: 3px 10px;
cursor: pointer;
}
[id^=soul-filter-list] ul li:hover {
background-color: deepskyblue;
}
[id^=soul-filter-list] i.layui-icon {
display: inline-block;
width: 16px;
}
[id^=soul-dropList] ul {
border: 0;
max-height: 116px;
overflow-y: auto;
}
[id^=soul-dropList] ul li, [id^=soul-filter-list] [id^=soul-columns]>li{
padding: 2px 10px;
}
[id^=soul-dropList] .check {
padding: 5px 10px;
}
.filter-search {
padding: 5px 10px 0 10px;
}
[id^=soul-condition] .layui-inline {
width: 100px;
}
[id^=soul-condition] table.condition-table tr>td {
padding: 0 3px;
}
[id^=soul-condition] table.condition-table tr>td:first-child {
min-width: 60px;
}
[id^=soul-condition] .layui-form-switch {
background-color: #1E9FFF;
border: 1px solid #1E9FFF;
width: 35px;
margin-top: 0px;
}
[id^=soul-condition] .layui-form-switch.layui-form-onswitch {
background-color: #5FB878;
border: 1px solid #5FB878;
}
[id^=soul-condition] .layui-form-switch em {
color: #fff!important;
}
[id^=soul-condition] .layui-form-switch i {
background-color: #fff;
}
[data-type^=date][class$=Condition] {
width: 273px;
}
/*表格筛选*/
[id^=soul-condition]>div{
width: 270px;
}
.soul-condition-title {
text-align: center;
font-weight: bolder;
}
/*底部筛选*/
.soul-bottom-contion {
height: 31px;
/*line-height: 29px;*/
border-top: solid 1px #e6e6e6;
}
.soul-bottom-contion .condition-items {
display: inline-block;
width: calc(100vw - 100px);
height: 30px;
float: left;
overflow: hidden;
white-space: nowrap;
}
.soul-bottom-contion .condition-item>div {
display: inline-block;
height: 28px;
line-height: 28px;
cursor: pointer;
}
.soul-bottom-contion .condition-items .condition-item>div[class^='item-']:hover{
text-decoration: underline;
}
.soul-bottom-contion .condition-items .condition-item{
padding: 0 10px;
margin: 0 2px;
font-weight: bold;
border: solid 1px darkslateblue;
border-radius: 10px;
display: inline-block;
height: 28px;
position: relative;
}
.soul-bottom-contion .editCondtion {
height: 30px;
float: right;
}
.soul-bottom-contion .item-value {
min-width: 20px;
display: inline-block;
}
.soul-bottom-contion .editCondtion a {
border: hidden;
border-left: solid 1px #e6e6e6;
height: 28px;
line-height: 29px;
}
.soul-bottom-contion .condition-items .condition-item .condition-item-close {
position: absolute;
cursor: pointer;
margin-top: -8px;
}
.soul-bottom-contion .condition-items>.condition-item>.condition-item-close {
margin-top: -2px;
}
.soul-bottom-contion .condition-items .condition-item .condition-item-close:hover{
color: red
}
.soul-bottom-contion .condition-items .condition-item .condition-item-close:before {
background: white;
border-radius: 10px;
}
.soul-edit-out {
padding: 10px;
}
[id^=soul-bf] li {
padding: 0px 10px;
height: 22px;
line-height: 22px;
color: #000;
cursor: pointer;
}
[id^=soul-bf] li.soul-bf-selected {
background-color: deepskyblue;
}
[id^=soul-bf] li:hover {
background-color: deepskyblue;
}
.soul-edit-out .tempValue {
height: 25px;
}
.soul-bf-condition-value {
display: inline;
width: 100px;
}
/*子表格*/
.layui-table tbody tr.noHover:hover {
background-color: white;
}
/*编辑筛选*/
.soul-edit-out .layui-form-radio {
margin: 0;
}
.soul-edit-out ul li > div {
display: inline-block;
margin-right: 10px;
height: 25px;
vertical-align: top;
cursor: pointer;
}
.soul-edit-out ul.group {
padding-left: 50px;
}
.soul-edit-out ul.group.line {
border-left: 1px dashed grey;
}
.soul-edit-out ul li {
line-height: 25px;
}
.soul-edit-out table {
display: inline-block;
}
.soul-edit-out table td[data-type='top'] {
width: 12px;
height: 12px;
border-left: dashed 1px grey;
border-bottom: dashed 1px grey;
}
.soul-edit-out table td[data-type='bottom'] {
width: 12px;
height: 12px;
border-left: dashed 1px grey;
}
.soul-edit-out li.last>div>table td[data-type='bottom'] {
border-left: none;
}
.soul-edit-out .layui-form-switch {
background-color: #1E9FFF;
border: 1px solid #1E9FFF;
width: 35px;
margin-top: 0px;
}
.soul-edit-out .layui-form-switch em {
color: #fff!important;
}
.soul-edit-out .layui-form-switch i {
background-color: #fff;
}
.soul-edit-out .layui-form-switch.layui-form-onswitch {
background-color: #5FB878;
border: 1px solid #5FB878;
}
.soul-edit-out .delete-item {
display: none;
}
.soul-edit-out li:hover>.delete-item {
display: inline-block;
}
/* 拖拽相关 */
#column-remove {
position: absolute;
z-index: 2147483647;
}
.layui-table-box.no-left-border td.isDrag, .layui-table-box.no-left-border th.isDrag {
border-left: inherit!important;
}
.soul-drag-bar {
position: absolute;
top: 100px;
z-index: 200;
left: 50%;
font-weight: 900;
color: white;
box-shadow: 0 1px 20px rgba(0,0,0,.15);
text-align: center;
transform: translateX(100vw);
/*transition: transform .3s;*/
}
.soul-drag-bar.active {
transform: translateX(-98px);
}
.soul-drag-bar > div {
display: inline-block;
padding: 10px;
cursor: crosshair;
width: 62px;
background-color: rgba(0, 150, 136, 0.5);
}
.soul-drag-bar > div.active, .soul-drag-bar[data-type='left']>div[data-type='left'], .soul-drag-bar[data-type='right']>div[data-type='right'], .soul-drag-bar[data-type='none']>div[data-type='none'] {
background-color: rgb(0, 150, 136);
}
/* 动画 */
.animated {
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
-o-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
-o-animation-fill-mode: both;
animation-fill-mode: both;
}
@-moz-keyframes fadeInLeft {
0% {
opacity: 0;
-moz-transform: translateX(-20px);
}
100% {
opacity: 1;
-moz-transform: translateX(0);
}
}
@-o-keyframes fadeInLeft {
0% {
opacity: 0;
-o-transform: translateX(-20px);
}
100% {
opacity: 1;
-o-transform: translateX(0);
}
}
@keyframes fadeInLeft {
0% {
opacity: 0;
transform: translateX(-20px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
.animated.fadeInLeft {
-webkit-animation-name: fadeInLeft;
-moz-animation-name: fadeInLeft;
-o-animation-name: fadeInLeft;
animation-name: fadeInLeft;
}
@-webkit-keyframes fadeOutLeft {
0% {
opacity: 1;
-webkit-transform: translateX(0);
}
100% {
opacity: 0;
-webkit-transform: translateX(-20px);
}
}
@-moz-keyframes fadeOutLeft {
0% {
opacity: 1;
-moz-transform: translateX(0);
}
100% {
opacity: 0;
-moz-transform: translateX(-20px);
}
}
@-o-keyframes fadeOutLeft {
0% {
opacity: 1;
-o-transform: translateX(0);
}
100% {
opacity: 0;
-o-transform: translateX(-20px);
}
}
@keyframes fadeOutLeft {
0% {
opacity: 1;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(-20px);
}
}
.animated.fadeOutLeft {
-webkit-animation-name: fadeOutLeft;
-moz-animation-name: fadeOutLeft;
-o-animation-name: fadeOutLeft;
animation-name: fadeOutLeft;
}
@-webkit-keyframes fadeInRight {
0% {
opacity: 0;
-webkit-transform: translateX(20px);
}
100% {
opacity: 1;
-webkit-transform: translateX(0);
}
}
@-moz-keyframes fadeInRight {
0% {
opacity: 0;
-moz-transform: translateX(20px);
}
100% {
opacity: 1;
-moz-transform: translateX(0);
}
}
@-o-keyframes fadeInRight {
0% {
opacity: 0;
-o-transform: translateX(20px);
}
100% {
opacity: 1;
-o-transform: translateX(0);
}
}
@keyframes fadeInRight {
0% {
opacity: 0;
transform: translateX(20px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
.animated.fadeInRight {
-webkit-animation-name: fadeInRight;
-moz-animation-name: fadeInRight;
-o-animation-name: fadeInRight;
animation-name: fadeInRight;
}
@-webkit-keyframes fadeInUp {
0% {
opacity: 0;
-webkit-transform: translateY(20px);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
}
}
@-moz-keyframes fadeInUp {
0% {
opacity: 0;
-moz-transform: translateY(20px);
}
100% {
opacity: 1;
-moz-transform: translateY(0);
}
}
@-o-keyframes fadeInUp {
0% {
opacity: 0;
-o-transform: translateY(20px);
}
100% {
opacity: 1;
-o-transform: translateY(0);
}
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animated.fadeInUp {
-webkit-animation-name: fadeInUp;
-moz-animation-name: fadeInUp;
-o-animation-name: fadeInUp;
animation-name: fadeInUp;
}
@-webkit-keyframes fadeOutDown {
0% {
opacity: 1;
-webkit-transform: translateY(0);
}
100% {
opacity: 0;
-webkit-transform: translateY(20px);
}
}
@-moz-keyframes fadeOutDown {
0% {
opacity: 1;
-moz-transform: translateY(0);
}
100% {
opacity: 0;
-moz-transform: translateY(20px);
}
}
@-o-keyframes fadeOutDown {
0% {
opacity: 1;
-o-transform: translateY(0);
}
100% {
opacity: 0;
-o-transform: translateY(20px);
}
}
@keyframes fadeOutDown {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(20px);
}
}
.animated.fadeOutDown {
-webkit-animation-name: fadeOutDown;
-moz-animation-name: fadeOutDown;
-o-animation-name: fadeOutDown;
animation-name: fadeOutDown;
}
#soul-table-contextmenu-wrapper {
width: 0;
}
.soul-table-contextmenu {
position: absolute;
z-index: 2147483647;
list-style: none;
margin: 0;
padding: 0;
border: 1px solid #ebeef5;
box-shadow: 2px 2px 4px -2px rgba(0,0,0,.2);
background: white;
}
.soul-table-contextmenu li {
line-height: 26px;
padding: 0 30px;
cursor: pointer;
word-break: keep-all;
}
.soul-table-contextmenu li:hover {
background: #c5c5c5;
}
.soul-table-contextmenu li i.prefixIcon{
position: absolute;
left: 8px;
}
.soul-table-contextmenu li i.endIcon{
position: absolute;
right: 8px;
}
/*拖拽相关*/
.layui-table-sort-invalid {
width: 10px;
height: 20px;
margin-left: 5px;
cursor: pointer!important;
}
.layui-table-sort-invalid .layui-table-sort-asc {
top: 3px;
border-top: none;
border-bottom-style: solid;
border-bottom-color: #b2b2b2;
}
.layui-table-sort-invalid .layui-edge {
position: absolute;
left: 5px;
border-width: 5px;
}
.layui-table-sort-invalid .layui-table-sort-desc {
bottom: 5px;
border-bottom: none;
border-top-style: solid;
border-top-color: #b2b2b2;
}
.layui-table-sort-invalid .layui-edge {
position: absolute;
left: 5px;
border-width: 5px;
}
.noselect {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently */
}
/* 固定列滚动 */
.soul-fixed-scroll::-webkit-scrollbar{
display: none
}
.soul-fixed-scroll{
overflow-y: auto!important;
-ms-overflow-style:none;
overflow:-moz-scrollbars-none;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

4
start.php Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
support\App::run();

Some files were not shown because too many files have changed in this diff Show More