exam-table

This commit is contained in:
xiaomlove
2021-04-23 20:05:39 +08:00
parent ba7e5030e9
commit a0c7a7e5dc
8 changed files with 271 additions and 136 deletions
+3
View File
@@ -36,6 +36,9 @@ const api = {
listExam: (params = {}) => { listExam: (params = {}) => {
return axios.get('exam', {params: params}); return axios.get('exam', {params: params});
}, },
listExamIndex: (params = {}) => {
return axios.get('exam-index', {params: params});
},
storeExam: (params = {}) => { storeExam: (params = {}) => {
return axios.post('exam', params); return axios.post('exam', params);
}, },
+108 -63
View File
@@ -2,33 +2,74 @@
<div> <div>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form :model="formData" :rules="rules" ref="goodRef" label-width="100px" class="formData"> <el-form :model="formData" :rules="rules" ref="formRef" label-width="200px" class="formData">
<el-form-item label="" prop="name"> <el-form-item label="Name" prop="name">
<el-input v-model="formData.name" placeholder="请输入商品名称"></el-input> <el-input v-model="formData.name" placeholder=""></el-input>
</el-form-item> </el-form-item>
<el-form-item label="商品简介" prop="description"> <el-form-item label="Index" prop="indexes">
<el-input type="textarea" v-model="formData.description" placeholder="请输入商品简介(100字)"></el-input> <template v-for="(item, index) in formData.indexes" :key="index">
<el-row>
<el-col :span="6">
<el-checkbox v-model="item.checked" :label="item.checked">{{item.name}}</el-checkbox>
</el-col>
<el-col :span="12">
<el-input type="number" v-model="item.require_value"></el-input>
</el-col>
<el-col :span="6" style="padding: 0 20px; color: #aaa">
<template v-if="item.unit">
Unit: {{item.unit}}
</template>
</el-col>
</el-row>
</template>
</el-form-item> </el-form-item>
<el-form-item label="商品价格" prop="begin">
<el-input type="number" min="0" v-model="formData.begin" placeholder="请输入商品价格"></el-input> <el-form-item label="Status" prop="status">
</el-form-item>
<el-form-item label="商品售卖价" prop="end">
<el-input type="number" min="0" v-model="formData.end" placeholder="请输入商品售价"></el-input>
</el-form-item>
<el-form-item label="商品库存" prop="requires">
<el-input type="number" min="0" v-model="formData.requires" placeholder="请输入商品库存"></el-input>
</el-form-item>
<el-form-item label="商品标签" prop="filters">
<el-input v-model="formData.filters" placeholder="请输入商品小标签"></el-input>
</el-form-item>
<el-form-item label="上架状态" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio label="0">启用</el-radio> <el-radio label="0">Enabled</el-radio>
<el-radio label="1">禁用</el-radio> <el-radio label="1">Disabled</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="Begin" prop="begin">
<el-date-picker
v-model="formData.begin"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
placeholder="Select Begin Time">
</el-date-picker>
</el-form-item>
<el-form-item label="End" prop="end">
<el-date-picker
v-model="formData.end"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
placeholder="Select End Time">
</el-date-picker>
</el-form-item>
<el-form-item label="Target User Class" prop="filters.classes">
<el-checkbox-group v-model="formData.filters.classes">
<el-checkbox v-for="(item, index) in allClasses" :label="index" :key="index">{{item}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="Target User Register Time">
<el-date-picker
v-model="formData.filters.register_time_range"
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
range-separator="to"
start-placeholder="Begin"
end-placeholder="End">
</el-date-picker>
</el-form-item>
<el-form-item label="Description" prop="description">
<el-input type="textarea" v-model="formData.description" placeholder=""></el-input>
</el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="submitAdd()">{{ id ? '立即修改' : '立即创建' }}</el-button> <el-button type="primary" @click="submitAdd()">Submit</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-col> </el-col>
@@ -41,81 +82,72 @@ import { reactive, ref, toRefs, onMounted, onBeforeUnmount, getCurrentInstance }
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { localGet } from '../../utils' import { localGet } from '../../utils'
import api from "../../utils/api";
export default { export default {
name: 'AddGood', name: 'ExamForm',
setup() { setup() {
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
console.log('proxy', proxy) console.log('proxy', proxy)
const goodRef = ref(null) const formRef = ref(null)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { id } = route.query const { id } = route.query
const state = reactive({ const state = reactive({
token: localGet('token') || '', token: localGet('token') || '',
id: id, id: id,
defaultCate: '', allClasses: [],
formData: { formData: {
name: '', name: '',
description: '', description: '',
begin: '', begin: '',
end: '', end: '',
requires: {}, indexes: [],
filters: {}, filters: {
classes: [],
register_time_range: []
},
status: '', status: '',
}, },
rules: { rules: {
goodsName: [ name: [
{ required: 'true', } { required: 'true', }
], ],
originalPrice: [ indexes: [
{ required: 'true', } { required: 'true', }
], ],
sellingPrice: [ status: [
{ required: 'true',} { required: 'true',}
], ],
stockNum: [
{ required: 'true', }
],
}, },
}) })
let instance
onMounted(() => { onMounted(() => {
listAllClass()
listAllIndex()
if (id) {
let res = api.getExam(id)
// state.formData = res.data
} else {
let res = api.listExamIndex()
state.formData.indexes = res.data
}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
}) })
const submitAdd = () => { const submitAdd = () => {
goodRef.value.validate((vaild) => { formRef.value.validate(async (vaild) => {
if (vaild) { if (vaild) {
// 默认新增用 post 方法 let params = state.formData;
let httpOption = axios.post if (params.begin) {
let params = { params.begin = dayjs(params.begin).format('YYYY-MM-DD HH:mm:ss')
goodsCategoryId: state.categoryId,
goodsCoverImg: state.formData.goodsCoverImg,
goodsDetailContent: instance.txt.html(),
goodsIntro: state.formData.goodsIntro,
goodsName: state.formData.goodsName,
goodsSellStatus: state.formData.goodsSellStatus,
originalPrice: state.formData.originalPrice,
sellingPrice: state.formData.sellingPrice,
stockNum: state.formData.stockNum,
tag: state.formData.tag
} }
if (hasEmoji(params.goodsIntro) || hasEmoji(params.goodsName) || hasEmoji(params.tag) || hasEmoji(params.goodsDetailContent)) { if (params.end) {
ElMessage.error('不要输入表情包,再输入就打死你个龟孙儿~') params.end = dayjs(params.end).format('YYYY-MM-DD HH:mm:ss')
return
} }
console.log('params', params) console.log(params)
if (id) { let res = await api.storeExam(params)
params.goodsId = id console.log(res)
// 修改商品使用 put 方法
httpOption = axios.put
}
httpOption('/goods', params).then(() => {
ElMessage.success(id ? '修改成功' : '添加成功')
router.push({ path: '/good' })
})
} }
}) })
} }
@@ -132,13 +164,26 @@ export default {
const handleChangeCate = (val) => { const handleChangeCate = (val) => {
state.categoryId = val[2] || 0 state.categoryId = val[2] || 0
} }
const listAllClass = async () => {
let res = await api.listClass()
state.allClasses = res.data
}
const listAllIndex = async () => {
let res = await api.listExamIndex()
state.formData.indexes = res.data
}
const getExam = async (id) => {
let res = await api.getExam(id)
console.log(res)
}
return { return {
...toRefs(state), ...toRefs(state),
goodRef, formRef,
submitAdd, submitAdd,
handleBeforeUpload, handleBeforeUpload,
handleUrlSuccess, handleUrlSuccess,
handleChangeCate handleChangeCate,
} }
} }
} }
+53 -46
View File
@@ -2,7 +2,7 @@
<el-card class="swiper-container"> <el-card class="swiper-container">
<template #header> <template #header>
<div class="header"> <div class="header">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增商品</el-button> <el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">Add</el-button>
</div> </div>
</template> </template>
<el-table <el-table
@@ -17,53 +17,55 @@
width="55"> width="55">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="goodsId" prop="id"
label="商品编号" label="Id"
width="50"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="goodsName" prop="name"
label="商品名" label="Name"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="goodsIntro" label="Index"
label="商品简介" width="250px"
> >
</el-table-column> <template #default="scope" >
<el-table-column <p style="white-space: pre-line" v-html="scope.row.indexes_formatted"></p>
label="商品图片"
width="150px"
>
<template #default="scope">
<img style="width: 100px; height: 100px;" :key="scope.row.goodsId" :src="$filters.prefix(scope.row.goodsCoverImg)" alt="商品主图">
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="stockNum" prop="begin"
label="商品库存" label="Begin"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="sellingPrice" prop="end"
label="商品售价" label="End"
> >
</el-table-column> </el-table-column>
<el-table-column
label="上架状态"
>
<template #default="scope">
<span style="color: green;" v-if="scope.row.goodsSellStatus == 0">销售中</span>
<span style="color: red;" v-else>已下架</span>
</template>
</el-table-column>
<el-table-column <el-table-column
label="操作" label="Target User"
width="400px"
>
<template #default="scope" >
<p style="white-space: pre-line" v-html="scope.row.filters_formatted"></p>
</template>
</el-table-column>
<el-table-column
prop="status_text"
label="Status"
>
</el-table-column>
<el-table-column
label="Action"
width="100" width="100"
> >
<template #default="scope"> <template #default="scope">
<a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.goodsId)">修改</a> <a style="cursor: pointer; margin-right: 10px" @click="handleEdit(scope.row.id)">Edit</a>
<a style="cursor: pointer; margin-right: 10px" v-if="scope.row.goodsSellStatus == 0" @click="handleStatus(scope.row.goodsId, 1)">下架</a> <a style="cursor: pointer; margin-right: 10px" v-if="scope.row.goodsSellStatus == 0" @click="handleStatus(scope.row.goodsId, 1)">下架</a>
<a style="cursor: pointer; margin-right: 10px" v-else @click="handleStatus(scope.row.goodsId, 0)">上架</a> <a style="cursor: pointer; margin-right: 10px" v-else @click="handleStatus(scope.row.goodsId, 0)">上架</a>
</template> </template>
@@ -85,13 +87,19 @@
import { onMounted, reactive, ref, toRefs } from 'vue' import { onMounted, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import api from '../../utils/api'
export default { export default {
name: 'Good', name: 'ExamTable',
setup() { setup() {
const multipleTable = ref(null) const multipleTable = ref(null)
const router = useRouter() const router = useRouter()
const state = reactive({ const state = reactive({
loading: false, loading: false,
query: {
page: 1,
sort_field: 'id',
sort_type: 'desc'
},
tableData: [], // 数据列表 tableData: [], // 数据列表
multipleSelection: [], // 选中项 multipleSelection: [], // 选中项
total: 0, // 总条数 total: 0, // 总条数
@@ -99,28 +107,27 @@ export default {
pageSize: 10 // 分页大小 pageSize: 10 // 分页大小
}) })
onMounted(() => { onMounted(() => {
getGoodList() fetchTableData()
}) })
// 获取轮播图列表 // 获取轮播图列表
const getGoodList = () => { const fetchTableData = async () => {
state.loading = true state.loading = true
axios.get('/goods/list', { let res = await api.listExam(state.query)
params: { renderTableData(res)
pageNumber: state.currentPage, }
pageSize: state.pageSize const renderTableData = (res) => {
} state.tableData = res.data.data
}).then(res => { state.page = res.data.meta.current_page
state.tableData = res.list state.total = res.data.meta.total
state.total = res.totalCount state.currentPage = res.data.meta.current_page
state.currentPage = res.currPage state.pageSize = res.data.meta.per_page
state.loading = false state.loading = false
})
} }
const handleAdd = () => { const handleAdd = () => {
router.push({ name: 'exam-form' }) router.push({ name: 'exam-form' })
} }
const handleEdit = (id) => { const handleEdit = (id) => {
router.push({ path: '/add', query: { id } }) router.push({ path: '/exam-form', query: { id } })
} }
// 选择项 // 选择项
const handleSelectionChange = (val) => { const handleSelectionChange = (val) => {
@@ -128,14 +135,14 @@ export default {
} }
const changePage = (val) => { const changePage = (val) => {
state.currentPage = val state.currentPage = val
getGoodList() fetchTableData()
} }
const handleStatus = (id, status) => { const handleStatus = (id, status) => {
axios.put(`/goods/status/${status}`, { axios.put(`/goods/status/${status}`, {
ids: id ? [id] : [] ids: id ? [id] : []
}).then(() => { }).then(() => {
ElMessage.success('修改成功') ElMessage.success('修改成功')
getGoodList() fetchTableData()
}) })
} }
return { return {
@@ -144,7 +151,7 @@ export default {
handleSelectionChange, handleSelectionChange,
handleAdd, handleAdd,
handleEdit, handleEdit,
getGoodList, fetchTableData,
changePage, changePage,
handleStatus handleStatus
} }
+10 -10
View File
@@ -40,10 +40,10 @@ class ExamController extends Controller
{ {
$rules = [ $rules = [
'name' => 'required|string', 'name' => 'required|string',
'begin' => 'required|date_format:Y-m-d H:i:s', 'indexes' => 'required|array|min:1',
'end' => 'required|date_format:Y-m-d H:i:s', 'status' => 'required|in:0,1',
'requires' => 'required|array|min:1', // 'begin' => 'nullable|date',
'filters' => 'required|array|min:1', // 'end' => 'nullable|date',
]; ];
$request->validate($rules); $request->validate($rules);
$result = $this->repository->store($request->all()); $result = $this->repository->store($request->all());
@@ -55,11 +55,13 @@ class ExamController extends Controller
* Display the specified resource. * Display the specified resource.
* *
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return array
*/ */
public function show($id) public function show($id)
{ {
// $result = $this->repository->getDetail($id);
$resource = new ExamResource($result);
return $this->success($resource);
} }
/** /**
@@ -73,10 +75,8 @@ class ExamController extends Controller
{ {
$rules = [ $rules = [
'name' => 'required|string', 'name' => 'required|string',
'begin' => 'required|date_format:Y-m-d H:i:s', 'indexes' => 'required|array|min:1',
'end' => 'required|date_format:Y-m-d H:i:s', 'status' => 'required|in:0,1',
'requires' => 'required|array|min:1',
'filters' => 'required|array|min:1',
]; ];
$request->validate($rules); $request->validate($rules);
$result = $this->repository->update($request->all(), $id); $result = $this->repository->update($request->all(), $id);
+48 -1
View File
@@ -2,6 +2,9 @@
namespace App\Http\Resources; namespace App\Http\Resources;
use App\Models\Exam;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
class ExamResource extends JsonResource class ExamResource extends JsonResource
@@ -21,8 +24,52 @@ class ExamResource extends JsonResource
'begin' => $this->begin, 'begin' => $this->begin,
'end' => $this->end, 'end' => $this->end,
'filters' => $this->filters, 'filters' => $this->filters,
'requires' => $this->requires, 'filters_formatted' => $this->formatFilters($this->resource),
'indexes' => $this->indexes,
'indexes_formatted' => $this->formatIndexes($this->resource),
'status' => $this->status, 'status' => $this->status,
'status_text' => $this->statusText,
]; ];
} }
private function formatFilters(Exam $exam)
{
$currentFilters = $exam->filters;
$arr = [];
$filter = Exam::FILTER_USER_CLASS;
if (!empty($currentFilters->{$filter})) {
$classes = collect(User::$classes)->only($currentFilters->{$filter});
$arr[] = sprintf('%s: %s', Exam::$filters[$filter]['name'], $classes->pluck('text')->implode(', '));
}
$filter = Exam::FILTER_USER_REGISTER_TIME_RANGE;
if (!empty($currentFilters->{$filter})) {
$range = $currentFilters->{$filter};
$arr[] = sprintf(
"%s: \n%s ~ %s",
Exam::$filters[$filter]['name'],
$range[0] ? Carbon::parse($range[0])->toDateTimeString() : '-',
$range[1] ? Carbon::parse($range[1])->toDateTimeString() : '-'
);
}
return implode("\n", $arr);
}
private function formatIndexes(Exam $exam)
{
$indexes = $exam->indexes;
$arr = [];
foreach ($indexes as $index) {
if (isset($index['checked']) && $index['checked']) {
$arr[] = sprintf(
'%s: %s %s',
Exam::$indexes[$index['index']]['name'] ?? '',
$index['require_value'],
Exam::$indexes[$index['index']]['unit'] ?? ''
);
}
}
return implode("\n", $arr);
}
} }
+22 -11
View File
@@ -4,28 +4,39 @@ namespace App\Models;
class Exam extends NexusModel class Exam extends NexusModel
{ {
protected $fillable = ['name', 'description', 'begin', 'end', 'status']; protected $fillable = ['name', 'description', 'begin', 'end', 'status', 'filters', 'indexes'];
protected $casts = [
'filters' => 'object',
'indexes' => 'array',
];
const STATUS_ENABLED = 0; const STATUS_ENABLED = 0;
const STATUS_DISABLED = 1; const STATUS_DISABLED = 1;
public static $status = [ public static $status = [
self::STATUS_ENABLED => ['text' => '启用中'], self::STATUS_ENABLED => ['text' => 'Enabled'],
self::STATUS_DISABLED => ['text' => '已禁用'], self::STATUS_DISABLED => ['text' => 'Disabled'],
]; ];
const INDEX_UPLOADED = 1; const INDEX_UPLOADED = 1;
const INDEX_SEED_TIME = 2; const INDEX_SEED_TIME_AVERGAGE = 2;
const INDEX_DOWNLOADED = 3; const INDEX_DOWNLOADED = 3;
const INDEX_LEECH_TIME = 4; const INDEX_BONUS = 4;
const INDEX_BONUS = 5;
public static $indexes = [ public static $indexes = [
self::INDEX_UPLOADED => ['text' => 'Uploaded'], self::INDEX_UPLOADED => ['name' => 'Uploaded', 'unit' => 'GB'],
self::INDEX_SEED_TIME => ['text' => 'Seed time'], self::INDEX_SEED_TIME_AVERGAGE => ['name' => 'Seed Time Average', 'unit' => 'Hour'],
self::INDEX_DOWNLOADED => ['text' => 'Download'], self::INDEX_DOWNLOADED => ['name' => 'Downloaded', 'unit' => 'GB'],
self::INDEX_LEECH_TIME => ['text' => 'Leech time'], self::INDEX_BONUS => ['name' => 'Bonus', 'unit' => ''],
self::INDEX_BONUS => ['text' => 'Bonus'], ];
const FILTER_USER_CLASS = 'classes';
const FILTER_USER_REGISTER_TIME_RANGE = 'register_time_range';
public static $filters = [
self::FILTER_USER_CLASS => ['name' => 'User Class'],
self::FILTER_USER_REGISTER_TIME_RANGE => ['name' => 'User Register Time Range'],
]; ];
public function getStatusTextAttribute() public function getStatusTextAttribute()
+23 -1
View File
@@ -5,6 +5,7 @@ use App\Models\Exam;
use App\Models\Setting; use App\Models\Setting;
use App\Models\User; use App\Models\User;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class ExamRepository extends BaseRepository class ExamRepository extends BaseRepository
@@ -19,6 +20,10 @@ class ExamRepository extends BaseRepository
public function store(array $params) public function store(array $params)
{ {
$data = Arr::only($params, ['name', 'description', 'status', 'filters']);
if (!empty($params['begin'])) {
}
$exam = Exam::query()->create($params); $exam = Exam::query()->create($params);
return $exam; return $exam;
} }
@@ -30,11 +35,28 @@ class ExamRepository extends BaseRepository
return $exam; return $exam;
} }
public function getDetail($id)
{
$exam = Exam::query()->findOrFail($id);
return $exam;
}
public function listIndexes() public function listIndexes()
{ {
$out = []; $out = [];
foreach(Exam::$indexes as $key => $value) { foreach(Exam::$indexes as $key => $value) {
$out[$key] = $value['text']; $value['index'] = $key;
$out[] = $value;
}
return $out;
}
public function listFilters()
{
$out = [];
foreach(Exam::$filters as $key => $value) {
$value['filter'] = $key;
$out[] = $value;
} }
return $out; return $out;
} }
@@ -17,10 +17,10 @@ class CreateExamsTable extends Migration
$table->id(); $table->id();
$table->string('name'); $table->string('name');
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->dateTime('begin'); $table->dateTime('begin')->nullable();
$table->dateTime('end'); $table->dateTime('end')->nullable();
$table->text('filters'); $table->text('filters')->nullable();
$table->text('requires'); $table->text('indexes');
$table->tinyInteger('status')->default(0); $table->tinyInteger('status')->default(0);
$table->timestamps(); $table->timestamps();
}); });