feat 完善商品的管理
This commit is contained in:
parent
440824c98f
commit
4935b9c24c
@ -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;
|
||||
}
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -76,26 +76,4 @@ 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>
|
||||
</script>
|
0
admin-fe/src/components/uploader/index.ts
Normal file
0
admin-fe/src/components/uploader/index.ts
Normal file
62
admin-fe/src/components/uploader/uploader.vue
Normal file
62
admin-fe/src/components/uploader/uploader.vue
Normal 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>
|
@ -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"
|
||||
|
@ -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, {
|
||||
|
@ -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 || '保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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: [
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
@ -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, "");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user