feat 完善商品的管理

This commit is contained in:
LittleBoy 2022-12-08 15:45:15 +08:00
parent 440824c98f
commit 4935b9c24c
12 changed files with 266 additions and 63 deletions

View File

@ -119,14 +119,46 @@ body {
.pointer-cursor {
cursor: pointer;
}
table{
width: 100%;
border-left: solid 1px #eee;
border-top: solid 1px #eee;
border-collapse: collapse;
.table-wrapper {
.table-toolbar{
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
table {
width: 100%;
border-left: solid 1px #eee;
border-top: solid 1px #eee;
border-collapse: collapse;
}
td, th {
border-right: solid 1px #eee;
border-bottom: solid 1px #eee;
padding: 6px;
}
}
td,th {
border-right: solid 1px #eee;
border-bottom: solid 1px #eee;
padding:6px;
.page-wrapper {
margin: 20px 0;
.page-item {
cursor: pointer;
border: solid 1px var(--primary-2);
border-radius: var(--border-radius-middle);
display: inline-block;
padding: 4px 10px;
margin: 0 5px;
min-width: 40px;
text-align: center;
&:hover, &.current-page {
border-color: var(--primary-color);
color: var(--primary-color)
}
}
}
.display-flex{
display: flex;
}

View File

@ -24,12 +24,12 @@ import Eye from "../icon/Eye.vue";
import EyeClose from "../icon/EyeClose.vue";
interface PInputEvents {
'update:modelValue': (value: string) => void
'update:modelValue': (value: any) => void
}
type PInputProps = {
placeholder?: string
modelValue?: string
modelValue?: any
type?: 'text' | 'password' | 'textarea'
}

View File

@ -77,25 +77,3 @@ export default defineComponent({
}
})
</script>
<style scoped lang="less">
.page-wrapper {
margin: 20px 0;
.page-item {
cursor: pointer;
border: solid 1px var(--primary-2);
border-radius: var(--border-radius-middle);
display: inline-block;
padding: 4px 10px;
margin: 0 5px;
min-width: 40px;
text-align: center;
&:hover, &.current-page {
border-color: var(--primary-color);
color: var(--primary-color)
}
}
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="upload-wrapper">
<input type="file" :accept="accept" @change="onFileSelectChange"/>
<span>
<!-- 上传触发元素的插槽 -->
<slot/>
</span>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "Uploader"
})
</script>
<script lang="ts" setup>
import http from "../../util/http";
type PropsType = {
/**
* 上传路径
*/
action: string
/**
* 文件上传默认的参数名称默认为file
*/
fieldName?: string
/**
* 允许上传的文件类型默认为*表示所有文件
*/
accept?: string
modelValue?: string
}
type EmitsType = {
onSuccess: () => void
onError: () => void
'update:modelValue': (value: string) => void
}
type FileUploadResultModel = {
url: string
name?: string
}
const props = defineProps<PropsType>()
const emits = defineEmits<EmitsType>()
async function onFileSelectChange(e: InputEvent) {
//@ts-ignore
const files = e.target.files as FileList
if (!files || files.length == 0) return;
const data = {},fieldName = props.fieldName || 'file'
data[fieldName] = files[0];
const ret = await http.request<FileUploadResultModel>(props.action, data, 'post', 'form')
emits('update:modelValue', ret.url)
}
</script>
<style scoped>
</style>

View File

@ -4,7 +4,7 @@ import {createPinia} from 'pinia'
import App from './App.vue'
import router from './router'
import {httpConfig} from "./util/http";
import './assets/app.css'
import './assets/app.less'
import PInput from "./components/input";
httpConfig.baseURL = "http://localhost:8080"

View File

@ -7,7 +7,7 @@ export type HttpMethod = 'get' | 'post' | 'delete' | 'put'
export enum RequestDataContentType {
JSON = 'application/json;charset=UTF-8',
FORM = 'application/x-www-form-urlencoded;charset=UTF-8',
FORM_DATA = 'multipart/form-data;charset=UTF-8',
FORM_DATA = 'multipart/form-data',
}
export const httpConfig = {
@ -68,7 +68,9 @@ class Http {
*/
request<T>(url: string, data: any = null, method: HttpMethod = 'post', type: 'normal' | 'form' = 'normal') {
return new Promise<T>((resolve, reject) => {
let contentType = RequestDataContentType.FORM;
const headers: any = {
'Content-Type': RequestDataContentType.FORM
}
if (data) {
if (type === 'form') {
const form = new FormData();
@ -76,12 +78,13 @@ class Http {
form.append(key, data[key]);
}
data = form; // 将data有{} => formData
contentType = RequestDataContentType.FORM_DATA; // 由于使用formData
// contentType = RequestDataContentType.FORM_DATA; // 由于使用formData
delete headers['Content-Type'];
method = 'post';
} else if (method == 'post') {
} else if (method == 'post' || method == 'put') {
// 将数据对象 转成json
data = JSON.stringify(data);
contentType = RequestDataContentType.JSON;
headers['Content-Type'] = RequestDataContentType.JSON;
} else {
// 装对象转成 key=value&key=value
const params = [];
@ -92,10 +95,6 @@ class Http {
url += '?' + params.join('&')
}
}
const headers: any = {
'Content-Type': contentType
}
const token = useUserStore().token();
if (token) headers.token = token
fetch(httpConfig.baseURL + url, {

View File

@ -1,16 +1,21 @@
<template>
<div>
<div class="search-form">
<span>标题<PInput v-model="param.title" placeholder="要查询的商品标题"/></span>
<span>
<div class="table-wrapper">
<div class="table-toolbar">
<div class="search-form">
<span>标题<PInput v-model="param.title" placeholder="要查询的商品标题"/></span>
<span>
分类
<select v-model="param.category">
<option v-for="(text,value) in CategoryEnum" :key="value" :value="value">{{ text }}</option>
</select>
</span>
<button @click="onReset">重置</button>
<button @click="onSearch">搜索</button>
<button @click="onReset">重置</button>
<button @click="onSearch">搜索</button>
</div>
<div class="toolbar-extra">
<button @click="onEditData({id:0})">新增</button>
</div>
</div>
<table>
<tr>
@ -53,24 +58,84 @@
<Modal v-model="modalVisible">
<div>
<div>
<span>昵称</span>
<span>标题</span>
<p>
<PInput placeholder="请输入昵称" v-model="editData.nickname"/>
<PInput placeholder="请输入标题" v-model="editData.title"/>
</p>
</div>
<div class="display-flex">
<div>
<span>分类</span>
<p>
<select v-model="editData.category">
<option v-for="(text,value) in CategoryEnum" :key="value" :value="value">{{ text }}</option>
</select>
</p>
</div>
<div>
<span>类型</span>
<p>
<select v-model="editData.type">
<option v-for="(text,value) in TypeEnum" :key="value" :value="value">{{ text }}</option>
</select>
</p>
</div>
</div>
<div>
<span>商品图片</span>
<!-- 上传组件 -->
<Uploader action="/file/upload" v-model="editData.cover">
<div v-if="editData.cover">
<img :src="editData.cover" alt="" style="width: 120px;height: 120px;">
</div>
<div v-else>选择商品图</div>
</Uploader>
</div>
<div>
<span>描述</span>
<p>
<PInput placeholder="请输入描述" v-model="editData.description"/>
</p>
</div>
<div>
<span>省份</span>
<span>原价</span>
<p>
<PInput placeholder="请输入省份" v-model="editData.province"/>
<PInput type="number" placeholder="请输入原价" v-model="editData.originPrice"/>
</p>
</div>
<div>
<span>城市</span>
<span>销售价格</span>
<p>
<PInput placeholder="请输入城市" v-model="editData.city"/>
<PInput type="number" placeholder="请输入销售价格" v-model="editData.price"/>
</p>
</div>
<button @click="updateData">提交</button>
<div>
<span>库存</span>
<p>
<PInput type="number" placeholder="请输入库存" v-model="editData.stock"/>
</p>
</div>
<div>
<span>限购数量</span>
<p>
<PInput type="number" placeholder="请输入限购数量" v-model="editData.limitCount"/>
</p>
</div>
<div>
<span>上架时间</span>
<p>
<PInput placeholder="请输入上架时间" v-model="editData.onlineTime"/>
</p>
</div>
<div>
<span>下架时间</span>
<p>
<PInput placeholder="请输入下架时间" v-model="editData.offlineTime"/>
</p>
</div>
<button @click="saveGoodsData">提交</button>
</div>
</Modal>
</template>
@ -81,6 +146,7 @@ import http, {DataListModel} from "../../util/http";
import message from "../../components/message";
import Modal from "../../components/modal/modal.vue";
import Pager from "../../components/pager/Pager.vue";
import Uploader from "../../components/uploader/uploader.vue";
//
//(1: 2: 3: 4:)
const CategoryEnum = {
@ -138,18 +204,44 @@ function loadDataList() {
})
}
function fillDataToObject(obj: any, data: any, keys: string[]) {
keys.forEach(key => {
if (typeof (data[key]) === 'undefined') {
if(typeof(obj[key]) != 'undefined'){
// null
obj[key] = null;
}
return;
}
obj[key] = data[key]
})
}
const fieldKey = [
'id', 'cover', 'title',
'type', 'category', 'description',
'offlineTime', 'onlineTime', 'price',
'originPrice', 'stock', 'limitCount',
]
function onEditData(data: GoodsModel) {
fillDataToObject(editData, data, fieldKey)
modalVisible.value = true
}
async function updateData() {
async function saveGoodsData() {
try {
await http.put(`/admin/goods/${editData.id}`, editData)
console.log(editData)
if (editData.id <= 0) {
await http.post('/admin/goods', editData)
} else {
await http.put(`/admin/goods/${editData.id}`, editData)
}
modalVisible.value = false;
message.toast('更新成功');
message.toast('保存成功');
loadDataList();
} catch (e) {
message.toast(e.message || '更新失败')
message.toast(e.message || '保存失败')
}
}

View File

@ -2,9 +2,10 @@ import vue from '@vitejs/plugin-vue'
import vueJsxPlugin from "@vitejs/plugin-vue-jsx";
export default {
hmr: true,
// 开发服务信息
server: {
host:'::'
host: '::'
},
// 必须配置vue插件
plugins: [

View File

@ -11,7 +11,9 @@ public class ApplicationConfig implements WebMvcConfigurer {
// 跨域 目前小程序不需要暂时不用考虑
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE");
}

View File

@ -0,0 +1,21 @@
package me.xiaoyan.point.api.controller;
import lombok.SneakyThrows;
import me.xiaoyan.point.api.pojo.dto.FileUploadResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("file")
public class FileController {
@SneakyThrows
@PostMapping("upload")
public FileUploadResult upload(MultipartFile multipartFile) {
Thread.sleep(1);
String url = "https://m.360buyimg.com/mobilecms/s750x750_jfs/t1/211852/28/15135/312413/6235a37fEc6496443/de07faa29ece45ee.jpg";
return new FileUploadResult(url, "");
}
}

View File

@ -0,0 +1,16 @@
package me.xiaoyan.point.api.pojo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileUploadResult implements Serializable {
private String url;
private String name;
}