add
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -51,6 +51,29 @@ public class ConfessionController {
|
||||
return ApiResponse.ok(confessionRepository.save(confession));
|
||||
}
|
||||
|
||||
@GetMapping("/order/{orderId}")
|
||||
public ApiResponse<Confession> getByOrder(@PathVariable Long orderId) {
|
||||
Order order = orderRepository.findById(orderId)
|
||||
.orElseThrow(() -> new ApiException(404, "订单不存在"));
|
||||
if (!order.getUserId().equals(AuthContext.get().getId())) {
|
||||
throw new ApiException(403, "无权限");
|
||||
}
|
||||
return ApiResponse.ok(confessionRepository.findByOrderId(orderId).orElse(null));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Confession> update(@PathVariable Long id, @RequestBody CreateConfessionRequest request) {
|
||||
Confession confession = confessionRepository.findById(id)
|
||||
.orElseThrow(() -> new ApiException(404, "告白不存在"));
|
||||
if (!confession.getUserId().equals(AuthContext.get().getId())) {
|
||||
throw new ApiException(403, "无权限");
|
||||
}
|
||||
confession.setTitle(request.getTitle());
|
||||
confession.setMessage(request.getMessage());
|
||||
confession.setImageUrl(request.getImageUrl());
|
||||
return ApiResponse.ok(confessionRepository.save(confession));
|
||||
}
|
||||
|
||||
@PublicEndpoint
|
||||
@GetMapping("/{code}")
|
||||
public ApiResponse<Confession> get(@PathVariable String code) {
|
||||
|
||||
@@ -6,4 +6,5 @@ import java.util.Optional;
|
||||
|
||||
public interface ConfessionRepository extends JpaRepository<Confession, Long> {
|
||||
Optional<Confession> findByCode(String code);
|
||||
Optional<Confession> findByOrderId(Long orderId);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,21 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
private final AuthInterceptor authInterceptor;
|
||||
private final String allowedOrigins;
|
||||
private final String uploadDir;
|
||||
|
||||
public WebConfig(AuthInterceptor authInterceptor,
|
||||
@Value("${app.cors.allowed-origins}") String allowedOrigins) {
|
||||
@Value("${app.cors.allowed-origins}") String allowedOrigins,
|
||||
@Value("${app.upload.dir}") String uploadDir) {
|
||||
this.authInterceptor = authInterceptor;
|
||||
this.allowedOrigins = allowedOrigins;
|
||||
this.uploadDir = uploadDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,4 +34,21 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/uploads/**")
|
||||
.addResourceLocations("file:" + toFileLocation(uploadDir));
|
||||
}
|
||||
|
||||
private String toFileLocation(String path) {
|
||||
String normalized = path.replace("\\", "/");
|
||||
if (normalized.matches("^[A-Za-z]:/.*")) {
|
||||
return "/" + normalized + "/";
|
||||
}
|
||||
if (!normalized.startsWith("/")) {
|
||||
return "/" + normalized + "/";
|
||||
}
|
||||
return normalized.endsWith("/") ? normalized : normalized + "/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ server:
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/flower_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
url: jdbc:mysql://localhost:3307/flower_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: root
|
||||
password: qq5211314
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
@@ -16,9 +16,18 @@ spring:
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: Asia/Shanghai
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
|
||||
app:
|
||||
cors:
|
||||
allowed-origins: http://localhost:8081
|
||||
qr:
|
||||
base-url: http://localhost:8081/#/gift
|
||||
upload:
|
||||
dir: D:/bs/flower/files
|
||||
ai:
|
||||
token: b676775d829147dea4955ce809cc1beb.GZIuVsLf19YT5B12
|
||||
model: glm-4.7
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"core-js": "^3.36.0",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"element-ui": "^2.15.14",
|
||||
"vue": "^2.7.16",
|
||||
"vue-router": "^3.6.5"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import http from './http';
|
||||
|
||||
export const createConfession = (data) => http.post('/confessions', data);
|
||||
export const getConfessionByOrder = (orderId) => http.get(`/confessions/order/${orderId}`);
|
||||
export const updateConfession = (id, data) => http.put(`/confessions/${id}`, data);
|
||||
export const getConfession = (code) => http.get(`/confessions/${code}`);
|
||||
export const listBarrages = (code) => http.get(`/confessions/${code}/barrages`);
|
||||
export const sendBarrage = (code, data) => http.post(`/confessions/${code}/barrages`, data);
|
||||
|
||||
@@ -4,19 +4,38 @@
|
||||
<el-button type="text" @click="$router.push('/orders')">返回订单</el-button>
|
||||
</el-card>
|
||||
<el-card class="content">
|
||||
<h3>定制告白弹幕</h3>
|
||||
<el-form :model="form">
|
||||
<div class="title">定制告白弹幕</div>
|
||||
<el-form :model="form" label-width="90px" class="form">
|
||||
<el-form-item label="标题">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="祝福文字">
|
||||
<el-input type="textarea" v-model="form.message" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配图链接">
|
||||
<el-input v-model="form.imageUrl" placeholder="https://..." />
|
||||
<el-form-item label="AI 生成">
|
||||
<div class="ai-row">
|
||||
<el-input v-model="aiPrompt" placeholder="例如:生成3条简短表白祝福语" />
|
||||
<el-button type="primary" :loading="aiLoading" @click="generateWithAi">AI 生成</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="配图上传">
|
||||
<el-upload
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:on-success="handleUpload"
|
||||
>
|
||||
<el-button type="primary">上传图片</el-button>
|
||||
</el-upload>
|
||||
<div v-if="form.imageUrl" class="preview">
|
||||
<img :src="form.imageUrl" alt="preview" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" @click="submit">生成页面</el-button>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="submit">生成页面</el-button>
|
||||
</div>
|
||||
<div v-if="confession" class="result">
|
||||
<p>专属链接:{{ giftUrl }}</p>
|
||||
<img :src="qrUrl" class="qr" />
|
||||
@@ -26,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createConfession, getQrUrl } from '../api/confession';
|
||||
import { createConfession, getConfessionByOrder, updateConfession, getQrUrl } from '../api/confession';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -36,7 +55,9 @@ export default {
|
||||
message: '',
|
||||
imageUrl: ''
|
||||
},
|
||||
confession: null
|
||||
confession: null,
|
||||
aiPrompt: ' 约50字,文艺但不晦涩,避免过度肉麻,表达温柔与坚定。仅输出文案本身,不要加标题或解释。',
|
||||
aiLoading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -45,16 +66,84 @@ export default {
|
||||
},
|
||||
qrUrl() {
|
||||
return this.confession ? getQrUrl(this.confession.code) : '';
|
||||
},
|
||||
uploadAction() {
|
||||
return '/api/upload';
|
||||
},
|
||||
uploadHeaders() {
|
||||
const token = localStorage.getItem('token');
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadExisting();
|
||||
},
|
||||
methods: {
|
||||
loadExisting() {
|
||||
const orderId = this.$route.params.orderId;
|
||||
getConfessionByOrder(orderId).then((res) => {
|
||||
const data = res.data.data;
|
||||
if (data) {
|
||||
this.confession = data;
|
||||
this.form.title = data.title || '';
|
||||
this.form.message = data.message || '';
|
||||
this.form.imageUrl = data.imageUrl || '';
|
||||
}
|
||||
});
|
||||
},
|
||||
generateWithAi() {
|
||||
if (!this.aiPrompt) {
|
||||
this.$message.warning('请输入提示词');
|
||||
return;
|
||||
}
|
||||
this.aiLoading = true;
|
||||
fetch('https://yunwu.ai/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer sk-sloS0Nm2VJRPJKJ1c3D2nD4w68d3IESuvJng4NxEnSk1SdhK',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{ role: 'system', content: '你是文艺风格的祝福文案写手,语言自然、真诚、不过度夸张。' },
|
||||
{ role: 'user', content: '请生成一条表白文案,要求如下:'+this.aiPrompt }
|
||||
],
|
||||
stream: false,
|
||||
temperature: 1
|
||||
})
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const content = res && res.choices && res.choices[0] && res.choices[0].message
|
||||
? res.choices[0].message.content
|
||||
: '';
|
||||
if (!content) {
|
||||
this.$message.error('AI 返回为空');
|
||||
return;
|
||||
}
|
||||
this.form.message = content;
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('AI 生成失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.aiLoading = false;
|
||||
});
|
||||
},
|
||||
handleUpload(response) {
|
||||
if (response && response.data && response.data.url) {
|
||||
this.form.imageUrl = response.data.url;
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
createConfession({
|
||||
orderId: this.$route.params.orderId,
|
||||
...this.form
|
||||
}).then((res) => {
|
||||
const payload = { orderId: this.$route.params.orderId, ...this.form };
|
||||
const action = this.confession && this.confession.id
|
||||
? updateConfession(this.confession.id, payload)
|
||||
: createConfession(payload);
|
||||
action.then((res) => {
|
||||
this.confession = res.data.data;
|
||||
this.$message.success('生成成功');
|
||||
this.$message.success('保存成功');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -68,9 +157,34 @@ export default {
|
||||
.content {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form {
|
||||
max-width: 720px;
|
||||
}
|
||||
.ai-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.ai-row .el-input {
|
||||
flex: 1;
|
||||
}
|
||||
.actions {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.preview img {
|
||||
margin-top: 8px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.qr {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
<div>
|
||||
<el-button type="text" @click="$router.push('/orders')">我的订单</el-button>
|
||||
<el-button type="text" @click="$router.push('/profile')">个人中心</el-button>
|
||||
<el-button type="text" @click="$router.push('/admin')">后台管理</el-button>
|
||||
<el-button type="primary" @click="$router.push('/login')">登录</el-button>
|
||||
<el-button v-if="isAdmin" type="text" @click="$router.push('/admin')">后台管理</el-button>
|
||||
<el-button v-if="!isLoggedIn" type="primary" @click="$router.push('/login')">登录</el-button>
|
||||
<el-button v-else type="danger" @click="handleLogout">注销</el-button>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main>
|
||||
@@ -41,6 +42,7 @@
|
||||
|
||||
<script>
|
||||
import { listProducts, listCategories } from '../api/product';
|
||||
import { logout } from '../api/auth';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -50,14 +52,36 @@ export default {
|
||||
categories: [],
|
||||
categoryId: null,
|
||||
keyword: '',
|
||||
placeholder: 'https://via.placeholder.com/300x200?text=Flower'
|
||||
placeholder: 'https://via.placeholder.com/300x200?text=Flower',
|
||||
isAdmin: false,
|
||||
isLoggedIn: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const user = JSON.parse(localStorage.getItem('user') || 'null');
|
||||
this.isAdmin = user && user.role === 'ADMIN';
|
||||
this.isLoggedIn = Boolean(localStorage.getItem('token'));
|
||||
this.loadCategories();
|
||||
this.loadProducts();
|
||||
},
|
||||
methods: {
|
||||
handleLogout() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
this.$confirm('确认退出登录?', '提示', { type: 'warning' })
|
||||
.then(() => {
|
||||
logout().finally(() => {
|
||||
this.$message.success('已退出登录');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
this.isLoggedIn = false;
|
||||
this.isAdmin = false;
|
||||
this.$router.push('/login');
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
loadCategories() {
|
||||
listCategories().then((res) => {
|
||||
this.categories = res.data.data || [];
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
<el-card class="content">
|
||||
<el-table :data="orders">
|
||||
<el-table-column prop="order.orderNo" label="订单号" />
|
||||
<el-table-column prop="order.status" label="状态" />
|
||||
<el-table-column label="状态">
|
||||
<template slot-scope="scope">
|
||||
{{ statusText(scope.row.order.status) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="order.totalAmount" label="金额" />
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
@@ -33,6 +37,16 @@ export default {
|
||||
this.loadOrders();
|
||||
},
|
||||
methods: {
|
||||
statusText(status) {
|
||||
const map = {
|
||||
CREATED: '待支付',
|
||||
PAID: '已支付',
|
||||
SHIPPED: '已发货',
|
||||
COMPLETED: '已完成',
|
||||
CANCELED: '已取消'
|
||||
};
|
||||
return map[status] || status;
|
||||
},
|
||||
loadOrders() {
|
||||
listOrders().then((res) => {
|
||||
this.orders = res.data.data || [];
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
import { getProduct } from '../api/product';
|
||||
import { listAddresses } from '../api/address';
|
||||
import { createOrder } from '../api/order';
|
||||
import { codeToText } from 'element-china-area-data';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -65,7 +66,18 @@ export default {
|
||||
});
|
||||
},
|
||||
formatAddress(addr) {
|
||||
return `${addr.recipientName} ${addr.phone} ${addr.province || ''}${addr.city || ''}${addr.district || ''}${addr.detail || ''}`;
|
||||
const province = this.formatRegion(addr.province);
|
||||
const city = this.formatRegion(addr.city);
|
||||
const district = this.formatRegion(addr.district);
|
||||
return `${addr.recipientName} ${addr.phone} ${province}${city}${district}${addr.detail || ''}`;
|
||||
},
|
||||
formatRegion(value) {
|
||||
if (!value) return '';
|
||||
const key = String(value);
|
||||
if (codeToText && codeToText[key]) {
|
||||
return codeToText[key];
|
||||
}
|
||||
return value;
|
||||
},
|
||||
submitOrder() {
|
||||
if (!this.addressId) {
|
||||
|
||||
@@ -50,9 +50,13 @@
|
||||
<el-input v-model="addressForm.phone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="省市区">
|
||||
<el-input v-model="addressForm.province" placeholder="省" />
|
||||
<el-input v-model="addressForm.city" placeholder="市" />
|
||||
<el-input v-model="addressForm.district" placeholder="区" />
|
||||
<el-cascader
|
||||
v-model="region"
|
||||
:options="regionOptions"
|
||||
:props="regionProps"
|
||||
placeholder="请选择省市区"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="详细地址">
|
||||
<el-input v-model="addressForm.detail" />
|
||||
@@ -73,6 +77,7 @@
|
||||
import { me } from '../api/auth';
|
||||
import { listAddresses, createAddress, updateAddress, deleteAddress } from '../api/address';
|
||||
import http from '../api/http';
|
||||
import { regionData } from 'element-china-area-data';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -80,7 +85,10 @@ export default {
|
||||
profile: { nickname: '', phone: '', email: '' },
|
||||
addresses: [],
|
||||
showDialog: false,
|
||||
addressForm: {}
|
||||
addressForm: {},
|
||||
regionOptions: regionData,
|
||||
regionProps: { value: 'label', label: 'label', children: 'children' },
|
||||
region: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -105,6 +113,7 @@ export default {
|
||||
},
|
||||
editAddress(row) {
|
||||
this.addressForm = { ...row };
|
||||
this.region = [row.province, row.city, row.district].filter(Boolean);
|
||||
this.showDialog = true;
|
||||
},
|
||||
removeAddress(id) {
|
||||
@@ -114,11 +123,15 @@ export default {
|
||||
});
|
||||
},
|
||||
saveAddress() {
|
||||
this.addressForm.province = this.region[0] || '';
|
||||
this.addressForm.city = this.region[1] || '';
|
||||
this.addressForm.district = this.region[2] || '';
|
||||
const api = this.addressForm.id ? updateAddress(this.addressForm.id, this.addressForm) : createAddress(this.addressForm);
|
||||
api.then(() => {
|
||||
this.$message.success('保存成功');
|
||||
this.showDialog = false;
|
||||
this.addressForm = {};
|
||||
this.region = [];
|
||||
this.loadAddresses();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<el-container class="layout">
|
||||
<el-aside width="200px" class="aside">
|
||||
<div class="title">后台管理</div>
|
||||
<el-menu router>
|
||||
<el-aside width="220px" class="aside">
|
||||
<div class="title">植愈后台</div>
|
||||
<el-menu
|
||||
router
|
||||
background-color="#1f2d3d"
|
||||
text-color="#c0c4cc"
|
||||
active-text-color="#409eff"
|
||||
:default-active="$route.path"
|
||||
>
|
||||
<el-menu-item index="/admin">仪表盘</el-menu-item>
|
||||
<el-menu-item index="/admin/products">商品管理</el-menu-item>
|
||||
<el-menu-item index="/admin/orders">订单管理</el-menu-item>
|
||||
@@ -26,14 +32,28 @@
|
||||
min-height: 100vh;
|
||||
}
|
||||
.aside {
|
||||
background: #2d3a4b;
|
||||
background: linear-gradient(180deg, #1f2d3d 0%, #233044 100%);
|
||||
color: #fff;
|
||||
}
|
||||
.title {
|
||||
padding: 20px;
|
||||
padding: 20px 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.header {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
}
|
||||
.el-menu-item {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
}
|
||||
.el-menu-item.is-active {
|
||||
background: rgba(64, 158, 255, 0.15) !important;
|
||||
border-right: 3px solid #409eff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
<div>
|
||||
<el-table :data="orders">
|
||||
<el-table-column prop="order.orderNo" label="订单号" />
|
||||
<el-table-column prop="order.status" label="状态" />
|
||||
<el-table-column label="状态">
|
||||
<template slot-scope="scope">
|
||||
{{ statusText(scope.row.order.status) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="order.totalAmount" label="金额" />
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
@@ -31,6 +35,16 @@ export default {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
statusText(status) {
|
||||
const map = {
|
||||
CREATED: '待支付',
|
||||
PAID: '已支付',
|
||||
SHIPPED: '已发货',
|
||||
COMPLETED: '已完成',
|
||||
CANCELED: '已取消'
|
||||
};
|
||||
return map[status] || status;
|
||||
},
|
||||
load() {
|
||||
adminOrders.list().then((res) => {
|
||||
this.orders = res.data.data || [];
|
||||
|
||||
@@ -26,8 +26,19 @@
|
||||
<el-form-item label="库存">
|
||||
<el-input v-model="form.stock" />
|
||||
</el-form-item>
|
||||
<el-form-item label="封面">
|
||||
<el-input v-model="form.coverUrl" />
|
||||
<el-form-item label="封面上传">
|
||||
<el-upload
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:on-success="handleUpload"
|
||||
>
|
||||
<el-button type="primary">上传图片</el-button>
|
||||
</el-upload>
|
||||
<div v-if="form.coverUrl" class="preview">
|
||||
<img :src="form.coverUrl" alt="cover" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input type="textarea" v-model="form.description" />
|
||||
@@ -55,13 +66,25 @@ export default {
|
||||
return {
|
||||
products: [],
|
||||
showDialog: false,
|
||||
form: {}
|
||||
form: {},
|
||||
uploadAction: '/api/upload'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
uploadHeaders() {
|
||||
const token = localStorage.getItem('token');
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
handleUpload(response) {
|
||||
if (response && response.data && response.data.url) {
|
||||
this.form.coverUrl = response.data.url;
|
||||
}
|
||||
},
|
||||
load() {
|
||||
adminProducts.list().then((res) => {
|
||||
this.products = res.data.data || [];
|
||||
@@ -90,3 +113,13 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preview img {
|
||||
margin-top: 8px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,10 @@ module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080/',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user