feat 完善商品的管理
This commit is contained in:
parent
440824c98f
commit
4935b9c24c
@ -119,14 +119,46 @@ body {
|
|||||||
.pointer-cursor {
|
.pointer-cursor {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
table{
|
|
||||||
width: 100%;
|
.table-wrapper {
|
||||||
border-left: solid 1px #eee;
|
.table-toolbar{
|
||||||
border-top: solid 1px #eee;
|
display: flex;
|
||||||
border-collapse: collapse;
|
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;
|
.page-wrapper {
|
||||||
border-bottom: solid 1px #eee;
|
margin: 20px 0;
|
||||||
padding:6px;
|
|
||||||
|
.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";
|
import EyeClose from "../icon/EyeClose.vue";
|
||||||
|
|
||||||
interface PInputEvents {
|
interface PInputEvents {
|
||||||
'update:modelValue': (value: string) => void
|
'update:modelValue': (value: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type PInputProps = {
|
type PInputProps = {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
modelValue?: string
|
modelValue?: any
|
||||||
type?: 'text' | 'password' | 'textarea'
|
type?: 'text' | 'password' | 'textarea'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,25 +77,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</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>
|
|
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 App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import {httpConfig} from "./util/http";
|
import {httpConfig} from "./util/http";
|
||||||
import './assets/app.css'
|
import './assets/app.less'
|
||||||
import PInput from "./components/input";
|
import PInput from "./components/input";
|
||||||
|
|
||||||
httpConfig.baseURL = "http://localhost:8080"
|
httpConfig.baseURL = "http://localhost:8080"
|
||||||
|
@ -7,7 +7,7 @@ export type HttpMethod = 'get' | 'post' | 'delete' | 'put'
|
|||||||
export enum RequestDataContentType {
|
export enum RequestDataContentType {
|
||||||
JSON = 'application/json;charset=UTF-8',
|
JSON = 'application/json;charset=UTF-8',
|
||||||
FORM = 'application/x-www-form-urlencoded;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 = {
|
export const httpConfig = {
|
||||||
@ -68,7 +68,9 @@ class Http {
|
|||||||
*/
|
*/
|
||||||
request<T>(url: string, data: any = null, method: HttpMethod = 'post', type: 'normal' | 'form' = 'normal') {
|
request<T>(url: string, data: any = null, method: HttpMethod = 'post', type: 'normal' | 'form' = 'normal') {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
let contentType = RequestDataContentType.FORM;
|
const headers: any = {
|
||||||
|
'Content-Type': RequestDataContentType.FORM
|
||||||
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
if (type === 'form') {
|
if (type === 'form') {
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
@ -76,12 +78,13 @@ class Http {
|
|||||||
form.append(key, data[key]);
|
form.append(key, data[key]);
|
||||||
}
|
}
|
||||||
data = form; // 将data有{} => formData
|
data = form; // 将data有{} => formData
|
||||||
contentType = RequestDataContentType.FORM_DATA; // 由于使用formData
|
// contentType = RequestDataContentType.FORM_DATA; // 由于使用formData
|
||||||
|
delete headers['Content-Type'];
|
||||||
method = 'post';
|
method = 'post';
|
||||||
} else if (method == 'post') {
|
} else if (method == 'post' || method == 'put') {
|
||||||
// 将数据对象 转成json
|
// 将数据对象 转成json
|
||||||
data = JSON.stringify(data);
|
data = JSON.stringify(data);
|
||||||
contentType = RequestDataContentType.JSON;
|
headers['Content-Type'] = RequestDataContentType.JSON;
|
||||||
} else {
|
} else {
|
||||||
// 装对象转成 key=value&key=value
|
// 装对象转成 key=value&key=value
|
||||||
const params = [];
|
const params = [];
|
||||||
@ -92,10 +95,6 @@ class Http {
|
|||||||
url += '?' + params.join('&')
|
url += '?' + params.join('&')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': contentType
|
|
||||||
}
|
|
||||||
const token = useUserStore().token();
|
const token = useUserStore().token();
|
||||||
if (token) headers.token = token
|
if (token) headers.token = token
|
||||||
fetch(httpConfig.baseURL + url, {
|
fetch(httpConfig.baseURL + url, {
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div>
|
<div class="table-wrapper">
|
||||||
<div class="search-form">
|
<div class="table-toolbar">
|
||||||
<span>标题:<PInput v-model="param.title" placeholder="要查询的商品标题"/></span>
|
<div class="search-form">
|
||||||
<span>
|
<span>标题:<PInput v-model="param.title" placeholder="要查询的商品标题"/></span>
|
||||||
|
<span>
|
||||||
分类:
|
分类:
|
||||||
<select v-model="param.category">
|
<select v-model="param.category">
|
||||||
<option v-for="(text,value) in CategoryEnum" :key="value" :value="value">{{ text }}</option>
|
<option v-for="(text,value) in CategoryEnum" :key="value" :value="value">{{ text }}</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<button @click="onReset">重置</button>
|
<button @click="onReset">重置</button>
|
||||||
<button @click="onSearch">搜索</button>
|
<button @click="onSearch">搜索</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-extra">
|
||||||
|
<button @click="onEditData({id:0})">新增</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
@ -53,24 +58,84 @@
|
|||||||
<Modal v-model="modalVisible">
|
<Modal v-model="modalVisible">
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<span>昵称</span>
|
<span>标题</span>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>省份</span>
|
<span>原价</span>
|
||||||
<p>
|
<p>
|
||||||
<PInput placeholder="请输入省份" v-model="editData.province"/>
|
<PInput type="number" placeholder="请输入原价" v-model="editData.originPrice"/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>城市</span>
|
<span>销售价格</span>
|
||||||
<p>
|
<p>
|
||||||
<PInput placeholder="请输入城市" v-model="editData.city"/>
|
<PInput type="number" placeholder="请输入销售价格" v-model="editData.price"/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
@ -81,6 +146,7 @@ import http, {DataListModel} from "../../util/http";
|
|||||||
import message from "../../components/message";
|
import message from "../../components/message";
|
||||||
import Modal from "../../components/modal/modal.vue";
|
import Modal from "../../components/modal/modal.vue";
|
||||||
import Pager from "../../components/pager/Pager.vue";
|
import Pager from "../../components/pager/Pager.vue";
|
||||||
|
import Uploader from "../../components/uploader/uploader.vue";
|
||||||
//
|
//
|
||||||
//商品类别(1:普通 2:精选 3:秒杀 4:抽奖)
|
//商品类别(1:普通 2:精选 3:秒杀 4:抽奖)
|
||||||
const CategoryEnum = {
|
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) {
|
function onEditData(data: GoodsModel) {
|
||||||
|
fillDataToObject(editData, data, fieldKey)
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateData() {
|
async function saveGoodsData() {
|
||||||
try {
|
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;
|
modalVisible.value = false;
|
||||||
message.toast('更新成功');
|
message.toast('保存成功');
|
||||||
loadDataList();
|
loadDataList();
|
||||||
} catch (e) {
|
} 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";
|
import vueJsxPlugin from "@vitejs/plugin-vue-jsx";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
hmr: true,
|
||||||
// 开发服务信息
|
// 开发服务信息
|
||||||
server: {
|
server: {
|
||||||
host:'::'
|
host: '::'
|
||||||
},
|
},
|
||||||
// 必须配置vue插件
|
// 必须配置vue插件
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -11,7 +11,9 @@ public class ApplicationConfig implements WebMvcConfigurer {
|
|||||||
// 跨域 目前小程序不需要,暂时不用考虑
|
// 跨域 目前小程序不需要,暂时不用考虑
|
||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
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