add
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user