This commit is contained in:
18796357645 2025-05-22 21:38:08 +08:00
parent 3ecec5fd77
commit 61fb85befa
41 changed files with 1409 additions and 3967 deletions

View File

@ -1,6 +1,4 @@
package io.modules.sys.controller;
import io.common.annotation.LogOperation;
import io.common.constant.Constant;
import io.common.page.PageData;
@ -22,20 +20,11 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.Map;
/**
* 吐槽墙
*
* @author Mark #
* @since 1.0.0 2024-12-12
*/
@RestController
@RequestMapping("item/wall")
@Tag(name="吐槽墙")
public class WallController {
@RequestMapping("sys/book")
public class BookController {
@Autowired
private BookService wallService;
private BookService bookService;
@GetMapping("page")
@Operation(summary = "分页")
@ -45,58 +34,45 @@ public class WallController {
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref="String") ,
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String")
})
@RequiresPermissions("item:wall:page")
public Result<PageData<BookDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<BookDTO> page = wallService.page(params);
PageData<BookDTO> page = bookService.page(params);
return new Result<PageData<BookDTO>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@RequiresPermissions("item:wall:info")
public Result<BookDTO> get(@PathVariable("id") Long id){
BookDTO data = wallService.get(id);
BookDTO data = bookService.get(id);
return new Result<BookDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@RequiresPermissions("item:wall:save")
public Result save(@RequestBody BookDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
wallService.save(dto);
bookService.save(dto);
return new Result();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@RequiresPermissions("item:wall:update")
public Result update(@RequestBody BookDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
wallService.update(dto);
bookService.update(dto);
return new Result();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("item:wall:delete")
public Result delete(@RequestBody Long[] ids){
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
wallService.delete(ids);
bookService.delete(ids);
return new Result();
}

View File

@ -3,7 +3,7 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:33060/block_house?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://localhost:3306/block-chaincopyright?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: 123456
initial-size: 10

View File

@ -12,6 +12,6 @@ import org.apache.ibatis.annotations.Mapper;
* @since 1.0.0 2024-12-12
*/
@Mapper
public interface WallDao extends BaseDao<BookEntity> {
public interface BookDao extends BaseDao<BookEntity> {
}

View File

@ -1,5 +1,6 @@
package io.modules.item.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.SchemaProperty;
import lombok.Data;
@ -17,20 +18,24 @@ public class BookDTO implements Serializable {
private Long id; // 主键ID
private Long userId; // 主键ID
private String img; // 封面
private String image; // 封面
private String isbn; // ISBN编号
private String title; // 图书标题
private String author; // 作者
private String publisher; // 出版社
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date publishDate; // 出版日期
private String copyrightOwner; // 版权持有人
private Integer copyrightStartYear; // 版权起始年份
private Integer copyrightEndYear; // 版权到期年份
private String copyrightStartYear; // 版权起始年份
private String copyrightEndYear; // 版权到期年份
private String edition; // 版次
private String language; // 语言
private BigDecimal price; // 图书定价
private String hex; // 上链哈希值
private Date createTime; // 创建时间
private String file; // 电子数据文件地址
private String status;
}

View File

@ -33,8 +33,8 @@ public class UserDTO implements Serializable {
@SchemaProperty(name = "昵称")
private String nickName;
@SchemaProperty(name = "介绍")
private String introduce;
@SchemaProperty(name = "手机号")
private String phone;
}

View File

@ -1,6 +1,7 @@
package io.modules.item.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
@ -12,19 +13,22 @@ public class BookEntity {
private Long id; // 主键ID
private Long userId;
private String img; // 封面
private String image; // 封面
private String isbn; // ISBN编号
private String title; // 图书标题
private String author; // 作者
private String publisher; // 出版社
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date publishDate; // 出版日期
private String copyrightOwner; // 版权持有人
private Integer copyrightStartYear; // 版权起始年份
private Integer copyrightEndYear; // 版权到期年份
private String copyrightStartYear; // 版权起始年份
private String copyrightEndYear; // 版权到期年份
private String edition; // 版次
private String language; // 语言
private BigDecimal price; // 图书定价
private String hex; // 上链哈希值
private Date createTime; // 创建时间
private String file; // 电子数据文件地址
private String status; // 电子数据文件地址
}

View File

@ -31,7 +31,7 @@ public class FrontUserEntity {
*/
private String nickName;
/**
* 介绍
* 手机号
*/
private String introduce;
private String phone;
}

View File

@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import cn.hutool.core.util.StrUtil;
import io.common.service.impl.CrudServiceImpl;
import io.modules.item.dao.WallDao;
import io.modules.item.dao.BookDao;
import io.modules.item.dto.BookDTO;
import io.modules.item.entity.BookEntity;
import io.modules.item.service.BookService;
@ -14,19 +14,20 @@ import java.util.Map;
@Service
public class BookServiceImpl extends CrudServiceImpl<WallDao, BookEntity, BookDTO> implements BookService {
public class BookServiceImpl extends CrudServiceImpl<BookDao, BookEntity, BookDTO> implements BookService {
@Override
public QueryWrapper<BookEntity> getWrapper(Map<String, Object> params){
String id = (String)params.get("id");
String userId = (String)params.get("userId");
String title = (String)params.get("title");
String status = (String)params.get("status");
QueryWrapper<BookEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(id), "id", id);
wrapper.eq(StrUtil.isNotBlank(userId), "userId", userId);
wrapper.eq(StrUtil.isNotBlank(userId), "user_id", userId);
wrapper.like(StrUtil.isNotBlank(title), "title", title);
wrapper.like(StrUtil.isNotBlank(status), "status", status);
wrapper.orderByDesc("create_time");
return wrapper;
}
}

View File

@ -1,4 +1,5 @@
package io.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.annotation.Login;
import io.annotation.LoginUser;
import io.common.page.PageData;
@ -7,7 +8,10 @@ import io.common.validator.ValidatorUtils;
import io.common.validator.group.AddGroup;
import io.common.validator.group.DefaultGroup;
import io.entity.UserEntity;
import io.modules.item.dao.BookDao;
import io.modules.item.dto.BookDTO;
import io.modules.item.dto.OrderDTO;
import io.modules.item.entity.BookEntity;
import io.modules.item.service.DictService;
import io.modules.item.service.BookService;
import io.swagger.v3.oas.annotations.Operation;
@ -29,6 +33,9 @@ public class BookController {
@Autowired
private BookService bookService;
@Autowired
private BookDao bookDao;
@Login
@GetMapping("page")
@Operation(summary = "分页")
@ -49,5 +56,26 @@ public class BookController {
return new Result();
}
@GetMapping("{id}")
@Operation(summary = "信息")
public Result<BookDTO> get(@PathVariable("id") Long id){
BookDTO data = bookService.get(id);
return new Result<BookDTO>().ok(data);
}
@GetMapping("hex")
@Operation(summary = "分页")
public Result<BookEntity> hex(@Parameter(hidden = true) @RequestParam String hex){
LambdaQueryWrapper<BookEntity> lwq = new LambdaQueryWrapper<>();
lwq.eq(BookEntity::getHex,hex);
BookEntity bookEntity = bookDao.selectOne(lwq);
if (bookEntity == null){
return new Result<BookEntity>().error("查询不到信息");
}
return new Result<BookEntity>().ok(bookEntity);
}
}

View File

@ -73,7 +73,7 @@ public class UserController {
user.setId(dto.getId());
user.setUsername(dto.getUsername());
user.setNickName(dto.getNickName());
user.setIntroduce(dto.getIntroduce());
user.setPhone(dto.getPhone());
user.setPassword(DigestUtil.sha256Hex(dto.getPassword()));
userService.updateById(user);
return new Result();

View File

@ -30,6 +30,6 @@ public class RegisterDTO {
@Schema(title = "昵称")
private String nickName;
@Schema(title = "介绍")
private String introduce;
@Schema(title = "手机号")
private String phone;
}

View File

@ -30,9 +30,9 @@ public class UserEntity implements Serializable {
*/
private String nickName;
/**
* 介绍
* 手机号
*/
private String introduce;
private String phone;
/**
* 用户名
*/

View File

@ -6,7 +6,7 @@ spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:33060/block_house?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://localhost:3306/block-chaincopyright?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: 123456
initial-size: 10
@ -33,7 +33,7 @@ spring:
multi-statement-allow: true
web:
resources:
static-locations: "file:D:/202505/block-chaincopyright/upload/"
static-locations: "file:D:/20250519/block-chaincopyright/upload/"
upload:
path: D:\202505\block-chaincopyright\upload
path: D:\20250519\block-chaincopyright\upload
url: http://localhost:18081/

View File

@ -1,60 +0,0 @@
<template>
<div class="chart-container" ref="chartRef"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref(null)
let chartInstance = null
const fetchData = async () => {
try {
// 使 axios
const response = await adminRequest.get('/sys/item/view') // API URL
const data = response.data // [{ name: 'A', value: 100 }, ...]
//
const options = {
xAxis: {
type: 'category',
data: data.map((item) => item.year),
},
yAxis: {
type: 'value',
},
series: [
{
data: data.map((item) => item.count),
type: 'line',
},
],
}
//
chartInstance.setOption(options)
} catch (error) {
console.error('数据请求失败:', error)
}
}
onMounted(() => {
chartInstance = echarts.init(chartRef.value)
// fetchData
fetchData()
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.chart-container {
width: 100%;
height: 300px;
}
</style>

View File

@ -1,97 +0,0 @@
<!--详情底部-->
<template>
<el-row :gutter="20">
<el-col :span="12">
<div class="comment" v-for="item in state.commentList">
<el-row :gutter="20">
<el-col :span="3" ><span style="color: #598bd3;">{{ item.nickName }}</span></el-col>
<el-col :span="6">
<span class="time">
{{item.createTime}}
</span>
</el-col>
</el-row>
<div class="content">
{{ item.content }}
</div>
<el-divider />
</div>
</el-col>
<!--热门-->
<el-col :span="12">
<div v-html="state.info.description"></div>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
const state =reactive(<any>{
commentList:[],
content:"",
info:""
})
const itemId = route.params.id;
function init() {
frontRequest.get(`/api/item/${itemId}`).then(response =>{
state.info = response.data
})
frontRequest.get("/api/comment/list",{
params:{itemId:itemId}
}).then(response =>{
state.commentList = response.data
console.log( response.data)
})
}
function comment() {
if (state.content ==""){
ElMessage.error("内容不能为空")
return
}
frontRequest.post("/api/comment",{itemId:itemId,content:state.content}).then(response =>{
ElMessage.success("发表成功")
state.comment=""
init()
})
}
onMounted(()=>{
init()
})
</script>
<style scoped>
.comment{
color: #666;
font-size: 13px;
word-wrap: break-word;
.content{
padding-top: 15px;
font-size: 14px;
color: #111;
text-indent: 2em; /* 中文缩进 */
}
.time{
color: #598bd3;
}
}
:deep(.el-image){
border-radius: 10px;
height: 420px;
margin-left: 30px;
}
.commit{
width: 500px;
}
:deep(.el-input__wrapper){
height: 100px;
border-radius: 10px;
}
</style>

View File

@ -1,265 +0,0 @@
<template>
<!-- 搜索框 -->
<el-row v-if="isPage" justify="center">
<el-col :span="24" class="search-container">
<el-input
v-model="state.page.title"
placeholder="搜索厨房用具"
class="search-input"
clearable
@input="init"
>
<template #append>
<el-button :icon="Search" @click="init" />
</template>
</el-input>
</el-col>
</el-row>
<!-- 品牌标签云 -->
<el-row v-if="isPage" class="brand-container">
<el-col :span="24">
<div class="brand-list">
<el-tag
v-for="brand in state.brands"
:key="brand"
class="brand-item"
effect="plain"
round
>
{{ brand.name }}
</el-tag>
</div>
</el-col>
</el-row>
<!-- 房源卡片 -->
<el-row class="product-container">
<el-col :span="24">
<el-space wrap :size="20">
<el-card
v-for="item in getList"
:key="item.id"
class="product-card"
@click="to(item.id)"
shadow="hover"
>
<div class="image-container">
<el-image :src="item.image" class="product-image" />
<el-tag v-if="item.tag" class="product-tag">{{ item.tag }}</el-tag>
</div>
<div class="product-info">
<div class="product-title">{{ item.title }}</div>
<div class="price-container">
<span class="current-price">{{ item.price }}</span>
<el-tag type="success" size="small">区块溯源</el-tag>
</div>
</div>
</el-card>
</el-space>
</el-col>
</el-row>
<!-- 分页 -->
<el-row v-if="isPage" class="pagination-container">
<el-col :span="24">
<el-pagination
background
layout="prev, pager, next"
:total="state.total"
@current-change="handleCurrentChange"
class="custom-pagination"
/>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { itemPage } from '~/api/itemApi'
import { Search } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { computed, reactive, onMounted } from 'vue'
const router = useRouter()
const props = defineProps({
isPage: {
type: Boolean,
default: false,
},
getList: {
type: Array,
default: () => [], //
},
})
// list 使 props.getList
const getList = computed(() => {
return state.getList.length > 0 ? state.getList : props.getList
})
const state = reactive(<any>{
total: 0,
page: {
page: 1,
title: '',
limit:12
},
getList: [],
brands:[]
})
/**
* 初始化
*/
function init() {
if (props.isPage) {
itemPage(state.page).then((res) => {
state.getList = res.data.list
state.total = res.data.total
})
}
frontRequest.get("/api/categories/page").then((res) => {
state.brands = res.data.list
})
}
/**
* 分页
*/
const handleCurrentChange = (val: number) => {
state.page.page = val
init()
}
/**
* 跳转
*/
const to = (id: number) => {
router.push(`/info/${id}`)
}
onMounted(() => {
init()
})
</script>
<style scoped>
/* 搜索框样式 */
.search-container {
padding: 20px 0;
display: flex;
justify-content: center;
}
.search-input {
width: 652px;
height: 48px;
border: 2px solid #ef1f1f;
border-radius: 24px;
overflow: hidden;
}
/* 品牌标签样式 */
.brand-container {
padding: 0 20px 20px;
}
.brand-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.brand-item {
padding: 8px 20px;
transition: all 0.3s;
cursor: pointer;
}
.brand-item:hover {
transform: translateY(-2px);
background-color: #fef0f0;
}
/* 房源卡片样式 */
.product-container {
padding: 0 20px;
}
.product-card {
width: 240px;
border-radius: 12px;
transition: transform 0.3s;
cursor: pointer;
margin-bottom: 20px;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.image-container {
position: relative;
height: 260px;
overflow: hidden;
border-radius: 8px;
}
.product-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.product-image:hover {
transform: scale(1.05);
}
.product-tag {
position: absolute;
left: 10px;
background-color: #b9ccf6;
color: #010110;
}
:deep(.el-card__body){
padding-top: 0;
padding-left: 0;
padding-right: 0;
}
/* 房源信息 */
.product-info {
padding: 12px;
}
.product-title {
font-size: 14px;
color: #333;
font-weight: 500;
margin-bottom: 8px;
height: 40px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-title:hover {
color: #c27006;
}
.price-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.current-price {
color: #ff5722;
font-size: 18px;
font-weight: bold;
}
/* 分页样式 */
.pagination-container {
padding: 30px 0;
}
.custom-pagination {
display: flex;
justify-content: center;
}
</style>

View File

@ -1,90 +0,0 @@
<!--房源展示风格2-->
<template>
<el-row>
<el-col :span="24">
<el-space wrap>
<el-card
v-for="item in state.commit" :key="state.id"
class="box-card"
@click="to(item.id)"
shadow="hover" >
<!--图片-->
<img class="img" :src="item.largePic" fit="fill" />
<!-- 标题-->
<div class="item-title ">{{item.title}}</div>
<!-- 价格&& 评分-->
<div class="price-box">
<span class="price-now ft16 " >{{item.movieType}}</span>
<span class="ft14 c999 price-old" >{{item.ratingValue}} </span>
</div>
</el-card>
</el-space>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const state = reactive({
commit:[]
})
function init() {
frontRequest.get("/api/item/commit").then(res =>{
state.commit = res.data
})
}
onMounted(() =>{
init()
})
const to = (id:number) => {
router.push(`/info/${id}`)
}
</script>
<style scoped>
.box-card{
cursor: pointer;
width: 232px;
height: 300px;
border-radius: 10px;
}
.img{
display: block;
width: 170px;
height: 180px;
margin: 0 auto;
margin-bottom: 20px;
}
.item-title{
color: #333;
font-size: 14px;
width: 164px;
margin: 0 auto 8px auto;
line-height: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
}
.price-box{
text-align: center;
font-size: 16px;
}
.price-now{
color: #FF0000;
padding-right: 10px;
}
.ft16 {
font-size: 16px;
}
.ft14 {
font-size: 14px;
}
.price-old{
color: #fce04b;
}
</style>

View File

@ -1,114 +0,0 @@
<!--房源-->
<template>
<div style="height: 20px"></div>
<el-row>
<el-col :span="24">
<span style="font-weight: 700;font-size: 26px">狗狗主粮</span>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<div class="containa">
<el-space wrap>
<div v-for="(i, index) in 10" :key="i">
<div
@click="to()"
class="box-card"
v-if="index === 0 || index === 5">
<el-image
style="width: 230px;height: 300px;"
src="https://img2.epetbar.com/common/upload/commonfile/2020/03/20/0104650_205628.jpg"
fit="fill" />
</div>
<el-card
v-else
class="box-card"
@click="to()"
shadow="hover" >
<!-- 图片-->
<el-image src="https://img2.epetbar.com/common/upload/commonfile/2020/03/20/0104650_205628.jpg" fit="fill" />
<!-- 标题-->
<div class="item-title ">澳大利亚原装进口自然馈赠Natures Gift 牛肉配方成犬粮 18kg</div>
<!-- 价格&& 评分-->
<div class="price-box">
<span class="price-now ft16 " style="line-height: 1">¥998.00</span>
<span class="ft14 c999 price-old" style="line-height: 1">¥1098.00</span>
</div>
</el-card>
</div>
</el-space>
</div>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
// const props = defineProps({
// isPage: {
// type: Boolean,
// default: false,
// },
// })
const to = () => {
router.push("/front/info")
}
</script>
<style scoped>
.containa{
padding-top: 10px;
margin-bottom: 10px;
}
.box-card{
cursor: pointer;
width: 232px;
height: 300px
}
.box-card1{
cursor: pointer;
width: 100%;
height: 300px
}
:deep(.el-image){
display: block;
width: 170px;
height: 180px;
margin: 0 auto;
}
.item-title{
color: #333;
font-size: 14px;
width: 164px;
margin: 0 auto 8px auto;
line-height: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.price-box{
text-align: center;
font-size: 16px;
}
.price-now{
color: #FF0000;
padding-right: 10px;
}
.ft16 {
font-size: 16px;
}
.ft14 {
font-size: 14px;
}
.pagination-container{
padding-top: 10px;
}
</style>

View File

@ -32,8 +32,9 @@
<template #dropdown>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click="to('/user')">个人中心</el-dropdown-item>
<el-dropdown-item @click="to('/collect')">我的收藏</el-dropdown-item>
<el-dropdown-item @click="to('/order')">我的换住</el-dropdown-item>
<el-dropdown-item @click="to('/upload')">发布版权</el-dropdown-item>
<el-dropdown-item @click="to('/topic')">版权溯源</el-dropdown-item>
<el-dropdown-item @click="to('/push')">版权管理</el-dropdown-item>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>

View File

@ -1,100 +0,0 @@
<template>
<v-chart class="chart" :option="option" autoresize />
</template>
<script setup lang="ts">
import { use } from 'echarts/core'
import { PieChart } from 'echarts/charts' //
import { CanvasRenderer } from 'echarts/renderers'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
} from 'echarts/components'
import VChart from 'vue-echarts'
import { ref, onMounted } from 'vue'
import axios from 'axios' // axios
use([
CanvasRenderer,
PieChart, // 使
TitleComponent,
TooltipComponent,
LegendComponent,
])
// option
const option = ref<any>({
title: {
text: '入住品牌分析',
left: 'center',
},
tooltip: {
trigger: 'item', // tooltip 'item'
},
legend: {
orient: 'vertical', //
left: 'left',
data: [], //
},
series: [
{
name: '销售数量',
type: 'pie', //
radius: '50%', //
data: [], //
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
})
//
function getLastSevenDays() {
const dates = []
for (let i = 6; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
const formattedDate = date.toISOString().split('T')[0]
dates.push(formattedDate)
}
return dates
}
//
async function fetchData() {
try {
const response = await adminRequest.get('/sys/item/view1')
const data = response.data
const pieData = data.map((item: { name: string, value: number }) => ({
value: item.value,
name: item.name,
}))
// option
option.value.legend.data = data
option.value.series[0].data = pieData
} catch (error) {
console.error('数据加载失败', error)
}
}
//
onMounted(() => {
fetchData()
})
</script>
<style>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,87 +0,0 @@
<template>
<v-chart class="chart" :option="option" autoresize />
</template>
<script setup lang="ts">
import { use } from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
AxisPointerComponent,
} from 'echarts/components'
import VChart from 'vue-echarts'
use([
CanvasRenderer,
LineChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
AxisPointerComponent, //
])
function getLastSevenDays() {
const dates = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const formattedDate = date.toISOString().split('T')[0];
dates.push(formattedDate);
}
return dates;
}
const option = ref<any>({
title: {
text: '近几天房源销售分析',
left: 'center',
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['数量'],
left: 'left',
},
grid: {
left: '10%',
right: '10%',
bottom: '10%',
containLabel: true, //
},
xAxis: {
type: 'category',
data: getLastSevenDays(),
},
yAxis: {
type: 'value',
},
series: [
{
name: '数量',
type: 'line', // 线
data: [820, 932, 901, 934, 1290, 1330, 1320, 1010, 1100, 1230, 1300, 1420],
itemStyle: {
color: '#66b3ff',
},
lineStyle: {
width: 2,
},
smooth: true, // 线
areaStyle: { //
origin: 'start',
color: 'rgba(102, 179, 255, 0.2)',
},
},
],
})
</script>
<style>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@ -35,8 +35,7 @@ const state = reactive({
userMenu:[
{name: '个人中心', path: '/user'},
{name: '我的收藏', path: '/collect'},
{name: '我的换住', path: '/order'},
{name: '版权管理', path: '/push'},
]
})

View File

@ -1,232 +0,0 @@
<template>
<div>
<!--查交易记录的按钮-->
<el-button type="primary" round @click="openAddDialog" size="small">添加</el-button>
<!--表格-->
<el-row>
<el-col :span="24">
<el-table v-loading="loading" :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="recipientName" label="收货人" align="center" />
<el-table-column prop="phone" label="电话/手机" align="center" />
<el-table-column prop="city" label="所在地区" align="center" />
<el-table-column prop="address" label="详细地址" align="center" />
<!--列表结束-->
<el-table-column label="操作" align="center" >
<template #default="scope">
<el-button @click="del(scope.row.id)" type="danger" size="small">删除</el-button>
</template>
</el-table-column>
<template v-slot:empty>
<el-empty description="数据去外太空了~" />
</template>
</el-table>
<!-- 分页控件 -->
<el-pagination
v-if="state.query.total > 0"
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.query.total"
background layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-col>
</el-row>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="state.dialogVisible" title="新增" width="50%">
<el-form
:model="state.formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="收货人" prop="recipientName" :rules="[{ required: true, message: '请输入收货人', trigger: 'blur' }]">
<el-input v-model="state.formData.recipientName"/>
</el-form-item>
<el-form-item label="电话/手机" prop="phone" :rules="[{ required: true, message: '请输入电话/手机', trigger: 'blur' }]">
<el-input v-model="state.formData.phone"/>
</el-form-item>
<el-form-item label="所在地区" prop="city" :rules="[{ required: true, message: '请输入所在地区', trigger: 'blur' }]">
<!-- <el-input v-model.number="state.formData.city"/>-->
<el-cascader
style="width: 260px"
v-model="value"
:options="state.options"
:props="props"
@change="handleChange"
placeholder="请选择地区"
/>
</el-form-item>
<el-form-item label="详细地址" prop="address" :rules="[{ required: true, message: '请输入详细地址', trigger: 'blur' }]">
<el-input v-model="state.formData.address"/>
</el-form-item>
<!--表单结束===============================================================================================================================-->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="state.dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveTransaction(formRef)">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
//
import type {FormInstance, TabsPaneContext} from 'element-plus'
const formRef = ref<FormInstance>()
const loading = ref(true)
const state = reactive(<any>{
route:"api/address",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 5, //
title:"",
status:0,
},
formData:{},
cateList:[],
options:[],
})
const value = ref('')
const props = {
expandTrigger: 'hover' as const,
}
/**
* 转成列表
* @param value
*/
const handleChange = (value:any) => {
const labels = getLabelsFromValue(value, state.options);
state.formData.city = labels
}
// label
const getLabelsFromValue = (value:any, options:any) => {
const labels:any = [];
const findLabels = (value:any, options:any) => {
for (const item of options) {
if (item.value === value[0]) {
labels.push(item.label);
if (value.length > 1 && item.children) {
findLabels(value.slice(1), item.children);
}
break;
}
}
};
findLabels(value, options);
return labels;
};
//
const init = () => {
frontRequest.get(`${state.route}/page`, {
params: state.query
}).then((res:any) => {
state.getList = res.data.list
state.query.total = res.data.total
}).finally(() => {
loading.value = false
})
}
//
const openAddDialog = () => {
state.formData = {
sort:1
}
state.dialogVisible = true
}
//
const edit = (row: any) => {
state.formData = row
state.dialogVisible = true
}
//
const saveTransaction = async (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
state.formData.city = state.formData.city.toString()
if (state.formData.id) {
//
frontRequest.put(`${state.route}`, state.formData).then(() =>{
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
})
} else {
//
frontRequest.post(`${state.route}`, state.formData).then(() =>{
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
})
}
}
})
}
//
const del = async (id: number) => {
try {
await frontRequest.delete(`${state.route}/${id}`)
init()
ElMessage.success("删除成功~")
} catch (error) {
ElMessage.error("删除失败~")
}
}
//
const handlePageChange = (page: number) => {
state.query.page = page
init()
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
// tab name
const selectedStatus = state.getStatus[tab.index].status;
state.query.status = selectedStatus;
init();
};
//
onMounted(() => {
init()
frontRequest.get(`/api/area/tree`).then((res:any) => {
console.log(res)
state.options = res.data
})
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
:deep(.el-upload:hover) {
border-color: var(--el-color-primary);
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -1,207 +0,0 @@
<template>
<div>
<el-row>
<el-col :span="24">
<el-form :inline="true" >
<el-form-item label="分类名称">
<el-input v-model="state.query.name" placeholder="分类名称" clearable />
</el-form-item>
<el-form-item >
<el-button type="primary" @click="init" >查询</el-button>
<el-button type="primary" @click="openAddDialog" >添加</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
<div style="height: 30px"></div>
<!--表格-->
<el-row>
<el-col :span="24">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="id" label="编号" align="center" />
<el-table-column prop="name" label="分类名称" align="center" />
<!-- <el-table-column prop="largePic" label="图片" align="center" >-->
<!-- <template #default="{ row }">-->
<!-- &lt;!&ndash; 点击图片后显示弹框预览 &ndash;&gt;-->
<!-- <el-image-->
<!-- :src="row.path"-->
<!-- fit="cover"-->
<!-- style="width: 50px;height: 50px"-->
<!-- />-->
<!-- </template>-->
<!-- </el-table-column>-->
<!--列表结束-->
<el-table-column label="操作" align="center" >
<template #default="scope">
<el-button @click="edit(scope.row)" size="small">编辑</el-button>
<el-button @click="del(scope.row.id)" type="danger" size="small">删除</el-button>
</template>
</el-table-column>
<template v-slot:empty>
<el-empty description="数据去外太空了~" />
</template>
</el-table>
<!-- 分页控件 -->
<el-pagination
v-if="state.query.total > 0"
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.query.total"
background layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-col>
</el-row>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="state.dialogVisible" title="新增" width="50%">
<el-form
:model="state.formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="分类名称" prop="name" :rules="[{ required: true, message: '请输入分类名称', trigger: 'blur' }]">
<el-input v-model="state.formData.name"/>
</el-form-item>
<!-- <el-form-item label="图片" prop="image" >-->
<!-- <image-upload @update:imageUrl="handleImageUrl" :image-url="state.formData.image"></image-upload>-->
<!-- </el-form-item>-->
<!--表单结束===============================================================================================================================-->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="state.dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveTransaction(formRef)">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
//
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const state = reactive({
route:"sys/categories",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
},
formData:{}
})
//
const init = () => {
adminRequest.get(`${state.route}/page`, {
params: state.query
}).then((res:any) => {
state.getList = res.data.list
state.query.total = res.data.total
})
}
//
const openAddDialog = () => {
// formData = {
// name:"",
// path:"",
// }
state.dialogVisible = true
}
//
const edit = (row: any) => {
state.formData = row
state.dialogVisible = true
}
//
const saveTransaction = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
if (state.formData.id) {
//
adminRequest.put(`${state.route}`, state.formData).then(()=>{
init()
state.dialogVisible = false
ElMessage.success("更新成功~")
})
} else {
//
adminRequest.post(`${state.route}`, state.formData).then(() =>{
init()
state.dialogVisible = false
ElMessage.success("新增成功~")
})
}
}
})
}
//
const del = async (id: number) => {
try {
await adminRequest.delete(`${state.route}/${id}`)
init()
ElMessage.success("删除成功~")
} catch (error) {
ElMessage.error("删除失败~")
}
}
//
const handlePageChange = (page: number) => {
state.query.page = page
init()
}
//
onMounted(() => {
init()
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
:deep(.el-upload:hover) {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar{
width: 178px;
height: 178px;
object-fit: cover;
}
</style>
<route lang="json">
{
"meta": {
"layout": "admin"
}
}
</route>

View File

@ -1,128 +0,0 @@
<template>
<div>
<!--查交易记录的按钮-->
<!-- <el-button type="primary" round @click="openAddDialog" size="small">添加</el-button>-->
<!-- <el-row>-->
<!-- <el-col :span="24">-->
<!-- <el-form :inline="true" :model="state.query" class="demo-form-inline">-->
<!-- <el-form-item label="电影名称:">-->
<!-- <el-input v-model="state.query.title" placeholder="请输入电影名称" clearable @input="init" />-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!--表格-->
<el-row>
<el-col :span="24">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="item.id" label="编号" align="center" width="120px"/>
<el-table-column prop="item.title" label="名称" align="center" width="120px"/>
<el-table-column prop="userEntity.nickName" label="用户昵称" align="center" width="120px" />
<el-table-column prop="itemDTO.largePic" label="图片" align="center" width="100" >
<template #default="{ row }">
<!-- 点击图片后显示弹框预览 -->
<el-image
:src="row.item?.image"
fit="cover"
/>
</template>
</el-table-column>
<el-table-column prop="score" label="评分" align="center" />
<el-table-column prop="content" label="评论内容" align="center">
<template #default="scope">
<div style="width: 100%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
{{ scope.row.content }}
</div>
</template>
</el-table-column>
<template v-slot:empty>
<el-empty description="数据去外太空了~" />
</template>
</el-table>
<!-- 分页控件 -->
<el-pagination
v-if="state.query.total > 0"
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.query.total"
background layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
const state = reactive({
route:"sys/order",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
title:"",
type:"0",
}
})
//
const init = () => {
adminRequest.get(`${state.route}/page`, {
params: state.query
}).then((res:any) => {
state.getList = res.data.list
state.query.total = res.data.total
console.log(state.getList)
})
}
//
const handlePageChange = (page: number) => {
state.query.page = page
init()
}
//
onMounted(() => {
init()
})
</script>
<style scoped>
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
:deep(.el-upload:hover) {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar{
width: 178px;
height: 178px;
object-fit: cover;
}
</style>
<route lang="json">
{
"meta": {
"layout": "admin"
}
}
</route>

View File

@ -1,390 +0,0 @@
<template>
<div class="house-management-container">
<!-- 搜索区域 -->
<div class="search-area">
<el-form :inline="true" :model="state.query" class="search-form">
<el-form-item label="房源名称:" class="search-item">
<el-input
v-model="state.query.title"
placeholder="请输入房源名称"
clearable
@input="handleSearch"
class="search-input"
/>
</el-form-item>
<el-form-item label="状态:" class="search-item">
<el-select clearable
v-model="state.query.status"
placeholder="请选择状态"
@change="handleSearch"
style="width: 120px">
<el-option
v-for="item in state.statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item class="action-buttons">
<el-button type="primary" @click="init" class="query-button">
<el-icon><Search /></el-icon>
<span>查询</span>
</el-button>
<el-button type="primary" @click="openAddDialog" class="add-button">
<el-icon><Plus /></el-icon>
<span>添加</span>
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格区域 -->
<div class="table-area">
<el-table
v-loading="state.loading"
:data="state.list"
stripe
border
class="data-table"
empty-text="暂无数据"
>
<el-table-column prop="id" label="编号" width="80" align="center"/>
<el-table-column prop="title" label="标题" min-width="120" align="center"/>
<el-table-column prop="name" label="小区名称" min-width="120" align="center"/>
<el-table-column prop="city" label="城市" width="100" align="center"/>
<el-table-column prop="type" label="户型" width="120" align="center"/>
<el-table-column prop="area" label="面积" width="100" align="center"/>
<el-table-column prop="floor" label="楼层" width="80" align="center"/>
<el-table-column prop="face" label="朝向" width="100" align="center"/>
<el-table-column prop="decoration" label="装修" width="100" align="center"/>
<el-table-column label="封面图" width="100" align="center">
<template #default="{ row }">
<el-image
:src="row.image"
fit="cover"
class="house-image"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="价格 (元/㎡)" width="120" align="center"/>
<el-table-column prop="tag" label="标签" min-width="100" align="center"/>
<el-table-column prop="view" label="点击量" width="100" align="center"/>
<el-table-column prop="createTime" label="创建时间" width="180" align="center"/>
<el-table-column prop="userId" label="用户ID" width="100" align="center"/>
<el-table-column label="操作" fixed="right" width="180" align="center">
<template #default="{ row }">
<el-button size="small" type="primary" plain @click="edit(row)">编辑</el-button>
<el-popconfirm title="确认删除该房源?" @confirm="del(row.id)">
<template #reference>
<el-button size="small" type="danger" plain>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container" v-if="state.query.total > 0">
<el-pagination
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.query.total"
:page-sizes="[5,10,20,50]"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
<!-- 新增/编辑 对话框 -->
<el-dialog
v-model="state.dialogVisible"
:title="state.formData.id ? '编辑房源' : '新增房源'"
width="700px"
:close-on-click-modal="false"
>
<el-form
:model="state.formData"
ref="formRef"
label-width="120px"
label-position="right"
>
<el-form-item label="用户" prop="userId" :rules="[{ required: true, message: '请选择用户', trigger: 'blur' }]">
<el-select v-model="state.formData.userId" placeholder="请选择用户" filterable clearable>
<el-option
v-for="user in state.userList"
:key="user.id"
:label="`${user.username}(姓名: ${user.nickName}`"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="标题" prop="title" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input v-model="state.formData.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="小区名称" prop="name" :rules="[{ required: true, message: '请输入小区名称', trigger: 'blur' }]">
<el-input v-model="state.formData.name" placeholder="请输入小区名称" />
</el-form-item>
<el-form-item label="城市" prop="city" :rules="[{ required: true, message: '请输入城市', trigger: 'blur' }]">
<el-input v-model="state.formData.city" placeholder="请输入城市" />
</el-form-item>
<el-form-item label="户型" prop="type" :rules="[{ required: true, message: '请输入户型', trigger: 'blur' }]">
<el-select v-model="state.formData.type" placeholder="请选择户型" clearable>
<el-option
v-for="item in getHouseType()"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="面积(㎡)" prop="area">
<el-input v-model="state.formData.area" placeholder="请输入面积" />
</el-form-item>
<el-form-item label="楼层" prop="floor">
<el-input-number v-model="state.formData.floor" :min="0" />
</el-form-item>
<el-form-item label="朝向" prop="face">
<el-select v-model="state.formData.face" placeholder="请选择朝向">
<el-option v-for="item in getFace()" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="装修" prop="decoration">
<el-select v-model="state.formData.decoration" placeholder="请选择装修">
<el-option v-for="item in getDecoration()" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="封面图" prop="image" :rules="[{ required: true, message: '请上传封面图', trigger: 'blur' }]">
<image-upload
class="image-uploader"
:image-url="state.formData.image"
@update:imageUrl="handleImageUrl"
/>
</el-form-item>
<el-form-item label="价格(元/㎡)" prop="price">
<el-input-number v-model="state.formData.price" :min="0" :step="0.01" />
</el-form-item>
<el-form-item label="标签" prop="tag">
<el-input v-model="state.formData.tag" placeholder="请输入标签,用逗号分隔" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="state.formData.sort" :min="1" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
type="textarea"
v-model="state.formData.description"
rows="3"
placeholder="请输入描述"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="state.formData.status" placeholder="请选择状态">
<el-option
v-for="item in state.statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveTransaction">保存</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import type { FormInstance } from 'element-plus'
import { Search, Plus, Picture } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { getDecoration, getFace, getHouseType } from '~/utils/utils'
const formRef = ref<FormInstance>()
const state = reactive({
route: 'sys/house',
loading: false,
list: [] as any[],
dialogVisible: false,
// /
query: {
total: 0,
page: 1,
limit: 5,
title: '',
status: null as number | null,
},
//
statusOptions: [
{ label: '未审核', value: 0 },
{ label: '上架', value: 1 },
{ label: '下架', value: 2 },
{ label: '审核失败', value: 3 },
],
//
formData: {} as Record<string, any>,
userList:[]
})
//
const init = () => {
state.loading = true
adminRequest
.get(`${state.route}/page`, { params: state.query })
.then((res: any) => {
state.list = res.data.list
state.query.total = res.data.total
})
.finally(() => {
state.loading = false
})
adminRequest.get(`sys/user-front/page`, {
params: { limit:999 }
}).then((res:any) => {
state.userList = res.data.list
})
}
//
const handleSearch = () => {
state.query.page = 1
init()
}
//
const openAddDialog = () => {
state.formData = {
title: '',
name: '',
city: '',
type: '',
area: '',
floor: 1,
face: '',
decoration: '',
image: '',
price: 0,
description: '',
tag: '',
hex: '',
sort: 1,
status: 0,
view: 0,
createTime: '',
userId: null
}
state.dialogVisible = true
nextTick(() => formRef.value?.clearValidate())
}
//
const closeDialog = () => {
state.dialogVisible = false
state.formData = {}
}
//
const edit = (row: any) => {
state.formData = { ...row }
state.dialogVisible = true
}
// /
const saveTransaction = () => {
formRef.value?.validate(async valid => {
if (!valid) return
try {
const req = state.formData.id
? adminRequest.put(`${state.route}`, state.formData)
: adminRequest.post(`${state.route}`, state.formData)
await req
ElMessage.success('操作成功')
closeDialog()
init()
} catch {
ElMessage.error('操作失败')
}
})
}
//
const del = async (id: number) => {
try {
await adminRequest.delete(`${state.route}/${id}`)
ElMessage.success('删除成功')
init()
} catch {
ElMessage.error('删除失败')
}
}
//
const handlePageChange = (page: number) => {
state.query.page = page
init()
}
const handleSizeChange = (size: number) => {
state.query.limit = size
init()
}
//
const handleImageUrl = (url: string) => {
state.formData.image = url
}
onMounted(() => {
init()
})
</script>
<style lang="scss" scoped>
.house-management-container {
padding: 20px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.search-area {
margin-bottom: 20px;
.search-form {
display: flex;
align-items: center;
.search-item { margin-right: 16px; }
.search-input { width: 220px; }
.action-buttons { margin-left: auto; display: flex; gap: 8px; }
}
}
.table-area {
.data-table {
width: 100%;
.house-image {
width: 60px; height: 60px; border-radius: 4px;
&:hover { transform: scale(1.05); }
}
}
.pagination-container {
margin-top: 16px;
display: flex; justify-content: flex-end;
}
}
.form-dialog {
:deep(.el-dialog__body) { padding: 20px 30px; }
.image-uploader { width: 100%; }
.dialog-footer { text-align: center; padding-top: 16px; border-top: 1px solid #eee; }
}
</style>
<route lang="json">
{
"meta": {
"layout": "admin",
"title": "房源管理"
}
}
</route>

View File

@ -4,26 +4,19 @@
<div class="quick-access">
<el-row :gutter="20">
<el-col :span="6">
<el-card shadow="hover" class="quick-card" @click="navigateTo('house')">
<el-card shadow="hover" class="quick-card" @click="navigateTo('book')">
<div class="quick-content">
<el-icon :size="30" color="#409EFF"><House /></el-icon>
<span>房源管理</span>
<span>版权管理</span>
</div>
</el-card>
</el-col>
<!-- <el-col :span="6">-->
<!-- <el-card shadow="hover" class="quick-card" @click="navigateTo('transactions')">-->
<!-- <div class="quick-content">-->
<!-- <el-icon :size="30" color="#67C23A"><Money /></el-icon>-->
<!-- <span>交易记录</span>-->
<!-- </div>-->
<!-- </el-card>-->
<!-- </el-col>-->
<el-col :span="6">
<el-card shadow="hover" class="quick-card" @click="navigateTo('house')">
<el-card shadow="hover" class="quick-card" @click="navigateTo('slides')">
<div class="quick-content">
<el-icon :size="30" color="#E6A23C"><DocumentChecked /></el-icon>
<span>认证管理</span>
<span>轮播</span>
</div>
</el-card>
</el-col>
@ -40,62 +33,12 @@
<!-- 数据概览 -->
<el-row :gutter="20" class="mt-20">
<el-col :span="12">
<el-col :span="24">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>系统数据概览</span>
</div>
</template>
<div class="data-overview">
<el-row :gutter="20">
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.totalProperties }}</div>
<div class="data-label">总房源</div>
</div>
</el-col>
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.activeUsers }}</div>
<div class="data-label">活跃用户</div>
</div>
</el-col>
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.totalTransactions }}</div>
<div class="data-label">总交易</div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="mt-20">
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.pendingCertifications }}</div>
<div class="data-label">待认证</div>
</div>
</el-col>
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.availableProperties }}</div>
<div class="data-label">可换住房源</div>
</div>
</el-col>
<el-col :span="8">
<div class="data-item">
<div class="data-value">{{ overviewData.blockHeight }}</div>
<div class="data-label">区块链高度</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>交易趋势</span>
<span>发布趋势</span>
</div>
</template>
<div ref="chart" style="height: 220px;"></div>
@ -103,67 +46,6 @@
</el-col>
</el-row>
<!-- 最近交易和待认证 -->
<el-row :gutter="20" class="mt-20">
<el-col :span="12">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>最近交易记录</span>
<el-button type="text" @click="navigateTo('house')">查看全部</el-button>
</div>
</template>
<el-table :data="recentTransactions" style="width: 100%" height="300">
<el-table-column prop="txHash" label="交易哈希" width="180">
<template #default="{row}">
<el-tooltip :content="row.txHash" placement="top">
<span class="text-ellipsis">{{ shortenHash(row.txHash) }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="property" label="房源" width="120" />
<el-table-column prop="fromUser" label="换出方" width="120" />
<el-table-column prop="toUser" label="换入方" width="120" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{row}">
<el-tag :type="getStatusTagType(row.status)">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>待认证申请</span>
<el-button type="text" @click="navigateTo('house')">查看全部</el-button>
</div>
</template>
<el-table :data="pendingCertifications" style="width: 100%" height="300">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="user" label="用户" width="120" />
<el-table-column prop="type" label="认证类型" width="120">
<template #default="{row}">
<el-tag :type="row.type === '身份认证' ? 'success' : 'warning'">
{{ row.type }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="submitTime" label="提交时间" width="160" />
<el-table-column label="操作" width="120">
<template #default="{row}">
<el-button size="small" @click="handleCertify(row.id, 'approve')">通过</el-button>
<el-button size="small" type="danger" @click="handleCertify(row.id, 'reject')">拒绝</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
@ -181,59 +63,6 @@ import {
const router = useRouter()
//
const overviewData = ref({
totalProperties: 1245,
activeUsers: 876,
totalTransactions: 3421,
pendingCertifications: 23,
availableProperties: 567,
blockHeight: 124567
})
//
const recentTransactions = ref([
{
txHash: '0x4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6',
property: '阳光公寓-302',
fromUser: '用户A',
toUser: '用户B',
date: '2023-05-15',
status: '已完成'
},
{
txHash: '0x5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7',
property: '绿洲小区-1201',
fromUser: '用户C',
toUser: '用户D',
date: '2023-05-14',
status: '进行中'
},
{
txHash: '0x6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8',
property: '湖畔别墅-8',
fromUser: '用户E',
toUser: '用户F',
date: '2023-05-13',
status: '已完成'
},
{
txHash: '0x7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9',
property: '城市花园-502',
fromUser: '用户G',
toUser: '用户H',
date: '2023-05-12',
status: '已取消'
},
{
txHash: '0x8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0',
property: '星空大厦-2103',
fromUser: '用户I',
toUser: '用户J',
date: '2023-05-11',
status: '已完成'
}
])
//
const pendingCertifications = ref([
@ -269,13 +98,6 @@ const pendingCertifications = ref([
}
])
//
const blockchainInfo = ref({
height: 124567,
nodes: 12,
pendingTxs: 8,
status: '运行正常'
})
//
const chart = ref(null)
@ -292,7 +114,7 @@ onMounted(() => {
}
},
legend: {
data: ['换住交易', '认证交易']
data: ['用户量', '发布量']
},
grid: {
left: '3%',

View File

@ -1,211 +0,0 @@
<template>
<div>
<!--查交易记录的按钮-->
<el-button type="primary" round @click="openAddDialog" size="small">添加</el-button>
<el-row>
<el-col :span="24">
<el-form :inline="true" :model="state.query" class="demo-form-inline">
<el-form-item label="文创名称:">
<el-input v-model="state.query.title" placeholder="请输入文创名称" clearable @input="init" />
</el-form-item>
</el-form>
</el-col>
</el-row>
<!--表格-->
<el-row>
<el-col :span="24">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="id" label="编号" align="center" />
<el-table-column prop="title" label="文创名称" align="center" />
<el-table-column prop="view" label="阅读量" align="center" />
<el-table-column prop="sort" label="排序" align="center" />
<!--列表结束-->
<el-table-column label="操作" align="center" >
<template #default="scope">
<el-button @click="info(scope.row.content)" size="small">详情</el-button>
<el-button @click="edit(scope.row)" size="small">编辑</el-button>
<el-button @click="del(scope.row.id)" type="danger" size="small">删除</el-button>
</template>
</el-table-column>
<template v-slot:empty>
<el-empty description="数据去外太空了~" />
</template>
</el-table>
<!-- 分页控件 -->
<el-pagination
v-if="state.query.total > 0"
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.query.total"
background layout="prev, pager, next"
@current-change="handlePageChange"
/>
</el-col>
</el-row>
<!-- 新增/编辑对话框 -->
<el-dialog v-model="state.dialogVisible" title="新增" width="50%">
<el-form
:model="state.formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="文创名称" prop="title" :rules="[{ required: true, message: '请输入文创名称', trigger: 'blur' }]">
<el-input v-model="state.formData.title"/>
</el-form-item>
<el-form-item label="内容" prop="content" :rules="[{ required: true, message: '请编写内容', trigger: 'blur' }]">
<e-editor :content="state.formData.content" @update:content="handleImageUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort" :rules="[{ required: true, message: '请输入排序', trigger: 'blur' }]">
<el-input-number v-model="state.formData.sort" :min="1" :max="10000" />
</el-form-item>
<!--表单结束===============================================================================================================================-->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="state.dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveTransaction(formRef)">保存</el-button>
</div>
</el-dialog>
<el-dialog
v-model="dialogVisible"
title="详情"
width="500"
>
<div v-html="state.info"></div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
//
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const dialogVisible = ref(false)
const state = reactive({
info:"",
route:"sys/topic",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
title:"",
type:1,
},
formData:{}
})
const info = (info:string) => {
console.log(info)
state.info = info
dialogVisible.value = true
}
//
const init = () => {
adminRequest.get(`${state.route}/page`, {
params: state.query
}).then((res:any) => {
state.getList = res.data.list
state.query.total = res.data.total
})
}
//
const openAddDialog = () => {
state.formData = {}
state.dialogVisible = true
}
//
const edit = (row: any) => {
state.formData = row
state.dialogVisible = true
}
//
function handleImageUrl(content: string) {
state.formData.content = content
}
//
const saveTransaction = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
state.formData.type = 1
if (state.formData.id) {
//
adminRequest.put(`${state.route}`, state.formData)
console.log(state.formData)
} else {
//
adminRequest.post(`${state.route}`, state.formData)
}
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
}
})
}
//
const del = async (id: number) => {
try {
await adminRequest.delete(`${state.route}/${id}`)
init()
ElMessage.success("删除成功~")
} catch (error) {
ElMessage.error("删除失败~")
}
}
//
const handlePageChange = (page: number) => {
state.query.page = page
init()
}
//
onMounted(() => {
init()
})
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
:deep(.el-upload:hover) {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar{
width: 178px;
height: 178px;
object-fit: cover;
}
</style>
<route lang="json">
{
"meta": {
"layout": "admin"
}
}
</route>

View File

@ -1,173 +0,0 @@
<!--用户详情-->
<template>
<el-tabs v-model="state.page.orderStatus" @tab-click="handleClick">
<el-tab-pane v-for="status in state.getOrderStatus" :label="status.name" :name="status.status">
<el-table
:data="state.getList"
style="width: 100%"
>
<el-table-column prop="id" label="编号" />
<el-table-column prop="item.title" label="房源名称" />
<el-table-column prop="price" label="图片" >
<template #default="{ row }">
<img :src="row.item.image" style="width: 100px; height: 100px;">
</template>
</el-table-column>
<el-table-column prop="price" label="购买单价" />
<el-table-column prop="quantity" label="购买数量" />
<el-table-column prop="totalAmount" label="成交总价" />
<el-table-column prop="createdTime" label="购买时间" width="220" />
<el-table-column prop="addressDTO.city" label="收货地址" width="220" />
<el-table-column label="操作" min-width="120">
<template #default="scope" >
<el-button link type="primary" size="small" v-if="state.page.orderStatus == 0" @click="update(scope.row,1)">开始发货</el-button>
<el-button link type="primary" size="small" v-if="state.page.orderStatus == 1" @click="update(scope.row,2)">确认收货</el-button>
<el-button link type="primary" size="small" v-if="state.page.orderStatus == 2" @click="update(scope.row,3)">我要评价</el-button>
<el-button link type="primary" size="small" v-if="state.page.orderStatus == 3" @click="del(scope.row.id)">删除订单</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<el-dialog
v-model="dialogVisible"
title="我要评价"
width="500"
>
<el-form
ref="formRef"
style="max-width: 600px"
:model="state.orderForm"
label-width="auto"
label-position="top"
>
<el-form-item
prop="evaluate"
label="评论内容"
:rules="[
{
required: true,
message: '评论内容不能为空',
trigger: 'blur',
},
]"
>
<el-input v-model="state.orderForm.evaluate" :rows="4"
type="textarea"/>
</el-form-item>
<el-form-item
prop="score"
label="评分"
>
<el-rate v-model="state.orderForm.score" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="review(formRef)" >
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { FormInstance, TabsPaneContext } from 'element-plus'
const formRef = ref<FormInstance>()
const state = reactive({
getOrderStatus:[
{name:"未发货",status:0},
{name:"已发货",status:1},
{name:"未评价",status:2},
{name:"已完成",status:3},
],
getList:[],
orderForm:{
score: 0
},
page:{
page:1,
limit:10,
order:'desc',
orderStatus:0,
}
})
const dialogVisible = ref(false)
/**
* 删除订单
* @param id
*/
const del = (id:any) => {
frontRequest.delete(`/sys/order/${id}`).then(res =>{
toast.success("删除成功~")
getList()
})
}
/**
* 获取列表数据
*/
function getList() {
adminRequest.get("/sys/order/page",{ params: state.page}).then(res =>{
state.getList = res.data.list;
})
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
const selectedStatus = state.getOrderStatus[tab.index].status;
state.page.orderStatus = selectedStatus;
getList()
}
const update = (data:any,status:number) => {
console.log(status)
if(status === 3){
dialogVisible.value = true
state.orderForm = {...data,evaluate:''}
state.orderForm.status = 4
return
}
data.orderStatus = status
adminRequest.put("/sys/order",data).then(() =>{
toast.success("修改成功~")
getList()
})
}
onMounted(()=>{
getList()
})
const review = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
updateOrderItemApi(state.orderForm).then(res =>{
toast.success("修改成功~")
dialogVisible.value = false
getList()
})
}
})
}
</script>
<style scoped>
</style>
<route lang="json">
{
"meta": {
"layout": "admin"
}
}
</route>

View File

@ -16,11 +16,11 @@
<el-col :span="24">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="id" label="id"/>
<el-table-column prop="username" label="账号"/>
<el-table-column prop="createDate" label="创建时间"/>
<el-table-column prop="nickName" label="昵称"/>
<el-table-column prop="introduce" label="介绍"/>
<el-table-column prop="phone" label="手机号"/>
<!--列表结束-->
<el-table-column label="操作" align="center" >
<template #default="scope">
@ -65,8 +65,8 @@
<el-form-item label="昵称" prop="nickName" :rules="[{ required: true, message: '请输入昵称', trigger: 'blur' }]">
<el-input v-model="state.formData.nickName"/>
</el-form-item>
<el-form-item label="介绍" prop="introduce" :rules="[{ required: true, message: '请输入介绍', trigger: 'blur' }]">
<el-input v-model="state.formData.introduce"/>
<el-form-item label="手机号" prop="phone" :rules="[{ required: true, message: '请输入手机号', trigger: 'blur' }]">
<el-input v-model="state.formData.phone"/>
</el-form-item>
<!--表单结束===============================================================================================================================-->
</el-form>

View File

@ -1,339 +0,0 @@
<template>
<div class="cart-container">
<h1 class="cart-title">我的购物车</h1>
<div v-if="state.getList.length > 0">
<div
class="cart-item"
v-for="(item, index) in state.getList"
:key="item.id"
>
<img :src="item.item.image" class="item-image" />
<div class="item-details">
<h3>{{ item.item.title }}</h3>
</div>
<div class="quantity-control">
<button
class="quantity-btn"
@click="updateQuantity(item, -1)"
:disabled="item.quantity === 1"
>
<span class="icon"></span>
</button>
<input
type="number"
class="quantity-input"
v-model.number="item.quantity"
min="1"
/>
<button class="quantity-btn" @click="updateQuantity(item, 1)">
<span class="icon">+</span>
</button>
</div>
<div class="item-price">
¥{{ (item.price * item.quantity).toFixed(2) }}
</div>
<button class="delete-btn" @click="removeItem(item.id)">
<span class="trash-icon">🗑</span>
删除
</button>
</div>
<div class="total-section">
<div class="total-price">
总计¥{{ totalPrice.toFixed(2) }}
</div>
<!-- 收货地址选择 -->
<div class="address-section">
<label for="shipping-address">收货地址:</label>
<select id="shipping-address" v-model="selectedAddress">
<option value="">请选择收货地址</option>
<option
v-for="addr in state.addressList"
:key="addr.id"
:value="addr.id"
>
{{addr.recipientName}}- {{ addr.city }}-{{addr.address}}
</option>
</select>
</div>
<button
class="checkout-btn"
:disabled="!selectedAddress"
@click="checkout"
>
<span class="payment-icon">💳</span>
立即结算
</button>
</div>
</div>
<div v-else class="empty-cart">
<span class="cart-icon">🛒</span>
购物车是空的快去选购房源吧
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, computed, onMounted } from "vue";
//
const state = reactive(<any>{
getList: [],
addressList:[]
});
/**
* 计算总价
*/
const totalPrice = computed(() =>
state.getList.reduce((sum:any, item:any) => sum + item.price * item.quantity, 0)
);
/**
* 修改房源数量
*/
const updateQuantity = (data: any, sum: number) => {
data.quantity = Number(data.quantity) + sum;
frontRequest.put("/api/cart", data).then(() => {
init();
});
};
/**
* 删除购物车房源
*/
const removeItem = (id: number) => {
frontRequest.delete(`/api/cart/${id}`).then(() => {
ElMessage.success("删除成功");
init();
});
};
/**
* 初始化购物车数据
*/
async function init() {
try {
const res = await frontRequest.get("/api/cart/page");
const address = await frontRequest.get("/api/address/page",{params:{limit:999}});
state.getList = res.data.list;
state.addressList = address.data.list;
} catch (error) {
console.error("请求出错:", error);
}
}
onMounted(async () => {
await init();
});
//
const selectedAddress = ref("");
//
const checkout = () => {
if (!selectedAddress.value) {
ElMessage.error("请先选择收货地址!");
return;
}
let resList = []
for (let i = 0; i < state.getList.length; i++) {
resList.push({id:state.getList[i].item.id,quantity:state.getList[i].quantity,cartId:state.getList[i].id})
}
let data = {
dtoList:resList,
addressId:selectedAddress.value
}
frontRequest.post("/api/order/cart",data).then(res =>{
init()
ElMessage.success('下单成功~')
})
};
</script>
<style scoped>
/* 海鲜市场风格:清新海洋蓝绿色调 */
.cart-container {
max-width: 1200px;
margin: 2rem auto;
background: linear-gradient(135deg, #e0f7fa, #b2ebf2);
border-radius: 16px;
box-shadow: 0 0 20px rgba(0, 150, 136, 0.2);
padding: 2rem;
border: 1px solid #4db6ac;
}
.cart-title {
font-size: 2.8rem;
color: #006064;
margin-bottom: 2rem;
font-family: 'Arial', sans-serif;
letter-spacing: 2px;
text-align: center;
}
.cart-item {
display: grid;
grid-template-columns: 100px 2fr 1fr 1fr 120px;
align-items: center;
gap: 2rem;
padding: 1.5rem;
margin: 1.5rem 0;
border-radius: 12px;
background: #ffffff;
border: 1px solid #b2dfdb;
box-shadow: 0 2px 8px rgba(0, 150, 136, 0.15);
transition: transform 0.3s, box-shadow 0.3s;
}
.cart-item:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 150, 136, 0.3);
}
.item-image {
border-radius: 8px;
border: 2px solid #4db6ac;
}
.item-details h3 {
color: #006064;
margin: 0;
}
.quantity-control {
display: flex;
align-items: center;
gap: 0.5rem;
}
.quantity-btn {
background: #4db6ac;
color: #fff;
border: none;
padding: 0.5rem 0.8rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.quantity-btn:disabled {
background: #b2dfdb;
cursor: not-allowed;
}
.quantity-btn:hover:enabled {
background: #00897b;
}
.quantity-input {
background: #e0f2f1;
border: 1px solid #4db6ac;
color: #006064;
border-radius: 4px;
padding: 0.3rem;
text-align: center;
width: 50px;
}
.item-price {
color: #006064;
font-size: 1.2rem;
font-weight: bold;
}
.delete-btn {
background: #ff8a80;
color: #fff;
border: none;
padding: 0.5rem 0.8rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.delete-btn:hover {
background: #ff5252;
}
.total-section {
background: #ffffff;
padding: 1rem;
border-radius: 12px;
margin-top: 2rem;
border: 1px solid #b2dfdb;
text-align: right;
}
.total-price {
color: #006064;
font-size: 2rem;
margin-bottom: 1rem;
}
.address-section {
margin-bottom: 1rem;
}
.address-section label {
color: #006064;
margin-right: 0.5rem;
}
.address-section select {
background: #e0f2f1;
border: 1px solid #4db6ac;
color: #006064;
padding: 0.6rem;
border-radius: 4px;
}
.checkout-btn {
background: #4db6ac;
color: #fff;
font-weight: bold;
padding: 0.8rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.checkout-btn:disabled {
background: #b2dfdb;
cursor: not-allowed;
}
.checkout-btn:hover:enabled {
background: #00897b;
}
.empty-cart {
color: #006064;
font-size: 1.5rem;
text-align: center;
margin-top: 2rem;
}
/* 移动端响应式 */
@media (max-width: 768px) {
.cart-item {
grid-template-columns: 1fr;
padding: 2rem;
}
.item-image {
width: 80%;
margin: 0 auto;
}
.delete-btn {
margin-top: 1rem;
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -1,90 +0,0 @@
<!--用户详情-->
<template>
<el-tabs v-model="state.activeName" >
<el-tab-pane v-for="status in state.getOrderStatus" :label="status.name" :name="status.status">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="title" label="标题" align="center" />
<el-table-column prop="name" label="小区名称" align="center" />
<el-table-column prop="largePic" label="图片" align="center" >
<template #default="{ row }">
<!-- 点击图片后显示弹框预览 -->
<el-image
:src="row.image"
fit="cover"
:preview-src-list="[row.image]"
:preview-teleported="true"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="价格" align="center" />
<el-table-column prop="area" label="面积" align="center" />
<el-table-column prop="type" label="房屋类型" align="center" />
<el-table-column prop="city" label="城市" align="center" width="160"/>
<!--列表结束-->
<el-table-column label="操作" align="center" width="160" >
<template #default="scope">
<el-button @click="del(scope.row.id)" type="danger" size="small">取消</el-button>
<el-button @click="to(scope.row.id)" size="small">去换住</el-button>
</template>
</el-table-column>
<template v-slot:empty>
<el-empty description="数据去外太空了~" />
</template>
</el-table>
</el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
import { reactive } from 'vue'
const state = reactive({
activeName:1,
getOrderStatus:[
{name:"全部",status:1},
],
getList:[]
})
/**
* 跳转
*/
const to = (id:number) => {
router.push(`/info/${id}`)
}
/**
* 获取列表数据
*/
function init() {
frontRequest.get("/api/item/list").then(res =>{
state.getList = res.data
})
}
const del = (id:number) => {
frontRequest.post("/api/behavior/delete",{itemId:id,type:1}).then(res =>{
ElMessage.success("收藏成功")
init()
})
init()
}
onMounted(()=>{
init()
})
</script>
<style scoped>
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -1,61 +1,287 @@
<template>
<!--轮播图-->
<el-row :gutter="20">
<div class="blockchain-library-container">
<el-col :span="24">
<div class="h-300px">
<carousel></carousel>
</div>
</el-col>
</el-row>
<div style="padding-top: 20px"></div>
<!-- 推荐房源列表-->
<item :get-list=state.getList></item>
<!-- 主要内容区 -->
<main class="library-main">
<!-- 轮播图区域 -->
<section class="banner-section">
<el-carousel height="360px" :interval="5000" arrow="always">
<el-carousel-item v-for="(item, index) in state.banners" :key="index">
<el-image
:src="item.path"
fit="cover"
class="banner-image"
:preview-src-list="[item.path]"
/>
<div class="banner-content">
<h3 class="banner-title">{{ item.name }}</h3>
</div>
</el-carousel-item>
</el-carousel>
</section>
<!-- 新书上架 -->
<section class="new-books-section">
<div class="section-header">
<h2 class="section-title">
<el-icon><Calendar /></el-icon>
<span>最新发布</span>
</h2>
<el-link type="primary" :underline="false" class="view-all" @click="to('/list')">
更多 <el-icon><ArrowRight /></el-icon>
</el-link>
</div>
<div class="books-grid">
<div
v-for="book in state.newBooks"
:key="book.id"
class="book-card"
>
<div class="book-cover">
<el-image
:src="book.image"
fit="cover"
class="cover-image"
:preview-src-list="[book.image]"
/>
<div class="book-badge" v-if="book.isNew">
<span>NEW</span>
</div>
</div>
<div class="book-info" @click="to(`/info/${book.id}`)">
<h3 class="book-title" :title="book.title">{{ book.title }}</h3>
<p class="book-author">{{ book.author }}</p>
<div class="book-meta">
<span class="book-price">¥{{ book.price }}</span>
<el-tag size="small" type="success" class="blockchain-tag">
<el-icon><Connection /></el-icon>
链上版权
</el-tag>
</div>
</div>
</div>
</div>
</section>
</main>
</div>
</template>
<script setup lang="ts">
import Carousel from '~/components/front/carousel.vue'
import Item from '~/components/front/item.vue'
import { reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const state = reactive({
getList: [],
commit: [],
import {
ArrowRight,
Calendar,
Connection
} from '@element-plus/icons-vue'
const state = reactive(<any>{
newBooks:[],
banners:[]
})
// frontRequest.get("/api/user/userInfo").then(response =>{
// user.frontUserInfo = response.data
// })
onMounted(()=>{
frontRequest.get("/api/item/score").then(res =>{
state.getList = res.data
})
frontRequest.get("/api/item/commit").then(res =>{
state.commit = res.data
})
})
/**
* 跳转
* 初始化
*/
const to = (id:number) => {
router.push(`/info/${id}`)
function init() {
frontRequest.get("/api/book/page",{
params: { limit:5,}
}).then((res) => {
state.newBooks = res.data.list
})
frontRequest.get("/api/slides/page",{
params: { limit:5,}
}).then((res) => {
state.banners = res.data.list
})
}
onMounted(() => {
init()
})
function to(path:string) {
router.push(path)
}
</script>
<style scoped>
.hot {
background-color: rgba(0, 0, 0, 0.2); /* 更浅的黑色 */
border-radius: 10px; /* 四周圆弧,值可以根据需要调整 */
/* 全局样式 */
.blockchain-library-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #f8f9fa;
color: #333;
font-family: 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
}
:deep(.el-button){
color: #2e191e;
font-weight: 700;
/* 主要内容区样式 */
.library-main {
flex: 1;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
width: 100%;
}
:deep(.el-button:hover) {
background-color: black;
/* 轮播图样式 */
.banner-section {
margin-bottom: 40px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 30px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
color: white;
}
.banner-title {
font-size: 28px;
margin-bottom: 10px;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
}
.banner-desc {
font-size: 16px;
margin-bottom: 15px;
max-width: 60%;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.section-title {
display: flex;
align-items: center;
font-size: 22px;
color: #2c3e50;
}
.section-title .el-icon {
margin-right: 10px;
color: #3498db;
}
.view-all {
font-size: 14px;
display: flex;
align-items: center;
}
.view-all .el-icon {
margin-left: 5px;
}
/* 新书区域样式 */
.new-books-section {
margin-bottom: 40px;
}
.books-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
}
.book-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.book-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: #e74c3c;
color: white;
padding: 3px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.book-info {
padding: 15px;
cursor: pointer;
}
.book-title {
font-size: 16px;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.book-author {
font-size: 14px;
color: #7f8c8d;
margin-bottom: 10px;
}
.book-meta {
display: flex;
align-items: center;
justify-content: space-between;
}
.book-price {
font-size: 18px;
font-weight: bold;
color: #e74c3c;
}
.blockchain-tag {
background-color: #e8f4fc;
color: #3498db;
border: none;
}
.book-cover {
margin-right: 10px;
flex-shrink: 0;
}
.book-title {
font-size: 16px;
margin-bottom: 5px;
}
.book-author {
font-size: 14px;
color: #7f8c8d;
margin-bottom: 8px;
}
.link-column h4 {
font-size: 18px;
margin-bottom: 20px;
color: #fff;
}
</style>
<route lang="json">

View File

@ -3,18 +3,10 @@
<!-- 头部区块信息 -->
<div class="blockchain-header">
<div class="nav-logo">
<span class="logo-icon"></span>
<span class="logo-text">链家溯源 · 房源详情</span>
</div>
<div class="blockchain-breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>房源详情</el-breadcrumb-item>
<el-breadcrumb-item>{{ state.info.title || '加载中...' }}</el-breadcrumb-item>
</el-breadcrumb>
<span class="logo-icon">📚</span>
<span class="logo-text">区块链版权</span>
</div>
</div>
<!-- 主要内容区 -->
<div class="detail-content">
<el-row :gutter="30">
@ -32,32 +24,52 @@
<span class="badge-text">链上验证</span>
</div>
<div class="image-tags">
<el-tag v-if="state.info.tag" type="info" class="location-tag">
<i class="el-icon-location-information"></i>
{{ state.info.tag }}
<el-tag v-if="state.info.language" type="info" class="language-tag">
<i class="el-icon-chat-line-round"></i>
{{ state.info.language }}
</el-tag>
<el-tag v-if="state.info.type" type="success" class="type-tag">
{{ state.info.type }}
<el-tag v-if="state.info.status" :type="getStatusTagType(state.info.status)" class="status-tag">
{{ state.info.status }}
</el-tag>
</div>
</div>
<!-- 电子文件下载区 -->
<div class="file-section" v-if="state.info.file">
<el-button
type="primary"
class="download-button"
@click="handleDownload"
>
<el-icon><Download /></el-icon>
下载电子文件
</el-button>
<div class="file-info">
<el-icon><Document /></el-icon>
<span>已上传电子版权文件</span>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="14">
<!-- 信息展示区 -->
<div class="info-section">
<h1 class="property-title">{{ state.info.title }}</h1>
<h1 class="property-title">{{ state.info.title }}</h1>
<div class="author-info">
<el-icon><User /></el-icon>
<span>{{ state.info.author }}</span>
</div>
<div class="property-meta">
<div class="meta-item">
<span class="meta-label">房源地址:</span>
<span class="meta-value">{{ state.info.city || '暂无' }}</span>
<span class="meta-label">ISBN编号:</span>
<span class="meta-value">{{ state.info.isbn || '暂无' }}</span>
</div>
<div class="price-section">
<span class="price-label">价格:</span>
<span class="price-value">{{ state.info.price || '0' }}</span>
<span class="price-unit">/平方</span>
<span class="price-label">:</span>
<span class="price-value">{{ state.info.price || '0' }}</span>
<span class="price-unit"></span>
<el-tag type="success" size="small" class="blockchain-tag">
<span class="tag-icon"></span>
<span>链上价格</span>
@ -66,25 +78,31 @@
<div class="verification-badge">
<el-icon><SuccessFilled /></el-icon>
<span>房源信息已通过区块链验证</span>
<span>版权信息已通过区块链验证</span>
</div>
<div class="property-details">
<div class="detail-item">
<span class="detail-label">小区名称:</span>
<span class="detail-value">{{ state.info.name || '暂无' }}</span>
<span class="detail-label">出版社:</span>
<span class="detail-value">{{ state.info.publisher || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">朝向:</span>
<span class="detail-value">{{ state.info.face || '暂无' }}</span>
<span class="detail-label">出版日期:</span>
<span class="detail-value">{{ formatDate(state.info.publishDate) || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">面积:</span>
<span class="detail-value">{{ state.info.area || '0' }} 平方</span>
<span class="detail-label">版权持有人:</span>
<span class="detail-value">{{ state.info.copyrightOwner || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">楼层:</span>
<span class="detail-value">{{ state.info.floor || '0' }}</span>
<span class="detail-label">版权期限:</span>
<span class="detail-value">
{{ state.info.copyrightStartYear }} - {{ state.info.copyrightEndYear || '至今' }}
</span>
</div>
<div class="detail-item">
<span class="detail-label">版次:</span>
<span class="detail-value">{{ state.info.edition || '暂无' }}</span>
</div>
</div>
@ -94,9 +112,9 @@
<el-icon><Connection /></el-icon>
<span>区块链编号</span>
</div>
<el-tooltip :content="state.info.hex || '暂无'" placement="top">
<el-tooltip :content="state.info.hex || '暂无'" placement="top">
<div class="hash-value">
{{ shortenHash(state.info.hex) || '暂无区块链信息' }}
{{ shortenHash(state.info.hex) || '暂无区块链信息' }}
</div>
</el-tooltip>
<el-button
@ -105,123 +123,17 @@
class="view-transaction"
v-if="state.info.hex"
>
查看交易记录 <el-icon><ArrowRight /></el-icon>
</el-button>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button
type="primary"
class="buy-button"
@click="handleBuy"
v-if="state.info.price"
>
<el-icon><Refresh /></el-icon>
换住
</el-button>
<el-button
:type="state.info.isFavorite ? 'danger' : 'info'"
class="favorite-button"
@click="handleFavorite"
>
<el-icon>{{ state.info.isFavorite ? 'StarFilled' : 'Star' }}</el-icon>
{{ state.info.isFavorite ? '已收藏' : '收藏房源' }}
</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
<!-- 购买抽屉 -->
<el-drawer
v-model="buyDrawer"
title="区块链换住确认"
size="40%"
>
<div class="drawer-content">
<el-form
ref="formRef"
:model="state.buyForm"
label-position="top"
class="blockchain-form"
>
<el-form-item label="选择您需要交换的房源"
:rules="[
{
required: true,
message: '请选择您需要交换的房源',
trigger: 'blur',
},
]" prop="userHouseId">
<el-select
style="width:100%"
v-model="state.buyForm.userHouseId"
placeholder="请选择"
clearable
>
<el-option v-for="item in state.houseList"
:label="`${item.name}-${item.type}`"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="房源名称" prop="title">
<el-input v-model="state.buyForm.title" disabled>
<template #prefix>
<el-icon><OfficeBuilding /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="房源单价(元)" prop="price">
<el-input v-model="state.buyForm.price" disabled>
<template #prefix>
<el-icon><Coin /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="总价(元)" prop="totalAmount">
<el-input v-model="state.buyForm.totalAmount" disabled>
<template #prefix>
<el-icon><Money /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="用户姓名" prop="title">
<el-input v-model="state.userInfo.nickName" disabled>
<template #prefix>
<el-icon><User /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="联系方式" prop="title">
<el-input v-model="state.userInfo.introduce" disabled>
</el-input>
</el-form-item>
<el-form-item label="区块链确认" prop="blockchain">
<el-alert
title="此交易将通过区块链网络确认,交易记录将永久保存"
type="info"
:closable="false"
show-icon
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="confirm-button"
@click="buySubmitForm(formRef)"
>
<el-icon><Checked /></el-icon>
确认申请交换
</el-button>
</el-form-item>
</el-form>
</div>
</el-drawer>
</div>
</template>
@ -231,11 +143,12 @@ import { useRoute } from 'vue-router'
import {
SuccessFilled,
ShoppingCart,
Goods,
Download,
Document,
Notebook,
User,
Connection,
ArrowRight,
OfficeBuilding,
Coin,
Money,
Checked
@ -256,31 +169,58 @@ const state = reactive(<any>{
price: 0,
totalAmount: 0
},
cateList: [],
userInfo:{},
houseList:[]
userInfo: {}
})
/**
* 格式化日期
*/
function formatDate(dateStr: string) {
if (!dateStr) return ''
const date = new Date(dateStr)
return date.toLocaleDateString()
}
/**
* 获取状态标签类型
*/
function getStatusTagType(status: string) {
switch(status) {
case '已通过': return 'success'
case '审核中': return 'warning'
case '已拒绝': return 'danger'
default: return 'info'
}
}
/**
* 缩短哈希显示
*/
function shortenHash(hash: string) {
if (!hash) return ''
return hash.substring(0, 6) + '...' + hash.substring(hash.length - 4)
return hash.substring(0, 6) + '...' + hash.substring(hash.length - 4)
}
/**
* 更新总价
*/
function updateTotalAmount() {
state.buyForm.totalAmount = (state.buyForm.price * state.buyForm.quantity).toFixed(2)
}
/**
* 初始化数据
*/
function init() {
frontRequest.get(`/api/item/${itemId}`).then(response => {
state.info = response.data
})
frontRequest.get(`api/address/page`).then(response => {
state.cateList = response.data.list
frontRequest.get(`/api/book/${itemId}`).then(response => {
state.info = response.data
state.buyForm.title = state.info.title
state.buyForm.price = state.info.price
updateTotalAmount()
})
frontRequest.get("/api/item/push").then(res =>{
state.houseList = res.data
frontRequest.get("/api/user/userInfo").then(res => {
state.userInfo = res.data
})
}
@ -288,8 +228,8 @@ function init() {
* 用户收藏
*/
function handleFavorite() {
if (state.info.isFavorite) {
frontRequest.post("/api/behavior/delete", {itemId: itemId, type: 1}).then(res => {
if (state.info.isFavorite) {
frontRequest.post("/api/behavior/delete", {itemId: itemId, type: 1}).then(res => {
ElMessage.success({
message: '已取消收藏',
offset: 80
@ -297,7 +237,7 @@ function handleFavorite() {
init()
})
} else {
frontRequest.post("/api/behavior", {itemId: itemId, type: 1}).then(res => {
frontRequest.post("/api/behavior", {itemId: itemId, type: 1}).then(res => {
ElMessage.success({
message: '收藏成功',
offset: 80
@ -308,47 +248,44 @@ function handleFavorite() {
}
/**
* 购买按钮提交
* 下载电子文件
*/
const buySubmitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
frontRequest.post("/api/order",{
itemId:itemId, //
userHouseId:state.buyForm.userHouseId, //
}).then(() => {
ElMessage.success({
message: '换住信息已提交区块链网络',
offset: 80
})
buyDrawer.value = false
})
}
})
function handleDownload() {
if (!state.info.file) {
ElMessage.warning(' 该图书暂无电子文件')
return
}
window.open(state.info.file, '_blank')
}
/**
* 购买按钮
*/
const handleBuy = () => {
//
frontRequest.get("/api/user/userInfo").then(res =>{
if (state.info.userId === res.data.id){
ElMessage.warning("自己房源不能换住")
return
}
state.userInfo = res.data
buyDrawer.value = true
state.buyForm.title = state.info.title
state.buyForm.id = state.info.id
state.buyForm.price = state.info.price
updateTotalAmount()
})
function handlePurchase() {
if (state.info.userId === state.userInfo.id) {
ElMessage.warning(" 不能购买自己的图书版权")
return
}
buyDrawer.value = true
}
/**
* 购买按钮提交
*/
const buySubmitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
frontRequest.post("/api/order", state.buyForm).then(() => {
ElMessage.success({
message: '版权购买信息已提交区块链网络',
offset: 80
})
buyDrawer.value = false
})
}
})
}
onMounted(() => {
init()
@ -384,7 +321,7 @@ onMounted(() => {
.logo-icon {
margin-right: 10px;
font-size: 24px;
color: #1abc9c;
color: #3498db;
}
.blockchain-breadcrumb {
@ -405,6 +342,7 @@ onMounted(() => {
height: 400px;
display: block;
transition: all 0.3s;
background-color: #f5f7fa;
}
.main-image:hover {
@ -415,7 +353,7 @@ onMounted(() => {
position: absolute;
top: 15px;
right: 15px;
background-color: rgba(26, 188, 156, 0.9);
background-color: rgba(52, 152, 219, 0.9);
color: white;
padding: 6px 12px;
border-radius: 4px;
@ -439,18 +377,43 @@ onMounted(() => {
z-index: 2;
}
.location-tag {
background-color: rgba(52, 152, 219, 0.9);
.language-tag {
background-color: rgba(41, 128, 185, 0.9);
color: white;
border: none;
}
.type-tag {
background-color: rgba(46, 204, 113, 0.9);
.status-tag {
background-color: rgba(39, 174, 96, 0.9);
color: white;
border: none;
}
/* 电子文件下载区 */
.file-section {
margin-top: 20px;
text-align: center;
}
.download-button {
width: 100%;
height: 48px;
font-size: 16px;
}
.file-info {
margin-top: 10px;
color: #7f8c8d;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
.file-info .el-icon {
margin-right: 5px;
}
/* 信息区域 */
.info-section {
padding: 0 15px;
@ -460,10 +423,22 @@ onMounted(() => {
font-size: 24px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 20px;
margin-bottom: 10px;
line-height: 1.3;
}
.author-info {
display: flex;
align-items: center;
color: #7f8c8d;
margin-bottom: 20px;
font-size: 16px;
}
.author-info .el-icon {
margin-right: 8px;
}
.property-meta {
margin-bottom: 30px;
}
@ -478,7 +453,7 @@ onMounted(() => {
.meta-label {
color: #7f8c8d;
margin-right: 10px;
min-width: 80px;
min-width: 100px;
}
.meta-value {
@ -556,7 +531,7 @@ onMounted(() => {
.detail-label {
color: #7f8c8d;
margin-right: 8px;
min-width: 50px;
min-width: 80px;
}
.detail-value {
@ -570,7 +545,7 @@ onMounted(() => {
padding: 15px;
border-radius: 6px;
margin-bottom: 30px;
border-left: 4px solid #1abc9c;
border-left: 4px solid #3498db;
}
.hash-title {
@ -583,7 +558,7 @@ onMounted(() => {
.hash-title .el-icon {
margin-right: 8px;
color: #1abc9c;
color: #3498db;
}
.hash-value {
@ -604,7 +579,7 @@ onMounted(() => {
}
.view-transaction {
color: #1abc9c !important;
color: #3498db !important;
font-size: 13px;
}
@ -621,7 +596,7 @@ onMounted(() => {
}
.buy-button {
background-color: #e74c3c;
background-color: #3498db;
border: none;
height: 48px;
font-size: 16px;
@ -631,32 +606,13 @@ onMounted(() => {
}
.buy-button:hover {
background-color: #c0392b;
background-color: #2980b9;
}
.buy-button .el-icon {
margin-right: 8px;
}
.cart-button {
height: 48px;
font-size: 16px;
font-weight: 500;
padding: 0 25px;
flex: 1;
border: 1px solid #1abc9c;
color: #1abc9c;
background-color: rgba(26, 188, 156, 0.1);
}
.cart-button:hover {
background-color: rgba(26, 188, 156, 0.2);
}
.cart-button .el-icon {
margin-right: 8px;
}
.favorite-button {
height: 48px;
font-size: 16px;
@ -689,6 +645,12 @@ onMounted(() => {
height: 48px;
font-size: 16px;
margin-top: 20px;
background-color: #3498db;
border: none;
}
.confirm-button:hover {
background-color: #2980b9;
}
.confirm-button .el-icon {
@ -704,7 +666,6 @@ onMounted(() => {
flex-direction: column;
}
.buy-button,
.cart-button,
.favorite-button {
width: 100%;
}

View File

@ -5,7 +5,7 @@
<el-col :span="24" class="search-container">
<el-input
v-model="state.page.title"
placeholder="搜索房源信息"
placeholder="搜索版权信息"
class="search-input"
clearable
@input="init"
@ -16,24 +16,6 @@
</el-input>
</el-col>
</el-row>
<!-- &lt;!&ndash; 品牌标签云 &ndash;&gt;-->
<!-- <el-row class="brand-container">-->
<!-- <el-col :span="24">-->
<!-- <div class="brand-list">-->
<!-- <el-tag-->
<!-- v-for="brand in state.brands"-->
<!-- :key="brand"-->
<!-- class="brand-item"-->
<!-- effect="plain"-->
<!-- round-->
<!-- >-->
<!-- {{ brand.name }}-->
<!-- </el-tag>-->
<!-- </div>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- 房源卡片 -->
<el-row class="product-container">
<el-col :span="24">
@ -51,9 +33,9 @@
</div>
<div class="product-info">
<div class="product-title">{{ item.title }}</div>
<div class="product-sub">{{ item.name}}/{{item.face}}/{{ item.type}}/{{item.face}}/{{ item.area}}平方</div>
<div class="product-sub">{{ item.edition}}/{{item.language}}/{{ item.publisher}}/{{item.copyrightStartYear}}/{{ item.copyrightEndYear}}</div>
<div class="price-container">
<span class="current-price">{{ item.price }}/平方</span>
<span class="current-price">{{ item.publisher }}</span>
<el-tag type="success" size="small">区块溯源</el-tag>
</div>
</div>
@ -84,50 +66,23 @@ const state = reactive(<any>{
page: {
page: 1,
title: '',
limit:12
limit:8
},
getList: [{
"id": "9787559485054",
"title": "房东急售近地铁 近医院 诚心出售",
"name": "学府小区",
"city": "深圳",
"type": "学区房",
"area": "73.0",
"floor": 6,
"face": "西",
"decoration": "简装修",
"image": "http://localhost:18081/360x312c (1).webp",
"price": 6600,
"description": "<p>临近重点小学,交通便利</p>",
"tag": "深圳",
"hex": "0x389d64c05c566eafd726cf75af4b474250aefea760969b9ed04429ef520eb7f8730c176e2e39a429",
"sort": 3,
"status": 1,
"view": 8,
"createTime": "2025-05-19 16:42:57",
"userId": "1863920777825390593",
"user": null,
"orderEntityList": [],
"isFavorite": null
}],
getList: [],
brands:[]
})
/**
* 初始化
*/
function init() {
frontRequest.get("/api/item/page",{
frontRequest.get("/api/book/page",{
params: state.page
}).then((res) => {
state.getList = res.data.list
state.total = res.data.total
})
frontRequest.get("/api/categories/page").then((res) => {
state.brands = res.data.list
})
}
/**
* 分页
@ -136,7 +91,6 @@ const handleCurrentChange = (val: number) => {
state.page.page = val
init()
}
/**
* 跳转
*/
@ -162,25 +116,7 @@ onMounted(() => {
border-radius: 24px;
overflow: hidden;
}
/* 品牌标签样式 */
.brand-container {
padding: 0 20px 20px;
}
.brand-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.brand-item {
padding: 8px 20px;
transition: all 0.3s;
cursor: pointer;
}
.brand-item:hover {
transform: translateY(-2px);
background-color: #fef0f0;
}
/* 房源卡片样式 */
.product-container {
padding: 0 20px;
@ -261,7 +197,6 @@ onMounted(() => {
justify-content: center;
}
</style>
<route lang="json">
{
"meta": {

View File

@ -49,7 +49,7 @@
class="order-table"
>
<el-table-column prop="user.nickName" label="申请人名称" align="center"/>
<el-table-column prop="user.introduce" label="申请人联系方式" align="center"/>
<el-table-column prop="user.phone" label="申请人联系方式" align="center"/>
<el-table-column prop="item.name" label="申请人房源名称" align="center"/>
<el-table-column label="申请人房源图片" >
<template #default="{ row }">

View File

@ -1,107 +1,304 @@
<template>
<el-form :inline="true" :model="state.query" class="demo-form-inline mb-4">
<el-form-item label="书籍名称">
<el-input v-model="state.query.title" placeholder="书籍名称" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="init()">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-col :span="24">
<el-form :inline="true" :model="state.query" class="demo-form-inline">
<el-form-item label="房屋名称">
<el-input v-model="state.query.title" placeholder="房屋名称" clearable />
</el-form-item>
<el-form-item label="选择状态">
<el-select
style="width: 120px"
v-model="state.query.status"
placeholder="请选择"
clearable
<el-tabs v-model="state.query.status" @tab-click="handleClick">
<el-tab-pane v-for="status in state.status" :label="status.name" :name="status.status">
<el-table
v-loading="state.loading"
:data="state.list"
stripe
border
class="data-table"
empty-text="暂无数据"
>
<el-option label="未审核" value="未审核" />
<el-option label="已同意" value="已同意" />
<el-option label="已拒绝 " value="已拒绝 " />
<el-option label="已评价 " value="已评价 " />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="init()">查询</el-button>
</el-form-item>
</el-form>
<el-table-column label="封面图" width="100" align="center">
<template #default="{ row }">
<el-image :src="row.image" fit="cover" class="book-image" />
</template>
</el-table-column>
<el-table-column prop="isbn" label="ISBN" align="center" />
<el-table-column prop="title" label="书名" align="center" />
<el-table-column prop="author" label="作者" align="center" />
<el-table-column prop="publisher" label="出版社" align="center" />
<el-table-column prop="publishDate" label="出版日期" align="center" />
<el-table-column prop="copyrightOwner" label="版权持有人" align="center" />
<el-table-column prop="copyrightStartYear" label="版权开始" align="center" />
<el-table-column prop="copyrightEndYear" label="版权到期" align="center" />
<el-table-column prop="edition" label="版次" align="center" />
<el-table-column prop="price" label="定价(元)" align="center" />
<el-table-column label="状态" align="center" width="100">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="240" align="center">
<template #default="{ row }">
<el-button size="small" type="primary" plain @click="showDetail(row)">详情</el-button>
<el-button size="small" type="success" plain @click="showCertificate(row)">查看凭证</el-button>
<el-button size="small" type="warning" plain @click="downloadFile(row)">下载文件</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-table
v-loading="state.loading"
:data="state.list"
stripe
border
class="data-table"
empty-text="暂无数据"
>
<el-table-column prop="id" label="换住编号" width="80" align="center"/>
<el-table-column prop="title" label="标题" min-width="120" align="center"/>
<el-table-column prop="name" label="小区名称" min-width="120" align="center"/>
<el-table-column prop="city" label="城市" width="100" align="center"/>
<el-table-column prop="type" label="户型" width="120" align="center"/>
<el-table-column prop="area" label="面积" width="100" align="center"/>
<el-table-column prop="floor" label="楼层" width="80" align="center"/>
<el-table-column prop="face" label="朝向" width="100" align="center"/>
<el-table-column prop="decoration" label="装修" width="100" align="center"/>
<el-table-column label="封面图" width="100" align="center">
<template #default="{ row }">
<el-image
:src="row.image"
fit="cover"
class="house-image"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="价格 (元/㎡)" width="120" align="center"/>
<el-table-column prop="tag" label="标签" min-width="100" align="center"/>
<el-table-column prop="view" label="点击量" width="100" align="center"/>
<el-table-column prop="createTime" label="创建时间" width="180" align="center"/>
<el-table-column prop="userId" label="用户ID" width="100" align="center"/>
<el-table-column label="操作" fixed="right" width="180" align="center">
<template #default="{ row }">
<el-button size="small" type="primary" plain @click="edit(row)">编辑</el-button>
<el-popconfirm title="确认删除该房源?" @confirm="del(row.id)">
<template #reference>
<el-button size="small" type="danger" plain>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<el-pagination
class="pagination"
background
layout="prev, pager, next, jumper, ->, total"
:current-page="state.query.page"
:page-size="state.query.limit"
:total="state.total"
@current-change="handlePageChange"
/>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive(<any>{
<!-- 详情对话框 -->
<el-dialog v-model="state.detailVisible" title="书籍详情" width="50%">
<el-descriptions :column="2" border>
<el-descriptions-item label="封面图">
<el-image :src="state.currentBook.image" fit="cover" class="detail-image" />
</el-descriptions-item>
<el-descriptions-item label="ISBN">{{ state.currentBook.isbn || '-' }}</el-descriptions-item>
<el-descriptions-item label="书名">{{ state.currentBook.title || '-' }}</el-descriptions-item>
<el-descriptions-item label="作者">{{ state.currentBook.author || '-' }}</el-descriptions-item>
<el-descriptions-item label="出版社">{{ state.currentBook.publisher || '-' }}</el-descriptions-item>
<el-descriptions-item label="出版日期">{{ state.currentBook.publishDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权持有人">{{ state.currentBook.copyrightOwner || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权开始">{{ state.currentBook.copyrightStartYear || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权到期">{{ state.currentBook.copyrightEndYear || '-' }}</el-descriptions-item>
<el-descriptions-item label="版次">{{ state.currentBook.edition || '-' }}</el-descriptions-item>
<el-descriptions-item label="定价">{{ state.currentBook.price ? `${state.currentBook.price}` : '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusTagType(state.currentBook.status)">{{ state.currentBook.status || '-' }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="简介" :span="2">
<div class="book-description">{{ state.currentBook.description || '暂无简介' }}</div>
</el-descriptions-item>
</el-descriptions>
<template #footer>
<span class="dialog-footer">
<el-button @click="state.detailVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- 凭证对话框 -->
<el-dialog v-model="state.certificateVisible" title="版权凭证" width="60%">
<div class="certificate-container">
<div class="certificate-content">
<pre>{{ state.currentCertificate }}</pre>
</div>
<el-button type="primary" @click="copyCertificate" class="copy-btn">复制凭证</el-button>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import { ElMessage, TabsPaneContext } from 'element-plus'
const state = reactive<any>({
status:[
{name:"全部",status:null},
{name:"未审核",status:"未审核"},
{name:"已同意",status:"已同意"},
{name:"已拒绝",status:"已拒绝"},
],
loading: false,
list: [],
//
total: 0,
query: {
title: '',
status: null,
page: 1,
limit: 5,
},
//
activeTab: 'all',
//
detailVisible: false,
currentBook: {},
//
certificateVisible: false,
currentCertificate: ''
})
function init() {
state.loading = true
frontRequest.get("/api/item/push",{params:state.query}).then(res =>{
state.list = res.data
frontRequest.get('/api/book/page', { params: state.query }).then((res) => {
state.list = res.data.list
state.total = res.data.total
state.loading = false
})
}
onMounted(()=>{
function handlePageChange(page: number) {
state.query.page = page
init()
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
state.query.status = state.status[tab.index].status;
init();
}
function resetQuery() {
state.query.title = ''
state.query.status = null
init()
}
//
function getStatusTagType(status: string) {
switch (status) {
case '已同意':
return 'success'
case '已拒绝':
return 'danger'
case '未审核':
return 'warning'
default:
return 'info'
}
}
//
function showDetail(row: any) {
state.currentBook = { ...row }
state.detailVisible = true
}
//
function showCertificate(row: any) {
state.currentCertificate = row.hex || '暂无凭证数据'
state.certificateVisible = true
}
//
function copyCertificate() {
navigator.clipboard.writeText(state.currentCertificate)
.then(() => {
ElMessage.success('凭证已复制到剪贴板')
})
.catch(() => {
ElMessage.error('复制失败,请手动复制')
})
}
//
function downloadFile(row: any) {
if (!row.file) {
ElMessage.warning('该书籍没有可下载的文件')
return
}
//
const link = document.createElement('a')
link.href = row.file
link.download = `${row.title || 'book'}_${new Date().getTime()}${getFileExtension(row.file)}`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success('文件下载已开始')
}
//
function getFileExtension(url: string) {
const match = url.match(/\.([0-9a-z]+)(?:[\?#]|$)/i)
return match ? `.${match[1]}` : '.pdf' // pdf
}
onMounted(() => {
init()
})
</script>
<style scoped>
<style scoped>
.book-image {
width: 60px;
height: 80px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.detail-image {
width: 120px;
height: 160px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.pagination {
margin: 20px 0;
text-align: center;
}
.mb-4 {
margin-bottom: 16px;
}
.book-description {
line-height: 1.6;
color: #666;
}
.certificate-container {
position: relative;
}
.certificate-content {
background-color: #f8f8f8;
border: 1px solid #eaeaea;
border-radius: 4px;
padding: 15px;
max-height: 500px;
overflow-y: auto;
margin-bottom: 20px;
}
.certificate-content pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: monospace;
}
.copy-btn {
display: block;
margin: 0 auto;
}
.el-tabs {
margin-bottom: 20px;
}
.el-tag {
margin: 2px;
}
</style>
<route lang="json">
{
"meta": {

View File

@ -2,10 +2,10 @@
<div class="item-trace-container">
<!-- 顶部导航 -->
<div class="trace-header">
<h1 class="title">换住源溯源系统</h1>
<h1 class="title">版权源溯源系统</h1>
<el-input
v-model="searchQuery"
placeholder="请输入换住区块编号"
placeholder="请输入版权区块编号"
class="search-input"
clearable
@keyup.enter="handleSearch"
@ -16,230 +16,151 @@
</el-input>
</div>
<el-row :gutter="20" >
<el-col :span="12" v-if="!isEmpty(state.userA)">
<el-descriptions title="甲方用户信息" :column="3" border>
<el-descriptions-item
label="姓名"
label-align="right"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>{{ state.userA.nickName }}</el-descriptions-item
>
<el-descriptions-item label="手机号" label-align="right" align="center">{{ state.userA.introduce }}</el-descriptions-item>
<!-- 版权详情展示 -->
<div v-if="state.bookData.hex" class="copyright-detail">
<el-card shadow="hover" class="detail-card">
<div class="info-header">
<h2>图书版权信息</h2>
<el-tag :type="getStatusTagType(state.bookData.status)" size="large">
{{ state.bookData.status || '未知状态' }}
</el-tag>
</div>
</el-descriptions>
</el-col>
<el-col :span="12" v-if="!isEmpty(state.userB)">
<el-descriptions title="甲方用户信息" :column="3" border>
<el-descriptions-item
label="姓名"
label-align="right"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>{{ state.userB.nickName }}</el-descriptions-item
>
<el-descriptions-item label="手机号" label-align="right" align="center">{{ state.userB.introduce }}</el-descriptions-item>
</el-descriptions>
</el-col>
</el-row>
<div style="height: 40px"></div>
<!-- A-->
<el-row :gutter="20" v-if="!isEmpty(state.houseA)">
<el-col :span="6">
<el-image
:src=" state.houseA.image"
fit="cover"
class="main-image"
/>
</el-col>
<el-col :span="18">
<el-col :xs="24" :sm="24" :md="12" :lg="14">
<!-- 信息展示区 -->
<el-divider />
<div class="info-grid">
<div class="info-section">
<h1 class="property-title">{{ state.houseA.title}}</h1>
<div class="property-meta">
<div class="meta-item">
<span class="meta-label">房源地址:</span>
<span class="meta-value">{{ state.houseA.city || '暂无' }}</span>
</div>
<div class="price-section">
<span class="price-label">价格:</span>
<span class="price-value">{{ state.houseA.price || '0' }}</span>
<span class="price-unit">/平方</span>
<el-tag type="success" size="small" class="blockchain-tag">
<span class="tag-icon"></span>
<span>链上价格</span>
</el-tag>
</div>
<div class="verification-badge">
<el-icon><SuccessFilled /></el-icon>
<span>房源信息已通过区块链验证</span>
</div>
<div class="property-details">
<div class="detail-item">
<span class="detail-label">小区名称:</span>
<span class="detail-value">{{ state.houseA.name || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">朝向:</span>
<span class="detail-value">{{ state.houseA.face || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">面积:</span>
<span class="detail-value">{{ state.houseA.area || '0' }} 平方</span>
</div>
<div class="detail-item">
<span class="detail-label">楼层:</span>
<span class="detail-value">{{ state.houseA.floor || '0' }}</span>
</div>
</div>
<!-- 区块链哈希展示 -->
<div class="blockchain-hash">
<div class="hash-title">
<el-icon><Connection /></el-icon>
<span>区块链编号</span>
</div>
<el-tooltip :content="state.houseA.hex || '暂无'" placement="top">
<div class="hash-value">
{{ state.houseA.hex}}
</div>
</el-tooltip>
</div>
</div>
<h3>图书基本信息</h3>
<el-descriptions :column="1" border>
<el-descriptions-item label="封面">
<el-image
:src="state.bookData.image || 'https://via.placeholder.com/150x200?text=No+Cover'"
style="width: 100px; height: 140px;"
fit="cover"
/>
</el-descriptions-item>
<el-descriptions-item label="ISBN编号">{{ state.bookData.isbn || '-' }}</el-descriptions-item>
<el-descriptions-item label="图书标题">{{ state.bookData.title || '-' }}</el-descriptions-item>
<el-descriptions-item label="作者">{{ state.bookData.author || '-' }}</el-descriptions-item>
<el-descriptions-item label="出版社">{{ state.bookData.publisher || '-' }}</el-descriptions-item>
<el-descriptions-item label="出版日期">{{state.bookData.publishDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="版次">{{ state.bookData.edition || '-' }}</el-descriptions-item>
<el-descriptions-item label="语言">{{ state.bookData.language || '中文' }}</el-descriptions-item>
<el-descriptions-item label="定价">{{ state.bookData.price ? `${state.bookData.price}` : '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-col>
</el-row>
<el-row :gutter="20" v-if="!isEmpty(state.houseB)">
<el-col :span="6">
<el-image
:src=" state.houseB.image"
fit="cover"
class="main-image"
/>
</el-col>
<el-col :span="18">
<el-col :xs="24" :sm="24" :md="12" :lg="14">
<!-- 信息展示区 -->
<div class="info-section">
<h1 class="property-title">{{ state.houseB.title}}</h1>
<div class="property-meta">
<div class="meta-item">
<span class="meta-label">房源地址:</span>
<span class="meta-value">{{ state.houseB.city || '暂无' }}</span>
</div>
<div class="price-section">
<span class="price-label">价格:</span>
<span class="price-value">{{ state.houseB.price || '0' }}</span>
<span class="price-unit">/平方</span>
<el-tag type="success" size="small" class="blockchain-tag">
<span class="tag-icon"></span>
<span>链上价格</span>
</el-tag>
</div>
<div class="verification-badge">
<el-icon><SuccessFilled /></el-icon>
<span>房源信息已通过区块链验证</span>
</div>
<div class="property-details">
<div class="detail-item">
<span class="detail-label">小区名称:</span>
<span class="detail-value">{{ state.houseB.name || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">朝向:</span>
<span class="detail-value">{{ state.houseB.face || '暂无' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">面积:</span>
<span class="detail-value">{{ state.houseB.area || '0' }} 平方</span>
</div>
<div class="detail-item">
<span class="detail-label">楼层:</span>
<span class="detail-value">{{ state.houseB.floor || '0' }}</span>
</div>
</div>
<!-- 区块链哈希展示 -->
<div class="blockchain-hash">
<div class="hash-title">
<el-icon><Connection /></el-icon>
<span>区块链编号</span>
</div>
<el-tooltip :content="state.houseA.hex || '暂无'" placement="top">
<div class="hash-value">
{{ state.houseB.hex}}
</div>
</el-tooltip>
</div>
</div>
<h3>版权信息</h3>
<el-descriptions :column="1" border>
<el-descriptions-item label="版权区块编号">{{ state.bookData.hex || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权持有人">{{ state.bookData.copyrightOwner || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权起始年份">{{ state.bookData.copyrightStartYear || '-' }}</el-descriptions-item>
<el-descriptions-item label="版权到期年份">{{ state.bookData.copyrightEndYear || '-' }}</el-descriptions-item>
<el-descriptions-item label="上链时间">{{state.bookData.createTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="电子文件">
<el-button
v-if="state.bookData.file"
type="primary"
size="small"
@click="downloadFile(state.bookData.file)"
>
下载文件
</el-button>
<span v-else>-</span>
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-col>
</div>
</el-row>
<el-divider />
</el-card>
</div>
<!-- 空状态 -->
<div v-else class="empty-state">
<el-empty description="请输入版权区块编号查询详情" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ArrowRight, Connection, Search, SuccessFilled } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
//
const searchQuery = ref('')
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
const state = reactive(<any>{
dto:{},
houseA:{},
houseB:{},
userA:{},
userB:{},
const state = reactive({
bookData: {} as any
})
//
//
const getStatusTagType = (status: string) => {
switch (status) {
case '已通过':
return 'success'
case '待审核':
return 'warning'
case '已拒绝':
return 'danger'
default:
return 'info'
}
}
//
const handleSearch = async () => {
if (!searchQuery.value.trim()) {
ElMessage.warning('请输入查询内容')
return
}
frontRequest.get(`/api/order/hex?hex=${searchQuery.value}`).then(response =>{
try {
const response = await frontRequest.get(`/api/book/hex?hex=${searchQuery.value}`)
ElMessage.success('查询成功')
state.dto = response.data.dto
state.houseA = response.data.houseA
state.houseB = response.data.houseB
state.userA = response.data.userA
state.userB = response.data.userB
})
state.bookData = response.data || {}
} catch (error) {
ElMessage.error('查询失败,请检查区块编号是否正确')
console.error('查询失败:', error)
}
}
//
const downloadFile = (fileUrl: string) => {
if (!fileUrl) {
ElMessage.warning('没有可下载的文件')
return
}
const link = document.createElement('a')
link.href = fileUrl
link.download = `book_${state.bookData.isbn || 'file'}_${new Date().getTime()}`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
//
const viewCertificate = (hex: string) => {
if (!hex) {
ElMessage.warning('无有效的版权凭证')
return
}
//
window.open(`https://blockchain-explorer.com/tx/${hex}`, '_blank')
}
//
const viewTransaction = (hex: string) => {
if (!hex) {
ElMessage.warning('无有效的交易哈希')
return
}
//
window.open(`https://blockchain-explorer.com/tx/${hex}`, '_blank')
}
</script>
@ -265,11 +186,34 @@ const handleSearch = async () => {
.search-input {
width: 400px;
}
.copyright-detail {
margin-top: 20px;
}
.detail-card {
padding: 20px;
}
.info-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.info-header h2 {
margin-right: 15px;
font-size: 22px;
color: #333;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.info-section h3 {
color: #666;
font-size: 18px;
@ -278,363 +222,27 @@ const handleSearch = async () => {
border-bottom: 1px solid #eee;
}
</style>
<style scoped>
/* 全局样式 */
.blockchain-detail-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: 'Helvetica Neue', Arial, sans-serif;
color: #333;
}
/* 头部样式 */
.blockchain-header {
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid #eaeaea;
}
.nav-logo {
display: flex;
align-items: center;
font-size: 20px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 15px;
}
.logo-icon {
margin-right: 10px;
font-size: 24px;
color: #1abc9c;
}
.blockchain-breadcrumb {
padding: 8px 0;
}
/* 图片区域 */
.image-section {
position: relative;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.main-image {
width: 100%;
height: 400px;
display: block;
transition: all 0.3s;
}
.main-image:hover {
transform: scale(1.02);
}
.blockchain-badge {
position: absolute;
top: 15px;
right: 15px;
background-color: rgba(26, 188, 156, 0.9);
color: white;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
display: flex;
align-items: center;
z-index: 2;
}
.badge-icon {
margin-right: 5px;
font-size: 14px;
}
.image-tags {
position: absolute;
bottom: 15px;
left: 15px;
display: flex;
gap: 10px;
z-index: 2;
}
.location-tag {
background-color: rgba(52, 152, 219, 0.9);
color: white;
border: none;
}
.type-tag {
background-color: rgba(46, 204, 113, 0.9);
color: white;
border: none;
}
/* 信息区域 */
.info-section {
padding: 0 15px;
}
.property-title {
font-size: 24px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 20px;
line-height: 1.3;
}
.property-meta {
margin-bottom: 30px;
}
.meta-item {
display: flex;
align-items: center;
margin-bottom: 15px;
font-size: 16px;
}
.meta-label {
color: #7f8c8d;
margin-right: 10px;
min-width: 80px;
}
.meta-value {
color: #34495e;
font-weight: 500;
}
.price-section {
display: flex;
align-items: center;
margin: 25px 0;
padding-bottom: 20px;
border-bottom: 1px dashed #eaeaea;
}
.price-label {
color: #7f8c8d;
margin-right: 10px;
font-size: 16px;
}
.price-value {
color: #e74c3c;
font-size: 28px;
font-weight: 700;
margin-right: 5px;
}
.price-unit {
color: #95a5a6;
font-size: 16px;
margin-right: 15px;
}
.blockchain-tag {
background-color: #e8f8f5;
color: #1abc9c;
border: none;
display: flex;
align-items: center;
}
.tag-icon {
margin-right: 5px;
}
.verification-badge {
display: flex;
align-items: center;
background-color: #f8f9fa;
padding: 10px 15px;
border-radius: 6px;
margin-bottom: 25px;
color: #1abc9c;
font-size: 14px;
}
.verification-badge .el-icon {
margin-right: 8px;
font-size: 18px;
}
.property-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 30px;
}
.detail-item {
display: flex;
align-items: center;
}
.detail-label {
color: #7f8c8d;
margin-right: 8px;
min-width: 50px;
}
.detail-value {
color: #34495e;
font-weight: 500;
}
/* 区块链哈希区域 */
.blockchain-hash {
background-color: #f5f7fa;
padding: 15px;
border-radius: 6px;
margin-bottom: 30px;
border-left: 4px solid #1abc9c;
}
.hash-title {
display: flex;
align-items: center;
color: #2c3e50;
font-weight: 600;
margin-bottom: 10px;
}
.hash-title .el-icon {
margin-right: 8px;
color: #1abc9c;
}
.hash-value {
font-family: 'Courier New', monospace;
background-color: #2c3e50;
color: #ecf0f1;
padding: 10px;
border-radius: 4px;
word-break: break-all;
margin-bottom: 10px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.hash-value:hover {
background-color: #1a252f;
}
.view-transaction {
color: #1abc9c !important;
font-size: 13px;
}
.view-transaction .el-icon {
margin-left: 3px;
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 15px;
margin-top: 30px;
flex-wrap: wrap;
}
.buy-button {
background-color: #e74c3c;
border: none;
height: 48px;
font-size: 16px;
font-weight: 500;
padding: 0 25px;
flex: 1;
}
.buy-button:hover {
background-color: #c0392b;
}
.buy-button .el-icon {
margin-right: 8px;
}
.cart-button {
height: 48px;
font-size: 16px;
font-weight: 500;
padding: 0 25px;
flex: 1;
border: 1px solid #1abc9c;
color: #1abc9c;
background-color: rgba(26, 188, 156, 0.1);
}
.cart-button:hover {
background-color: rgba(26, 188, 156, 0.2);
}
.cart-button .el-icon {
margin-right: 8px;
}
.favorite-button {
height: 48px;
font-size: 16px;
font-weight: 500;
padding: 0 25px;
flex: 1;
}
.favorite-button .el-icon {
margin-right: 8px;
}
/* 抽屉样式 */
.drawer-content {
padding: 20px;
}
.blockchain-form {
max-width: 500px;
margin: 0 auto;
}
.blockchain-form :deep(.el-form-item__label) {
font-weight: 500;
color: #2c3e50;
}
.confirm-button {
width: 100%;
height: 48px;
font-size: 16px;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
.confirm-button .el-icon {
margin-right: 8px;
.empty-state {
margin-top: 100px;
text-align: center;
}
/* 响应式调整 */
@media (max-width: 768px) {
.property-details {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.buy-button,
.cart-button,
.favorite-button {
width: 100%;
}
:deep(.el-descriptions__body) {
background-color: #f9f9f9;
}
:deep(.el-descriptions__label) {
width: 120px;
}
</style>
<route lang="json">
{
"meta": {

View File

@ -1,142 +1,397 @@
<template>
<div class="house-upload-container">
<h2 class="upload-title">上传房屋信息</h2>
<div class="copyright-upload-container">
<h2 class="upload-title">图书版权信息登记</h2>
<el-form
:model="state.formData"
:model="formData"
ref="formRef"
label-width="120px"
label-position="right"
class="house-upload-form"
class="copyright-upload-form"
:rules="rules"
>
<!-- 基本信息部分 -->
<div class="form-section">
<h3 class="section-title">基本信息</h3>
<h3 class="section-title">
<el-icon><Document /></el-icon>
基本信息
</h3>
<div class="form-grid">
<el-form-item label="标题" prop="title" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input v-model="state.formData.title" placeholder="请输入标题" class="form-input" />
</el-form-item>
<el-form-item label="小区名称" prop="name" :rules="[{ required: true, message: '请输入小区名称', trigger: 'blur' }]">
<el-input v-model="state.formData.name" placeholder="请输入小区名称" class="form-input" />
</el-form-item>
<el-form-item label="城市" prop="city" :rules="[{ required: true, message: '请输入城市', trigger: 'blur' }]">
<el-input v-model="state.formData.city" placeholder="请输入城市" class="form-input" />
</el-form-item>
</div>
</div>
<div class="form-section">
<h3 class="section-title">房屋详情</h3>
<div class="form-grid">
<el-form-item label="户型" prop="type" :rules="[{ required: true, message: '请输入户型', trigger: 'blur' }]">
<el-select v-model="state.formData.type" placeholder="请选择户型" clearable class="form-input">
<el-option
v-for="item in getHouseType()"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="面积(㎡)" prop="area">
<el-input v-model="state.formData.area" placeholder="请输入面积" class="form-input" />
</el-form-item>
<el-form-item label="楼层" prop="floor">
<el-input-number v-model="state.formData.floor" :min="0" class="form-input" />
</el-form-item>
<el-form-item label="朝向" prop="face">
<el-select v-model="state.formData.face" placeholder="请选择朝向" class="form-input">
<el-option v-for="item in getFace()" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="装修" prop="decoration">
<el-select v-model="state.formData.decoration" placeholder="请选择装修" class="form-input">
<el-option v-for="item in getDecoration()" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="价格(元/㎡)" prop="price">
<el-input-number v-model="state.formData.price" :min="0" :step="0.01" class="form-input" />
</el-form-item>
</div>
</div>
<div class="form-section">
<h3 class="section-title">房屋展示</h3>
<div class="form-grid">
<el-form-item label="封面图" prop="image" :rules="[{ required: true, message: '请上传封面图', trigger: 'blur' }]">
<image-upload
class="image-uploader"
:image-url="state.formData.image"
@update:imageUrl="handleImageUrl"
/>
<p class="upload-tip">建议上传高清图片尺寸比例16:9</p>
</el-form-item>
</div>
</div>
<div class="form-section">
<h3 class="section-title">其他信息</h3>
<div class="form-grid">
<el-form-item label="标签" prop="tag">
<el-input v-model="state.formData.tag" placeholder="请输入标签,用逗号分隔" class="form-input" />
<p class="input-tip">例如地铁房,学区房,精装修</p>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="state.formData.sort" :min="1" class="form-input" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-form-item label="图书标题" prop="title">
<el-input
type="textarea"
v-model="state.formData.description"
rows="5"
placeholder="请输入房屋详细描述"
class="form-textarea"
v-model="formData.title"
placeholder="请输入图书标题"
clearable
/>
</el-form-item>
<el-form-item label="ISBN编号" prop="isbn">
<el-input
v-model="formData.isbn"
placeholder="请输入ISBN编号"
clearable
/>
<div class="input-tip">国际标准书号如978-7-121-12345-6</div>
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input
v-model="formData.author"
placeholder="请输入作者姓名"
clearable
/>
</el-form-item>
<el-form-item label="出版社" prop="publisher">
<el-input
v-model="formData.publisher"
placeholder="请输入出版社名称"
clearable
/>
</el-form-item>
</div>
</div>
<!-- 版权信息部分 -->
<div class="form-section">
<h3 class="section-title">
<el-icon><Lock /></el-icon>
版权信息
</h3>
<div class="form-grid">
<el-form-item label="出版日期" prop="publishDate">
<el-date-picker
v-model="formData.publishDate"
type="date"
placeholder="选择出版日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="版权持有人" prop="copyrightOwner">
<el-input
v-model="formData.copyrightOwner"
placeholder="请输入版权持有人"
clearable
/>
</el-form-item>
<el-form-item label="版权起始年份" prop="copyrightStartYear">
<el-date-picker
v-model="formData.copyrightStartYear"
type="year"
placeholder="选择起始年份"
value-format="YYYY"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="版权到期年份" prop="copyrightEndYear">
<el-date-picker
v-model="formData.copyrightEndYear"
type="year"
placeholder="选择到期年份"
value-format="YYYY"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="版次" prop="edition">
<el-input
v-model="formData.edition"
placeholder="如:第一版"
clearable
/>
</el-form-item>
<el-form-item label="语言" prop="language">
<el-select
v-model="formData.language"
placeholder="请选择语言"
style="width: 100%"
>
<el-option label="中文" value="中文" />
<el-option label="英文" value="英文" />
<el-option label="日文" value="日文" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
</div>
</div>
<!-- 文件上传部分 -->
<div class="form-section">
<h3 class="section-title">
<el-icon><UploadFilled /></el-icon>
文件上传
</h3>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="电子文件" prop="file" class="file-upload-item">
<el-upload
drag
:action="state.path"
:limit="1"
:on-success="handleFileSuccess"
:on-error="handleUploadError"
:before-upload="beforeFileUpload"
:file-list="fileList"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">
将图书电子文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持PDFEPUB等格式大小不超过50MB
</div>
</template>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="封面图片" prop="image" class="image-upload-item">
<el-upload
:action="state.path"
list-type="picture-card"
:show-file-list="false"
:on-success="handleImageSuccess"
:before-upload="beforeImageUpload"
>
<img
v-if="formData.image"
:src="formData.image"
class="uploaded-image"
alt="封面图片"
/>
<el-icon v-else class="uploader-icon">
<Plus />
</el-icon>
</el-upload>
<div class="upload-tip">建议尺寸16:9大小不超过5MB</div>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 其他信息部分 -->
<div class="form-section">
<h3 class="section-title">
<el-icon><PriceTag /></el-icon>
其他信息
</h3>
<div class="form-grid">
<el-form-item label="图书定价" prop="price">
<el-input-number
v-model="formData.price"
:min="0"
:precision="2"
controls-position="right"
/>
<span class="price-unit"></span>
</el-form-item>
</div>
</div>
<!-- 表单操作按钮 -->
<div class="form-actions">
<el-button type="primary" @click="saveTransaction" class="submit-btn">保存并发布</el-button>
<el-button @click="resetForm" class="reset-btn">重置</el-button>
<el-button
type="primary"
@click="submitForm"
:loading="submitting"
class="submit-btn"
>
<template #icon>
<el-icon><Upload /></el-icon>
</template>
提交版权信息
</el-button>
<el-button
@click="resetForm"
class="reset-btn"
>
<template #icon>
<el-icon><Refresh /></el-icon>
</template>
重置表单
</el-button>
</div>
</el-form>
</div>
</template>
<script setup lang="ts">
import { getDecoration, getFace, getHouseType } from '~/utils/utils'
import { reactive, ref } from 'vue'
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const state = reactive(<any>{
formData: {}
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules, UploadProps } from 'element-plus'
import {
Document,
Lock,
UploadFilled,
PriceTag,
Upload,
Refresh,
Plus
} from '@element-plus/icons-vue'
const state = reactive({
path:import.meta.env.VITE_API_FRONT_BASE_URL+"/api/upload"
})
// /
const saveTransaction = () => {
formRef.value?.validate(async valid => {
if (!valid) return
state.formData.status = 0
frontRequest.post(`/api/item`, state.formData).then(res => {
//
ElMessage.success("上传成功")
})
//
const formRef = ref<FormInstance>()
//
const formData = reactive({
title: '',
isbn: '',
author: '',
publisher: '',
publishDate: '',
copyrightOwner: '',
copyrightStartYear: '',
copyrightEndYear: '',
edition: '',
language: '中文',
price: 0,
image: '',
file: '',
useBlockchain: true
})
//
const fileList = ref([])
//
const submitting = ref(false)
//
const rules = reactive<FormRules>({
title: [
{ required: true, message: '请输入图书标题', trigger: 'blur' },
{ min: 2, max: 100, message: '长度在2到100个字符', trigger: 'blur' }
],
isbn: [
{ required: true, message: '请输入ISBN编号', trigger: 'blur' },
{ pattern: /^(?:\d{3}-)?\d{1,5}-\d{1,7}-\d{1,7}-\d$/, message: 'ISBN格式不正确', trigger: 'blur' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2到50个字符', trigger: 'blur' }
],
publisher: [
{ required: true, message: '请输入出版社', trigger: 'blur' },
{ min: 2, max: 100, message: '长度在2到100个字符', trigger: 'blur' }
],
copyrightOwner: [
{ required: true, message: '请输入版权持有人', trigger: 'blur' }
],
image: [
{ required: true, message: '请上传封面图片', trigger: 'change' }
],
file: [
{ required: true, message: '请上传电子文件', trigger: 'change' }
]
})
//
const handleImageSuccess: UploadProps['onSuccess'] = (response) => {
formData.image = response.data.path
}
//
const handleFileSuccess: UploadProps['onSuccess'] = (response) => {
formData.file = response.data.path
}
//
const handleUploadError: UploadProps['onError'] = () => {
ElMessage.error('文件上传失败,请重试')
}
//
const beforeImageUpload: UploadProps['beforeUpload'] = (file) => {
const isImage = file.type.startsWith('image/')
const isLt5M = file.size / 1024 / 1024 < 5
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt5M) {
ElMessage.error('图片大小不能超过5MB!')
return false
}
return true
}
//
const beforeFileUpload: UploadProps['beforeUpload'] = (file) => {
const allowedTypes = ['application/pdf', 'application/epub+zip']
const isAllowedType = allowedTypes.includes(file.type)
const isLt50M = file.size / 1024 / 1024 < 50
if (!isAllowedType) {
ElMessage.error('只能上传PDF或EPUB文件!')
return false
}
if (!isLt50M) {
ElMessage.error('文件大小不能超过50MB!')
return false
}
return true
}
//
const submitForm = () => {
formRef.value?.validate(async (valid) => {
if (!valid) {
ElMessage.warning('请填写完整的表单信息')
return
}
submitting.value = true
try {
// API
const response = await frontRequest.post('/api/book', formData)
// API
await new Promise(resolve => setTimeout(resolve, 1500))
ElMessage.success('版权信息提交成功')
resetForm()
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败,请稍后重试')
} finally {
submitting.value = false
}
})
}
//
const resetForm = () => {
formRef.value?.resetFields()
}
const handleImageUrl = (url: string) => {
state.formData.image = url
ElMessageBox.confirm('确定要重置表单吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
formRef.value?.resetFields()
fileList.value = []
formData.image = ''
formData.file = ''
ElMessage.success('表单已重置')
}).catch(() => {
//
})
}
</script>
<style scoped lang="scss">
.house-upload-container {
max-width: 1000px;
.copyright-upload-container {
max-width: 1200px;
margin: 0 auto;
padding: 30px 20px;
padding: 30px;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
@ -147,22 +402,34 @@ const handleImageUrl = (url: string) => {
color: #333;
font-size: 24px;
font-weight: 500;
letter-spacing: 1px;
}
.house-upload-form {
.copyright-upload-form {
.form-section {
margin-bottom: 30px;
padding: 20px;
background-color: #f9fafc;
border-radius: 8px;
border-left: 4px solid #409EFF;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #409EFF;
font-size: 18px;
font-weight: 500;
padding-bottom: 10px;
border-bottom: 1px solid #e6e6e6;
.el-icon {
margin-right: 8px;
font-size: 20px;
}
}
.form-grid {
@ -171,74 +438,87 @@ const handleImageUrl = (url: string) => {
gap: 20px;
:deep(.el-form-item) {
margin-bottom: 0;
margin-bottom: 20px;
}
}
}
.form-input {
width: 100%;
}
.form-textarea {
width: 100%;
}
.upload-tip {
margin-top: 8px;
font-size: 12px;
color: #999;
}
.input-tip {
margin-top: 8px;
font-size: 12px;
color: #999;
line-height: 1.5;
}
.image-uploader {
width: 100%;
max-width: 400px;
.price-unit {
margin-left: 8px;
color: #666;
}
.file-upload-item {
:deep(.el-upload) {
width: 100%;
}
:deep(.el-upload-dragger) {
width: 100%;
padding: 20px;
}
}
}
.form-actions {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
margin-top: 40px;
.submit-btn {
padding: 12px 30px;
padding: 12px 36px;
font-size: 16px;
letter-spacing: 1px;
}
.reset-btn {
padding: 12px 30px;
padding: 12px 36px;
font-size: 16px;
}
}
}
@media (max-width: 768px) {
.house-upload-container {
padding: 20px 15px;
.house-upload-form {
@media (max-width: 768px) {
.copyright-upload-container {
padding: 15px;
.copyright-upload-form {
.form-grid {
grid-template-columns: 1fr;
}
.form-section {
padding: 15px;
}
}
.form-grid {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
gap: 10px;
button {
width: 100%;
}
}
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
"layout": "front",
"requiresAuth": true
}
}
</route>

View File

@ -7,8 +7,8 @@ export const getAdminList = () => {
"icon": "HomeFilled" // 首页
},
{
"path": "/admin/house",
"name": "房源管理",
"path": "/admin/book",
"name": "版权管理",
"icon": "Discount" // 电影管理
},
@ -34,33 +34,23 @@ export const getFrontList = () => {
},
{
"path": "/list",
"name": "供应房源",
"name": "版权展示",
"icon": "House",
},
{
"path": "/upload",
"name": "发布版权",
"icon": "Trophy"
},
{
"path": "/upload",
"name": "发布房源",
"path": "/push",
"name": "版权管理",
"icon": "Trophy"
},
{
"path": "/topic",
"name": "房源溯源",
"icon": "Trophy"
},
{
"path": "/order",
"name": "我的换住",
"icon": "Trophy"
},
{
"path": "/push",
"name": "我的发布",
"icon": "Trophy"
},
{
"path": "/collect",
"name": "我的收藏",
"name": "版权溯源",
"icon": "Trophy"
},
]
@ -86,12 +76,4 @@ export const getDecoration = () => {
const routes =["精装", "简装", "毛坯"]
return routes;
}
export const getHouseStatus = () => {
const routes =[
{name:"未审核",status:0},
{name:"上架",status:1},
{name:"下架",status:2},
{name:"审核失败",status:3},
]
return routes;
}