批量上传

This commit is contained in:
闵宪瑞 2025-02-13 23:33:45 +08:00
parent b1c1ad81c8
commit 505dc2a173
101 changed files with 9654 additions and 7928 deletions

View File

@ -0,0 +1,100 @@
package io.modules.sys.controller;
import io.common.annotation.LogOperation;
import io.common.constant.Constant;
import io.common.page.PageData;
import io.common.utils.ExcelUtils;
import io.common.utils.Result;
import io.common.validator.AssertUtils;
import io.common.validator.ValidatorUtils;
import io.common.validator.group.AddGroup;
import io.common.validator.group.DefaultGroup;
import io.common.validator.group.UpdateGroup;
import io.modules.item.dto.CategoriesDTO;
import io.modules.item.service.CategoriesService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 商品分类表
*
* @author Mark #
* @since 1.0.0 2025-02-13
*/
@RestController
@RequestMapping("sys/categories")
@Tag(name="商品分类表")
public class CategoriesController {
@Autowired
private CategoriesService categoriesService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始", in = ParameterIn.QUERY, required = true, ref="int") ,
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY,required = true, ref="int") ,
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref="String") ,
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String")
})
public Result<PageData<CategoriesDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<CategoriesDTO> page = categoriesService.page(params);
return new Result<PageData<CategoriesDTO>>().ok(page);
}
@GetMapping("{id}")
public Result<CategoriesDTO> get(@PathVariable("id") Long id){
CategoriesDTO data = categoriesService.get(id);
return new Result<CategoriesDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
public Result save(@RequestBody CategoriesDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
categoriesService.save(dto);
return new Result();
}
@PutMapping
@Operation(summary = "修改")
public Result update(@RequestBody CategoriesDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
categoriesService.update(dto);
return new Result();
}
@DeleteMapping("{id}")
@Operation(summary = "删除")
@LogOperation("删除")
public Result delete(@PathVariable Long id){
Long[] ids = new Long[] { id };
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
categoriesService.delete(ids);
return new Result();
}
}

View File

@ -73,8 +73,8 @@ public class CommentController {
Long itemId = comment.getItemId();
ItemDTO itemDTO = itemService.get(itemId);
if (itemDTO != null){
itemDTO.setLargePic(uploadUrl + "item/" + itemDTO.getLargePic());
itemDTO.setNormalPic(uploadUrl + "item/n_" + itemDTO.getNormalPic());
// itemDTO.setLargePic(uploadUrl + "item/" + itemDTO.getLargePic());
// itemDTO.setNormalPic(uploadUrl + "item/n_" + itemDTO.getNormalPic());
}
comment.setItemDTO(itemDTO);
return comment;

View File

@ -10,7 +10,9 @@ import io.common.validator.ValidatorUtils;
import io.common.validator.group.AddGroup;
import io.common.validator.group.DefaultGroup;
import io.common.validator.group.UpdateGroup;
import io.modules.item.dto.CategoriesDTO;
import io.modules.item.dto.ItemDTO;
import io.modules.item.service.CategoriesService;
import io.modules.item.service.ItemService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
@ -20,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -30,41 +33,49 @@ import java.util.stream.Collectors;
@CrossOrigin
@RestController
@RequestMapping("sys/item")
@Tag(name="电影表")
@Tag(name = "电影表")
public class ItemController {
@Autowired
private ItemService itemService;
@Autowired
private CategoriesService categoriesService;
@Value("${upload.url}")
private String uploadUrl;
@GetMapping("query")
@Operation(summary = "首页顶部展示")
public Result< List<Object>> page(){
public Result<List<Object>> page() {
List<Object> res = itemService.query();
return new Result< List<Object>>().ok(res);
return new Result<List<Object>>().ok(res);
}
@GetMapping("view")
@Operation(summary = "首页分析")
public Result< List<Map<String, Object>>> view(){
List<Map<String, Object>>res = itemService.view();
return new Result< List<Map<String, Object>>>().ok(res);
public Result<List<Map<String, Object>>> view() {
List<Map<String, Object>> res = itemService.view();
return new Result<List<Map<String, Object>>>().ok(res);
}
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始", in = ParameterIn.QUERY, required = true, ref="int") ,
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY,required = true, ref="int") ,
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref="String") ,
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String")
@Parameter(name = Constant.PAGE, description = "当前页码从1开始", in = ParameterIn.QUERY, required = true, ref = "int"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY, required = true, ref = "int"),
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref = "String"),
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String"),
@Parameter(name = "status", description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String")
})
public Result<PageData<ItemDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
public Result<PageData<ItemDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
System.out.println(params);
PageData<ItemDTO> page = itemService.page(params);
List<ItemDTO> list = page.getList().stream().map(e -> {
e.setLargePic(uploadUrl + "item/l_" + e.getLargePic());
e.setNormalPic(uploadUrl + "item/n_" + e.getNormalPic());
e.setImage(uploadUrl + e.getImage());
CategoriesDTO categoriesDTO = categoriesService.get(e.getCategoryId());
e.setCategoryName(categoriesDTO.getName());
return e;
}).collect(Collectors.toList());
page.setList(list);
@ -73,28 +84,27 @@ public class ItemController {
@GetMapping("{id}")
@Operation(summary = "信息")
public Result<ItemDTO> get(@PathVariable("id") Long id){
public Result<ItemDTO> get(@PathVariable("id") Long id) {
ItemDTO data = itemService.get(id);
data.setLargePic(uploadUrl + "item/l_" + data.getLargePic());
data.setImage(uploadUrl + data.getImage());
return new Result<ItemDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
public Result save(@RequestBody ItemDTO dto){
public Result save(@RequestBody ItemDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
dto.setLargePic(uploadUrl + "item/l_" + dto.getLargePic());
dto.setNormalPic(uploadUrl + "item/n_" + dto.getNormalPic());
dto.setImage(uploadUrl + dto.getImage());
itemService.save(dto);
return new Result();
}
@PutMapping
@Operation(summary = "修改")
public Result update(@RequestBody ItemDTO dto){ //效验数据
public Result update(@RequestBody ItemDTO dto) { //效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
dto.setLargePic(dto.getLargePic().replace(uploadUrl+"item/l_","") );
dto.setImage(uploadUrl + dto.getImage());
itemService.update(dto);
return new Result();
}
@ -102,9 +112,9 @@ public class ItemController {
@DeleteMapping("{id}")
@Operation(summary = "删除")
@LogOperation("删除")
public Result delete(@PathVariable Long id){
public Result delete(@PathVariable Long id) {
Long[] ids = new Long[] { id };
Long[] ids = new Long[]{id};
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
itemService.delete(ids);

View File

@ -16,11 +16,14 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -33,6 +36,10 @@ public class SlidesController {
@Autowired
private SlidesService slidesService;
@Value("${upload.url}")
private String uploadUrl;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@ -43,6 +50,13 @@ public class SlidesController {
})
public Result<PageData<SlidesDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<SlidesDTO> page = slidesService.page(params);
List<SlidesDTO> list = page.getList().stream().map(e -> {
e.setPath(uploadUrl + e.getPath());
return e;
}).collect(Collectors.toList());
page.setList(list);
return new Result<PageData<SlidesDTO>>().ok(page);
}

View File

@ -3,9 +3,9 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ordinary.jimostudio.link:26449/bs_movies?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://47.94.76.54:23306/bookstore?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: minxianrui
password: x6dbfGN4s6YjcX8P
initial-size: 10
max-active: 100
min-idle: 10
@ -38,4 +38,4 @@ mybatis-plus:
boolValue: TRUE
upload:
path: D:\\code\\security\\upload\\
url: https://bs.oss.xunyingcloud.cn/upload/movies/
url: http://localhost:18081/

View File

@ -0,0 +1,17 @@
package io.modules.item.dao;
import io.common.dao.BaseDao;
import io.modules.item.entity.CategoriesEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 商品分类表
*
* @author Mark #
* @since 1.0.0 2025-02-13
*/
@Mapper
public interface CategoriesDao extends BaseDao<CategoriesEntity> {
}

View File

@ -1,17 +1,11 @@
package io.modules.item.dao;
import io.common.dao.BaseDao;
import io.modules.item.entity.ItemEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
* 主表
*/
@Mapper
public interface ItemDao extends BaseDao<ItemEntity> {
}
public interface ItemDao extends BaseDao<ItemEntity> {}

View File

@ -0,0 +1,38 @@
package io.modules.item.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.SchemaProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 商品分类表
*
* @author Mark #
* @since 1.0.0 2025-02-13
*/
@Data
@Schema(name = "商品分类表")
public class CategoriesDTO implements Serializable {
private static final long serialVersionUID = 1L;
@SchemaProperty(name = "编号")
private Long id;
@SchemaProperty(name = "名称")
private String name;
@SchemaProperty(name = "图片")
private String image;
@SchemaProperty(name = "父编号")
private Integer parentId;
@SchemaProperty(name = "创建时间")
private Date createTime;
}

View File

@ -7,56 +7,61 @@ import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
/**
* 电影
* 商品
*/
@Data
@Schema(name = "电影")
@Schema(name = "商品")
public class ItemDTO implements Serializable {
private static final long serialVersionUID = 1L;
@SchemaProperty(name = "编号")
private Long id;
@SchemaProperty(name = "电影名称")
@SchemaProperty(name = "名称")
private String title;
private String tag;
@SchemaProperty(name = "电影副标题")
private String cardSubtitle;
@SchemaProperty(name = "类别编号")
private Long categoryId;
private String categoryName;
@SchemaProperty(name = "评分")
private BigDecimal ratingValue;
@SchemaProperty(name = "图片")
private String image;
@SchemaProperty(name = "评分人数")
private Integer ratingCount;
@SchemaProperty(name = "图片列表")
private String imgList;
@SchemaProperty(name = "星级评分")
private BigDecimal ratingStarCount;
@SchemaProperty(name = "价格")
private Double price;
@SchemaProperty(name = "上映时间")
private String year;
@SchemaProperty(name = "描述")
private String description;
@SchemaProperty(name = "大图文件名")
private String largePic;
@SchemaProperty(name = "商品库存数量")
private Integer stockQuantity;
@SchemaProperty(name = "小图文件名")
private String normalPic;
@SchemaProperty(name = "状态")
private Integer status;
@SchemaProperty(name = "国家")
private String country;
@SchemaProperty(name = "电影类型")
private String movieType;
@SchemaProperty(name = "导演")
private String director;
@SchemaProperty(name = "主演")
private String actor;
private Boolean isFavorite;
@SchemaProperty(name = "排序")
private Integer sort;
@SchemaProperty(name = "创建时间")
private Date createTime;
@SchemaProperty(name = "产地")
private String origin;
@SchemaProperty(name = "品牌")
private String brand;
@SchemaProperty(name = "点击次数")
private Integer view;
@SchemaProperty(name = "属性")
private String attribute;
}

View File

@ -0,0 +1,35 @@
package io.modules.item.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* 商品分类表
*/
@Data
@TableName("tb_categories")
public class CategoriesEntity {
/**
* 编号
*/
private Long id;
/**
* 名称
*/
private String name;
/**
* 图片
*/
private String image;
/**
* 父编号
*/
private Integer parentId;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -3,14 +3,10 @@ package io.modules.item.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
* 商品表
*/
@Data
@TableName("tb_item")
@ -21,51 +17,60 @@ public class ItemEntity {
*/
private Long id;
/**
* 电影名称
* 名称
*/
private String title;
private String tag;
/**
* 电影副标题
* 类别编号
*/
private String cardSubtitle;
private Long categoryId;
/**
* 评分
* 图片
*/
private BigDecimal ratingValue;
private String image;
/**
* 评分人数
* 图片列表
*/
private Integer ratingCount;
private String imgList;
/**
* 星级评分
* 价格
*/
private BigDecimal ratingStarCount;
private Double price;
/**
* 上映时间
* 描述
*/
private String year;
private String description;
/**
* 大图文件名
* 商品库存数量
*/
private String largePic;
private Integer stockQuantity;
/**
* 小图文件名
* 状态
*/
private String normalPic;
private Integer status;
/**
* 国家
* 排序
*/
private String country;
private Integer sort;
/**
* 电影类型
* 创建时间
*/
private String movieType;
private Date createTime;
/**
* 导演
* 产地
*/
private String director;
private String origin;
/**
* 主演
* 品牌
*/
private String actor;
}
private String brand;
/**
* 点击次数
*/
private Integer view;
/**
* 属性
*/
private String attribute;
}

View File

@ -0,0 +1,16 @@
package io.modules.item.service;
import io.common.service.CrudService;
import io.modules.item.dto.CategoriesDTO;
import io.modules.item.entity.CategoriesEntity;
/**
* 商品分类表
*
* @author Mark #
* @since 1.0.0 2025-02-13
*/
public interface CategoriesService extends CrudService<CategoriesEntity, CategoriesDTO> {
}

View File

@ -8,10 +8,7 @@ import java.util.List;
import java.util.Map;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
* 主表
*/
public interface ItemService extends CrudService<ItemEntity, ItemDTO> {

View File

@ -0,0 +1,35 @@
package io.modules.item.service.impl;
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.CategoriesDao;
import io.modules.item.dto.CategoriesDTO;
import io.modules.item.entity.CategoriesEntity;
import io.modules.item.service.CategoriesService;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 商品分类表
*
* @author Mark #
* @since 1.0.0 2025-02-13
*/
@Service
public class CategoriesServiceImpl extends CrudServiceImpl<CategoriesDao, CategoriesEntity, CategoriesDTO> implements CategoriesService {
@Override
public QueryWrapper<CategoriesEntity> getWrapper(Map<String, Object> params){
String id = (String)params.get("id");
QueryWrapper<CategoriesEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(id), "id", id);
return wrapper;
}
}

View File

@ -24,10 +24,7 @@ import java.util.Map;
import java.util.stream.Collectors;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
* 主表
*/
@Service
public class ItemServiceImpl extends CrudServiceImpl<ItemDao, ItemEntity, ItemDTO> implements ItemService {
@ -42,7 +39,27 @@ public class ItemServiceImpl extends CrudServiceImpl<ItemDao, ItemEntity, ItemDT
@Override
public QueryWrapper<ItemEntity> getWrapper(Map<String, Object> params){
String title = (String)params.get("title");
String status = (String)params.get("status");
QueryWrapper<ItemEntity> wrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(status)){
switch (status){
case "0":
wrapper.eq("status", 0);
break;
case "1":
wrapper.eq("status", 1);
break;
case "2":
wrapper.lt("stock_quantity", 100);
break;
case "3":
wrapper.eq("stock_quantity", 0);
break;
}
}
wrapper.like(StrUtil.isNotBlank(title), "title", title);
return wrapper;
}
@ -51,7 +68,7 @@ public class ItemServiceImpl extends CrudServiceImpl<ItemDao, ItemEntity, ItemDT
@Override
public List<ItemEntity> score() {
LambdaQueryWrapper<ItemEntity> lwq = new LambdaQueryWrapper<>();
lwq.orderByDesc(ItemEntity::getRatingCount);
lwq.orderByDesc(ItemEntity::getView);
// 创建 Page 对象设置当前页和每页大小
@ -114,7 +131,7 @@ public class ItemServiceImpl extends CrudServiceImpl<ItemDao, ItemEntity, ItemDT
@Override
public List<ItemEntity> commit() {
LambdaQueryWrapper<ItemEntity> lwq = new LambdaQueryWrapper<>();
lwq.orderByDesc(ItemEntity::getRatingCount);
lwq.orderByDesc(ItemEntity::getView);
// 创建 Page 对象设置当前页和每页大小
Page<ItemEntity> page = new Page<>(1, 10); // 第1页每页10条

File diff suppressed because one or more lines are too long

View File

@ -1,7 +0,0 @@
-- 菜单初始SQL
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date)VALUES (1888815450461077506, 1067246875800000035, '电影表', 'item/item', NULL, 0, 'icon-desktop', 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077507, 1888815450461077506, '查看', NULL, 'item:item:page,item:item:info', 1, NULL, 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077508, 1888815450461077506, '新增', NULL, 'item:item:save', 1, NULL, 1, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077509, 1888815450461077506, '修改', NULL, 'item:item:update', 1, NULL, 2, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077510, 1888815450461077506, '删除', NULL, 'item:item:delete', 1, NULL, 3, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077511, 1888815450461077506, '导出', NULL, 'item:item:export', 1, NULL, 4, 1067246875800000001, now(), 1067246875800000001, now());

View File

@ -1,7 +0,0 @@
-- 菜单初始SQL
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date)VALUES (1888815450461077506, 1067246875800000035, '电影表', 'item/item', NULL, 0, 'icon-desktop', 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077507, 1888815450461077506, '查看', NULL, 'item:item:page,item:item:info', 1, NULL, 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077508, 1888815450461077506, '新增', NULL, 'item:item:save', 1, NULL, 1, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077509, 1888815450461077506, '修改', NULL, 'item:item:update', 1, NULL, 2, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077510, 1888815450461077506, '删除', NULL, 'item:item:delete', 1, NULL, 3, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077511, 1888815450461077506, '导出', NULL, 'item:item:export', 1, NULL, 4, 1067246875800000001, now(), 1067246875800000001, now());

View File

@ -1,7 +0,0 @@
-- 菜单初始SQL
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date)VALUES (1888815450461077506, 1067246875800000035, '电影表', 'item/item', NULL, 0, 'icon-desktop', 0, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077507, 1888815450461077506, '查看', NULL, 'item:item:page,item:item:info', 1, NULL, 0, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077508, 1888815450461077506, '新增', NULL, 'item:item:save', 1, NULL, 1, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077509, 1888815450461077506, '修改', NULL, 'item:item:update', 1, NULL, 2, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077510, 1888815450461077506, '删除', NULL, 'item:item:delete', 1, NULL, 3, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077511, 1888815450461077506, '导出', NULL, 'item:item:export', 1, NULL, 4, 1067246875800000001, CURRENT_DATE, 1067246875800000001, CURRENT_DATE);

View File

@ -1,7 +0,0 @@
-- 菜单初始SQL
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date)VALUES (1888815450461077506, 1067246875800000035, '电影表', 'item/item', NULL, 0, 'icon-desktop', 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077507, 1888815450461077506, '查看', NULL, 'item:item:page,item:item:info', 1, NULL, 0, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077508, 1888815450461077506, '新增', NULL, 'item:item:save', 1, NULL, 1, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077509, 1888815450461077506, '修改', NULL, 'item:item:update', 1, NULL, 2, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077510, 1888815450461077506, '删除', NULL, 'item:item:delete', 1, NULL, 3, 1067246875800000001, now(), 1067246875800000001, now());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077511, 1888815450461077506, '导出', NULL, 'item:item:export', 1, NULL, 4, 1067246875800000001, now(), 1067246875800000001, now());

View File

@ -1,7 +0,0 @@
-- 菜单初始SQL
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date)VALUES (1888815450461077506, 1067246875800000035, '电影表', 'item/item', NULL, 0, 'icon-desktop', 0, 1067246875800000001, getdate(), 1067246875800000001, getdate());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077507, 1888815450461077506, '查看', NULL, 'item:item:page,item:item:info', 1, NULL, 0, 1067246875800000001, getdate(), 1067246875800000001, getdate());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077508, 1888815450461077506, '新增', NULL, 'item:item:save', 1, NULL, 1, 1067246875800000001, getdate(), 1067246875800000001, getdate());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077509, 1888815450461077506, '修改', NULL, 'item:item:update', 1, NULL, 2, 1067246875800000001, getdate(), 1067246875800000001, getdate());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077510, 1888815450461077506, '删除', NULL, 'item:item:delete', 1, NULL, 3, 1067246875800000001, getdate(), 1067246875800000001, getdate());
INSERT INTO sys_menu(id, pid, name, url, permissions, menu_type, icon, sort, creator, create_date, updater, update_date) VALUES (1888815450461077511, 1888815450461077506, '导出', NULL, 'item:item:export', 1, NULL, 4, 1067246875800000001, getdate(), 1067246875800000001, getdate());

View File

@ -1,112 +0,0 @@
package io.common.modules.item.controller;
import io.common.common.annotation.LogOperation;
import io.common.common.constant.Constant;
import io.common.common.page.PageData;
import io.common.common.utils.ExcelUtils;
import io.common.common.utils.Result;
import io.common.common.validator.AssertUtils;
import io.common.common.validator.ValidatorUtils;
import io.common.common.validator.group.AddGroup;
import io.common.common.validator.group.DefaultGroup;
import io.common.common.validator.group.UpdateGroup;
import io.common.modules.item.dto.ItemDTO;
import io.common.modules.item.excel.ItemExcel;
import io.common.modules.item.service.ItemService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
@RestController
@RequestMapping("item/item")
@Tag(name="电影表")
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始", in = ParameterIn.QUERY, required = true, ref="int") ,
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY,required = true, ref="int") ,
@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:item:page")
public Result<PageData<ItemDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<ItemDTO> page = itemService.page(params);
return new Result<PageData<ItemDTO>>().ok(page);
}
@GetMapping("{id}")
@Operation(summary = "信息")
@RequiresPermissions("item:item:info")
public Result<ItemDTO> get(@PathVariable("id") Long id){
ItemDTO data = itemService.get(id);
return new Result<ItemDTO>().ok(data);
}
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@RequiresPermissions("item:item:save")
public Result save(@RequestBody ItemDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class);
itemService.save(dto);
return new Result();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@RequiresPermissions("item:item:update")
public Result update(@RequestBody ItemDTO dto){
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
itemService.update(dto);
return new Result();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("item:item:delete")
public Result delete(@RequestBody Long[] ids){
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
itemService.delete(ids);
return new Result();
}
@GetMapping("export")
@Operation(summary = "导出")
@LogOperation("导出")
@RequiresPermissions("item:item:export")
public void export(@Parameter(hidden = true) @RequestParam Map<String, Object> params, HttpServletResponse response) throws Exception {
List<ItemDTO> list = itemService.list(params);
ExcelUtils.exportExcelToTarget(response, null, "电影表", list, ItemExcel.class);
}
}

View File

@ -1,16 +0,0 @@
package io.common.modules.item.dao;
import io.common.common.dao.BaseDao;
import io.common.modules.item.entity.ItemEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
@Mapper
public interface ItemDao extends BaseDao<ItemEntity> {
}

View File

@ -1,58 +0,0 @@
package io.common.modules.item.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
/**
* 电影表
*/
@Data
@Schema(name = "电影表")
public class ItemDTO implements Serializable {
private static final long serialVersionUID = 1L;
@SchemaProperty(name = "编号")
private String id;
@SchemaProperty(name = "电影名称")
private String title;
@SchemaProperty(name = "电影副标题")
private String cardSubtitle;
@SchemaProperty(name = "评分")
private BigDecimal ratingValue;
@SchemaProperty(name = "评分人数")
private Integer ratingCount;
@SchemaProperty(name = "星级评分")
private BigDecimal ratingStarCount;
@SchemaProperty(name = "上映时间")
private unknowType year;
@SchemaProperty(name = "大图文件名")
private String largePic;
@SchemaProperty(name = "小图文件名")
private String normalPic;
@SchemaProperty(name = "国家")
private String country;
@SchemaProperty(name = "电影类型")
private String movieType;
@SchemaProperty(name = "导演")
private String director;
@SchemaProperty(name = "主演")
private String star;
}

View File

@ -1,71 +0,0 @@
package io.common.modules.item.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
@Data
@TableName("tb_item")
public class ItemEntity {
/**
* 编号
*/
private String id;
/**
* 电影名称
*/
private String title;
/**
* 电影副标题
*/
private String cardSubtitle;
/**
* 评分
*/
private BigDecimal ratingValue;
/**
* 评分人数
*/
private Integer ratingCount;
/**
* 星级评分
*/
private BigDecimal ratingStarCount;
/**
* 上映时间
*/
private unknowType year;
/**
* 大图文件名
*/
private String largePic;
/**
* 小图文件名
*/
private String normalPic;
/**
* 国家
*/
private String country;
/**
* 电影类型
*/
private String movieType;
/**
* 导演
*/
private String director;
/**
* 主演
*/
private String star;
}

View File

@ -1,47 +0,0 @@
package io.common.modules.item.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
@Data
public class ItemExcel {
@ExcelProperty(value = "编号")
private String id;
@ExcelProperty(value = "电影名称")
private String title;
@ExcelProperty(value = "电影副标题")
private String cardSubtitle;
@ExcelProperty(value = "评分")
private BigDecimal ratingValue;
@ExcelProperty(value = "评分人数")
private Integer ratingCount;
@ExcelProperty(value = "星级评分")
private BigDecimal ratingStarCount;
@ExcelProperty(value = "上映时间")
private unknowType year;
@ExcelProperty(value = "大图文件名")
private String largePic;
@ExcelProperty(value = "小图文件名")
private String normalPic;
@ExcelProperty(value = "国家")
private String country;
@ExcelProperty(value = "电影类型")
private String movieType;
@ExcelProperty(value = "导演")
private String director;
@ExcelProperty(value = "主演")
private String star;
}

View File

@ -1,15 +0,0 @@
package io.common.modules.item.service;
import io.common.common.service.CrudService;
import io.common.modules.item.dto.ItemDTO;
import io.common.modules.item.entity.ItemEntity;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
public interface ItemService extends CrudService<ItemEntity, ItemDTO> {
}

View File

@ -1,34 +0,0 @@
package io.common.modules.item.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.common.common.service.impl.CrudServiceImpl;
import io.common.modules.item.dao.ItemDao;
import io.common.modules.item.dto.ItemDTO;
import io.common.modules.item.entity.ItemEntity;
import io.common.modules.item.service.ItemService;
import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 电影表
*
* @author Mark #
* @since 1.0.0 2025-02-10
*/
@Service
public class ItemServiceImpl extends CrudServiceImpl<ItemDao, ItemEntity, ItemDTO> implements ItemService {
@Override
public QueryWrapper<ItemEntity> getWrapper(Map<String, Object> params){
String id = (String)params.get("id");
QueryWrapper<ItemEntity> wrapper = new QueryWrapper<>();
wrapper.eq(StrUtil.isNotBlank(id), "id", id);
return wrapper;
}
}

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.common.modules.item.dao.ItemDao">
<resultMap type="io.common.modules.item.entity.ItemEntity" id="itemMap">
<result property="id" column="id"/>
<result property="title" column="title"/>
<result property="cardSubtitle" column="card_subtitle"/>
<result property="ratingValue" column="rating_value"/>
<result property="ratingCount" column="rating_count"/>
<result property="ratingStarCount" column="rating_star_count"/>
<result property="year" column="year"/>
<result property="largePic" column="large_pic"/>
<result property="normalPic" column="normal_pic"/>
<result property="country" column="country"/>
<result property="movieType" column="movie_type"/>
<result property="director" column="director"/>
<result property="star" column="star"/>
</resultMap>
</mapper>

View File

@ -1,142 +0,0 @@
<template>
<el-dialog v-model="visible" :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form :model="dataForm" :rules="rules" ref="dataFormRef" @keyup.enter="dataFormSubmitHandle()" label-width="120px">
<el-form-item label="电影名称" prop="title">
<el-input v-model="dataForm.title" placeholder="电影名称"></el-input>
</el-form-item>
<el-form-item label="电影副标题" prop="cardSubtitle">
<el-input v-model="dataForm.cardSubtitle" placeholder="电影副标题"></el-input>
</el-form-item>
<el-form-item label="评分" prop="ratingValue">
<el-input v-model="dataForm.ratingValue" placeholder="评分"></el-input>
</el-form-item>
<el-form-item label="评分人数" prop="ratingCount">
<el-input v-model="dataForm.ratingCount" placeholder="评分人数"></el-input>
</el-form-item>
<el-form-item label="星级评分" prop="ratingStarCount">
<el-input v-model="dataForm.ratingStarCount" placeholder="星级评分"></el-input>
</el-form-item>
<el-form-item label="上映时间" prop="year">
<el-input v-model="dataForm.year" placeholder="上映时间"></el-input>
</el-form-item>
<el-form-item label="大图文件名" prop="largePic">
<el-input v-model="dataForm.largePic" placeholder="大图文件名"></el-input>
</el-form-item>
<el-form-item label="小图文件名" prop="normalPic">
<el-input v-model="dataForm.normalPic" placeholder="小图文件名"></el-input>
</el-form-item>
<el-form-item label="国家" prop="country">
<el-input v-model="dataForm.country" placeholder="国家"></el-input>
</el-form-item>
<el-form-item label="电影类型" prop="movieType">
<el-input v-model="dataForm.movieType" placeholder="电影类型"></el-input>
</el-form-item>
<el-form-item label="导演" prop="director">
<el-input v-model="dataForm.director" placeholder="导演"></el-input>
</el-form-item>
<el-form-item label="主演" prop="star">
<el-input v-model="dataForm.star" placeholder="主演"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmitHandle()">确定</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import baseService from "@/service/baseService";
import { ElMessage } from "element-plus";
const emit = defineEmits(["refreshDataList"]);
const visible = ref(false);
const dataFormRef = ref();
const dataForm = reactive({
id: '', title: '', cardSubtitle: '', ratingValue: '', ratingCount: '', ratingStarCount: '', year: '', largePic: '', normalPic: '', country: '', movieType: '', director: '', star: ''});
const rules = ref({
title: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
cardSubtitle: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
ratingValue: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
ratingCount: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
ratingStarCount: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
year: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
largePic: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
normalPic: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
country: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
movieType: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
director: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
],
star: [
{ required: true, message: '必填项不能为空', trigger: 'blur' }
]
});
const init = (id?: number) => {
visible.value = true;
dataForm.id = "";
//
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
if (id) {
getInfo(id);
}
};
//
const getInfo = (id: number) => {
baseService.get("/item/item/" + id).then((res) => {
Object.assign(dataForm, res.data);
});
};
//
const dataFormSubmitHandle = () => {
dataFormRef.value.validate((valid: boolean) => {
if (!valid) {
return false;
}
(!dataForm.id ? baseService.post : baseService.put)("/item/item", dataForm).then((res) => {
ElMessage.success({
message: '成功',
duration: 500,
onClose: () => {
visible.value = false;
emit("refreshDataList");
}
});
});
});
};
defineExpose({
init
});
</script>

View File

@ -1,58 +0,0 @@
<template>
<div class="mod-item__item">
<el-form :inline="true" :model="state.dataForm" @keyup.enter="state.getDataList()">
<el-form-item>
<el-button v-if="state.hasPermission('item:item:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
</el-form-item>
<el-form-item>
<el-button v-if="state.hasPermission('item:item:delete')" type="danger" @click="state.deleteHandle()">删除</el-button>
</el-form-item>
</el-form>
<el-table v-loading="state.dataListLoading" :data="state.dataList" border @selection-change="state.dataListSelectionChangeHandle" style="width: 100%">
<el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column>
<el-table-column prop="id" label="编号" header-align="center" align="center"></el-table-column>
<el-table-column prop="title" label="电影名称" header-align="center" align="center"></el-table-column>
<el-table-column prop="cardSubtitle" label="电影副标题" header-align="center" align="center"></el-table-column>
<el-table-column prop="ratingValue" label="评分" header-align="center" align="center"></el-table-column>
<el-table-column prop="ratingCount" label="评分人数" header-align="center" align="center"></el-table-column>
<el-table-column prop="ratingStarCount" label="星级评分" header-align="center" align="center"></el-table-column>
<el-table-column prop="year" label="上映时间" header-align="center" align="center"></el-table-column>
<el-table-column prop="largePic" label="大图文件名" header-align="center" align="center"></el-table-column>
<el-table-column prop="normalPic" label="小图文件名" header-align="center" align="center"></el-table-column>
<el-table-column prop="country" label="国家" header-align="center" align="center"></el-table-column>
<el-table-column prop="movieType" label="电影类型" header-align="center" align="center"></el-table-column>
<el-table-column prop="director" label="导演" header-align="center" align="center"></el-table-column>
<el-table-column prop="star" label="主演" header-align="center" align="center"></el-table-column>
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="150">
<template v-slot="scope">
<el-button v-if="state.hasPermission('item:item:update')" type="primary" link @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
<el-button v-if="state.hasPermission('item:item:delete')" type="primary" link @click="state.deleteHandle(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update ref="addOrUpdateRef" @refreshDataList="state.getDataList">确定</add-or-update>
</div>
</template>
<script lang="ts" setup>
import useView from "@/hooks/useView";
import { reactive, ref, toRefs } from "vue";
import AddOrUpdate from "./item-add-or-update.vue";
const view = reactive({
deleteIsBatch: true,
getDataListURL: "/item/item/page",
getDataListIsPage: true,
exportURL: "/item/item/export",
deleteURL: "/item/item"
});
const state = reactive({ ...useView(view), ...toRefs(view) });
const addOrUpdateRef = ref();
const addOrUpdateHandle = (id?: number) => {
addOrUpdateRef.value.init(id);
};
</script>

View File

@ -34,7 +34,7 @@ import java.util.stream.Collectors;
@CrossOrigin
@RestController
@RequestMapping("/api/item")
@Tag(name="电影")
@Tag(name="")
public class ItemController {
@Autowired
private ItemService itemService;
@ -48,13 +48,13 @@ public class ItemController {
@Parameter(name = Constant.PAGE, description = "当前页码从1开始", in = ParameterIn.QUERY, required = true, ref="int") ,
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY,required = true, ref="int") ,
@Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref="String") ,
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String")
@Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String"),
@Parameter(name = "status", description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref="String")
})
public Result<PageData<ItemDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<ItemDTO> page = itemService.page(params);
List<ItemDTO> list = page.getList().stream().map(e -> {
e.setLargePic(uploadUrl + "item/" + e.getLargePic());
e.setImage(uploadUrl + e.getImage());
return e;
}).collect(Collectors.toList());
page.setList(list);
@ -67,12 +67,6 @@ public class ItemController {
@Operation(summary = "查询收藏")
public Result<List<ItemEntity>> list(@Parameter(hidden = true) @RequestAttribute("userId") Long userId){
List<ItemEntity> page = itemService.listUser(userId);
List<ItemEntity> list = page.stream().map(e -> {
e.setLargePic(uploadUrl + "item/" + e.getLargePic());
e.setNormalPic(uploadUrl + "item/n_" + e.getNormalPic());
return e;
}).collect(Collectors.toList());
// List<ItemDTO> list = page.getList().stream().map(e -> {
// e.setLargePic(uploadUrl + "item/" + e.getLargePic());
@ -80,7 +74,7 @@ public class ItemController {
// return e;
// }).collect(Collectors.toList());
// page.setList(list);
return new Result<List<ItemEntity>>().ok(list);
return new Result<List<ItemEntity>>().ok(page);
}
@ -92,8 +86,8 @@ public class ItemController {
public Result<List<ItemEntity>> score(){
List<ItemEntity> list = itemService.score();
List<ItemEntity> res = list.stream().map(e -> {
e.setLargePic(uploadUrl + "item/" + e.getLargePic());
e.setNormalPic(uploadUrl + "item/n_" + e.getNormalPic());
e.setImage(uploadUrl + e.getImage());
return e;
}).collect(Collectors.toList());
return new Result<List<ItemEntity>>().ok(res);
@ -106,8 +100,7 @@ public class ItemController {
public Result<List<ItemEntity>> commit(){
List<ItemEntity> list = itemService.commit();
List<ItemEntity> res = list.stream().map(e -> {
e.setLargePic(uploadUrl + "item/" + e.getLargePic());
e.setNormalPic(uploadUrl + "item/n_" + e.getNormalPic());
e.setImage(uploadUrl + e.getImage());
return e;
}).collect(Collectors.toList());
return new Result<List<ItemEntity>>().ok(res);
@ -117,10 +110,9 @@ public class ItemController {
@Operation(summary = "信息")
public Result<ItemDTO> get(@PathVariable("id") Long id){
ItemDTO data = itemService.get(id);
data.setLargePic(uploadUrl + "item/" + data.getLargePic());
data.setNormalPic(uploadUrl + "item/n_" + data.getNormalPic());
data.setImage(uploadUrl + data.getImage());
data.setIsFavorite(userBehaviorService.getIsFavorite(data.getId(),1));
// data.setIsFavorite(userBehaviorService.getIsFavorite(data.getId(),1));
return new Result<ItemDTO>().ok(data);
}
@ -137,8 +129,7 @@ public class ItemController {
@Operation(summary = "修改")
public Result update(@RequestBody ItemDTO dto){ //效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class);
dto.setLargePic(uploadUrl + "item/" + dto.getLargePic());
dto.setNormalPic(uploadUrl + "item/n_" + dto.getNormalPic());
dto.setImage(uploadUrl + dto.getImage());
itemService.update(dto);
return new Result();
}

View File

@ -16,8 +16,12 @@ import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -31,6 +35,10 @@ public class SlidesFrontController {
@Autowired
private SlidesService slidesService;
@Value("${upload.url}")
private String uploadUrl;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@ -42,6 +50,13 @@ public class SlidesFrontController {
})
public Result<PageData<SlidesDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params){
PageData<SlidesDTO> page = slidesService.page(params);
List<SlidesDTO> list = page.getList().stream().map(e -> {
e.setPath(uploadUrl + e.getPath());
return e;
}).collect(Collectors.toList());
page.setList(list);
return new Result<PageData<SlidesDTO>>().ok(page);
}

View File

@ -34,7 +34,6 @@ public class UploadController {
file.transferTo(new File(path +"/"+ originFileName));
String url = uploadUrl + originFileName;
HashMap<String, String> map = new HashMap<>();
map.put("path",url);
map.put("absolute","upload/" + originFileName);
return new Result<>().ok(map);

View File

@ -6,9 +6,9 @@ spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ordinary.jimostudio.link:26449/bs_movies?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://47.94.76.54:23306/bookstore?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: minxianrui
password: x6dbfGN4s6YjcX8P
initial-size: 10
max-active: 100
min-idle: 10
@ -33,7 +33,7 @@ spring:
multi-statement-allow: true
web:
resources:
static-locations: "file:F:/upload/"
static-locations: "file:F:/2025/bookstore-springboot-vue/admin-ui/upload/"
upload:
path: F:\upload
url: https://localhost:10081/upload/
path: F:\2025\bookstore-springboot-vue\admin-ui\upload
url: http://localhost:18081/

View File

@ -7,31 +7,12 @@ server:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#MySQL配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.15.2:3306/bs_movies?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
url: jdbc:mysql://47.94.76.54:23306/bookstore?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: root
password: minxianrui
#达梦8
# driver-class-name: dm.jdbc.driver.DmDriver
# url: jdbc:dm://127.0.0.1:5236/renren_security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
# username: renren_security
# password: ren123456
#oracle配置
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@192.168.10.10:1521:xe
# username: renren_security
# password: 123456
#SQLServer配置
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://192.168.10.10:1433;DatabaseName=renren_security
# username: sa
# password: 123456
#PostgreSQL配置
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://192.168.10.10:5432/renren_security
# username: postgres
# password: 123456
password: x6dbfGN4s6YjcX8P
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
@ -50,6 +31,5 @@ pagehelper:
params: count=countSql
#指定数据库可选值有【mysql、oracle、sqlserver、postgresql、dm】
renren:
database: mysql

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -7,7 +7,7 @@ VITE_ADMIN_API_BASE_URL = http://localhost:18080
VITE_LOGIN_BG = "/login/e36341619bf8f04dcbdc6b01105a85a.png"
# 标题
VITE_APP_TITLE = 后台
VITE_APP_TITLE = 电影系统
# markdown 渲染支持
VITE_APP_MARKDOWN = true

2
ui/.gitignore vendored
View File

@ -9,4 +9,4 @@ type-router.d.ts
auto-imports.d.ts
.eslintrc-auto-import.json
vite.config.ts.timestamp*
.idea
.idea/

View File

@ -107,6 +107,13 @@
],
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.9.2"
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"element-plus": "^2.9.2",
"save": "^2.9.0",
"scss": "^0.2.4",
"use-element-plus-theme": "^0.0.5",
"v-charts": "^1.19.0",
"vite-plugin-theme": "^0.8.6"
}
}

10840
ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -193,8 +193,6 @@ export default function () {
if (env.VITE_APP_DEV_TOOLS) {
plugins.push(VueDevTools())
}
/**
* api
* https://github.com/antfu/unplugin-auto-import

BIN
ui/public/a1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
ui/public/p2915350868.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -2,6 +2,10 @@
<router-view />
</template>
<script lang="ts" setup>
</script>
<style>
p {
padding: 0 10px;

12
ui/src/api/itemApi.ts Normal file
View File

@ -0,0 +1,12 @@
/**
*
* @param data
*/
export function itemPage(data: any) {
return frontRequest.get("/api/item/page",{
params: data
})
}

View File

@ -1,16 +1,4 @@
/**
*
* @param data
*/
export function loginFront(data:any) {
frontRequest.post("/api/user/login", data).then(response =>{
const user = userStore()
user.frontToken = response.data.token
frontRequest.get("/api/user/userInfo").then(response =>{
user.frontUserInfo = response.data
})
})
}
/**
*
* @param data

View File

@ -0,0 +1,62 @@
<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,7 +1,6 @@
<template>
<div class="head">
<div class="head_l">
<!-- <img src="/icoimg.png" alt="收缩" />-->
</div>
<el-dropdown>
<div class="head_r">
@ -56,7 +55,6 @@
<el-input v-model="state.dynamicValidateForm.newPassword" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(formRef)">确定修改</el-button>
</el-form-item>
</el-form>
@ -73,7 +71,6 @@ const drawer = ref(false)
const state = reactive({
dynamicValidateForm:{}
})
/**
* 退出登录
*/
@ -83,7 +80,6 @@ const logout = () => {
router.push('/login');
})
}
/**
* 修改密码
* @param formEl
@ -93,7 +89,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
formEl.validate((valid) => {
if (valid) {
updatePasswordAdmin(state.dynamicValidateForm).then(result => {
ElMessage.success("修改成功")
})
}
})

View File

@ -0,0 +1,90 @@
<template>
<div>
<el-upload
class="avatar-uploader"
:action="state.path"
:show-file-list="false"
:on-success="handleSuccess"
:before-upload="beforeUpload"
>
<img v-if="state.imageUrl" :src="state.imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
imageUrl: {
type: String,
default: '',
},
})
// emit
import { UploadProps } from 'element-plus'
const emit = defineEmits();
const state = reactive({
imageUrl: '',
path:import.meta.env.VITE_API_FRONT_BASE_URL+"/api/upload"
})
// props.imageUrl
watch(
() => props.imageUrl, // imageUrl
(newVal, oldVal) => {
// console.log('imageUrl changed from', oldVal, 'to', newVal)
// state.imageUrl
state.imageUrl = newVal // imageUrl
}
)
onMounted(()=>{
console.log(props.imageUrl)
if (props.imageUrl){
state.imageUrl = props.imageUrl
}
})
// false
function beforeUpload(file) {
const isImage = file.type.startsWith('image/')
if (!isImage) {
ElMessage.error('只能上传图片文件!')
}
return isImage
}
//
const handleSuccess: UploadProps['onSuccess'] = (
response,
uploadFile
) => {
state.imageUrl = response.data.path
ElMessage.success('上传成功')
console.log("fanhui1",response)
// emit
emit('update:imageUrl', response.data.absolute);
}
</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);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar{
width: 178px;
height: 178px;
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<div >
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<!-- 内容-->
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css'
import { onBeforeUnmount, ref, shallowRef, reactive } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { ElMessage } from 'element-plus'
// shallowRef
const editorRef = shallowRef<InstanceType<typeof Editor> | null>(null)
const emit = defineEmits();
const state = reactive({
path: import.meta.env.VITE_API_FRONT_BASE_URL + '/api/upload',
})
const props = defineProps({
content: { type: String, default: '' },
});
// HTML
const valueHtml = ref<string>('')
// watch
watch(
() => props.content,
(newContent) => {
console.log('新值变化', newContent);
valueHtml.value = newContent;
emit( "update:content",valueHtml.value)
},
{ immediate: true } // immediate
);
watch(() => valueHtml.value,
(newContent) => {
emit( "update:content",valueHtml.value)
},
{ immediate: true } // immediate
);
//
const toolbarConfig = {
toolbarKeys: [
// key
"bold", //
"italic", //
"through", // 线
"underline", // 线
"bulletedList", //
"numberedList", //
"color", //
"fontSize", //
"lineHeight", //
"uploadImage", //
"delIndent", //
"indent", //
"deleteImage", //
"divider", // 线
"justifyCenter", //
"justifyJustify", //
"justifyLeft", //
"justifyRight", //
"undo", //
"redo", //
"clearStyle", //
],
};
//
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
server: state.path,
fieldName: 'file',
maxFileSize: 20 * 1024 * 1024, // 20MB
allowedFileTypes: ['image/jpeg', 'image/png', 'image/gif'], //
customInsert(res, insertFn) {
if (res.code == 0) {
insertFn(res.data, null, res.data);
ElMessage.success("上传成功")
}
}
},
},
}
//
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
//
const handleCreated = (editor: InstanceType<typeof Editor>) => {
editorRef.value = editor
}
// default simple
const mode = 'default'
</script>

View File

@ -3,7 +3,7 @@
<el-col :span="24">
<el-carousel :height="height" motion-blur>
<el-carousel-item v-for="item in state.item" :key="item">
<el-image :src="item.img" fit="fill" />
<el-image :src="item.path" fit="fill" />
</el-carousel-item>
</el-carousel>
</el-col>
@ -19,13 +19,18 @@ const props = defineProps({
},
})
const state = reactive({
item:[
{img:"https://img2.epetbar.com/2024-12/31/10/973909536cb5d8845253a118bd2ace20.jpg?x-oss-process=style/water"},
{img:"https://img2.epetbar.com/2024-12/31/10/973909536cb5d8845253a118bd2ace20.jpg?x-oss-process=style/water"},
{img:"https://img2.epetbar.com/2024-12/31/10/973909536cb5d8845253a118bd2ace20.jpg?x-oss-process=style/water"},
{img:"https://img2.epetbar.com/2024-12/31/10/973909536cb5d8845253a118bd2ace20.jpg?x-oss-process=style/water"},
]
item:[]
})
function init() {
}
onMounted(()=>{
frontRequest.get("/api/slides/page").then(res =>{
state.item = res.data.list
})
})
</script>
<style scoped>
:deep(.el-image){

View File

@ -1,11 +1,20 @@
<!--详情底部-->
<template>
<el-row :gutter="20">
<el-col :span="14">
<el-row>
<el-col :span="24">
<el-input v-model="state.content" class="commit " placeholder="发表神秘评论" />
<div style="margin-bottom: 10px">
<el-button color="#626aef" :dark="isDark" plain @click="comment">发表</el-button>
</div>
</el-col>
</el-row>
<!--评论区-->
<div class="comment" v-for="item in state.commentList">
<el-row :gutter="20">
<el-col :span="3">{{ item.userName }}</el-col>
<el-col :span="3" ><span style="color: #598bd3;">{{ item.nickName }}</span></el-col>
<el-col :span="6">
<span class="time">
{{item.createTime}}
@ -17,6 +26,7 @@
</div>
<el-divider />
</div>
</el-col>
<!--热门-->
<el-col :span="10">
@ -26,14 +36,38 @@
</template>
<script setup lang="ts">
import Item2 from '~/components/front/item2.vue'
import { useRoute } from 'vue-router';
const route = useRoute();
const state =reactive(<any>{
commentList:[
{userName:"小人",content:"的电影影视作品,影片的导演是凯莉",createTime:"2024-09-30 23:15:50"},
{userName:"小人",content:"的电影影视作品,影片的导演是凯莉",createTime:"2024-09-30 23:15:50"},
{userName:"小人",content:"的电影影视作品,影片的导演是凯莉",createTime:"2024-09-30 23:15:50"},
{userName:"小人",content:"的电影影视作品,影片的导演是凯莉",createTime:"2024-09-30 23:15:50"},
{userName:"小人",content:"的电影影视作品,影片的导演是凯莉",createTime:"2024-09-30 23:15:50"},
]
commentList:[],
content:""
})
const itemId = route.params.id;
function init() {
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>
@ -43,15 +77,27 @@ const state =reactive(<any>{
word-wrap: break-word;
.content{
padding-top: 15px;
font-size: 14px;
color: #111;
text-indent: 2em; /* 中文缩进 */
}
.time{
color: #aaa;
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

@ -3,22 +3,20 @@
<el-row :gutter="20" class="info">
<el-col :span="10">
<!-- 详情图片-->
<el-image :src="state.info.url" fit="contain" />
<el-image :src="state.info.largePic" fit="contain" />
</el-col>
<el-col :span="14">
<!-- 标题-->
<h1>{{state.info.title}}</h1>
<h1>{{state.info.title}} ( {{state.info.year}} ) </h1>
<!-- 简介-->
<div class="introduction" v-if="state.info.introduction">
{{state.info.introduction}}
</div>
<!-- 价格-->
<div class="tag" v-if="state.info.price">
<span style="color: red;font-size: 26px"> <span style="font-weight: 700">{{state.info.price}}</span></span>
</div>
<div class="introduction" v-if="state.info.country"> <span style="font-weight: 700">编剧:</span> {{state.info.actor}} </div>
<div class="introduction" v-if="state.info.country"> <span style="font-weight: 700">导演: </span> {{state.info.director}} </div>
<div class="introduction" v-if="state.info.country"> <span style="font-weight: 700">制片国家/地区: </span> {{state.info.country}} </div>
<div class="introduction" v-if="state.info.country"> <span style="font-weight: 700">电影评分: </span> {{state.info.ratingValue}} </div>
<!--标签-->
<div class="tag">
<el-tag v-for="tag in state.info.tags" :key="tag">{{tag}}</el-tag>
<span style="font-weight: 700">导演: </span> <el-tag v-for="tag in (state.info.movieType || '').split(' ')" :key="tag" style="margin-right: 10px">{{ tag }}</el-tag>
</div>
<!--操作-->
<div class="operate">
@ -27,8 +25,8 @@
<!-- 立即购买-->
<el-button type="primary" @click="handleBuy" v-if="state.info.price">立即购买</el-button>
<!-- 收藏-->
<el-button type="primary" @click="handleFavorite">
{{state.isFavorite?'已收藏':'收藏'}}
<el-button style="width: 220px" type="primary" @click="handleFavorite">
{{state.info.isFavorite? '已收藏':'收藏'}}
</el-button>
</div>
</el-col>
@ -70,10 +68,12 @@
<script setup lang="ts">
import { reactive } from 'vue'
import type { FormInstance } from 'element-plus'
const buyDrawer = ref(false)
const formRef = ref<FormInstance>()
import { useRoute } from 'vue-router';
const route = useRoute();
const itemId = route.params.id;
const state =reactive(<any>{
info:{
id:1122334545,
@ -92,6 +92,39 @@ const state =reactive(<any>{
}
})
// route.params.id
watch( () => route.params.id,
(newId) => {
window.location.href = `/info/${newId}`
},
{ deep: true }
);
function init() {
frontRequest.get(`/api/item/${itemId}`).then(response =>{
state.info = response.data
})
}
onMounted(()=>{
init()
})
/**
* 用户收藏
*/
function handleFavorite() {
if (state.info.isFavorite){
frontRequest.post("/api/behavior/delete",{itemId:itemId,type:1}).then(res =>{
ElMessage.success("取消成功")
init()
})
}else {
frontRequest.post("/api/behavior",{itemId:itemId,type:1}).then(res =>{
ElMessage.success("收藏成功")
init()
})
}
}
/**
* 购买按钮提交
* @param formEl
@ -107,7 +140,6 @@ const buySubmitForm = (formEl: FormInstance | undefined) => {
})
}
/**
* 购买按钮
*/
@ -130,13 +162,13 @@ const handleChange = () => {
padding: 20px;
h1{
color: #11192d;
font-size: 20px;
font-size: 38px;
font-weight: 600;
margin-bottom: 20px;
}
.introduction{
font-size: 14px;
text-indent: 2em;
margin-bottom: 20px;
}
.tag{
@ -147,7 +179,6 @@ const handleChange = () => {
border-radius: 10px;
height: 420px;
margin-left: 30px;
}
</style>

View File

@ -1,27 +1,53 @@
<!--商品-->
<template>
<el-row v-if="isPage">
<el-col :span="24">
<div style="padding: 10px 10px 10px 10px;width: 350px;margin: 0 auto" >
<el-input
v-model="state.page.title"
style="max-width: 600px"
placeholder="寻找好图片"
class="input-with-select"
clearable
@input="init"
>
<template #append>
<el-button :icon="Search" @click="init"/>
</template>
</el-input>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-space wrap>
<el-card
v-for="i in 5" :key="i"
v-for="item in getList" :key="item.id"
class="box-card"
@click="to()"
@click="to(item.id)"
shadow="hover" >
<!-- 图片-->
<el-image src="https://img2.epetbar.com/common/upload/commonfile/2020/03/20/0104650_205628.jpg" fit="fill" />
<el-image :src="item.image" fit="fill" />
<!-- 标题-->
<div class="item-title ">澳大利亚原装进口自然馈赠Natures Gift 牛肉配方成犬粮 18kg</div>
<div class="item-title ">{{ item.title }}</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>
<span class="price-now ft16 " style="line-height: 1;">
{{item.price}}
<!-- <el-rate-->
<!-- v-model="item.ratingStarCount"-->
<!-- disabled-->
<!-- show-score-->
<!-- text-color="#ff9900"-->
<!-- score-template="{value}"-->
<!-- />-->
</span>
</div>
</el-card>
</el-space>
</el-col>
</el-row>
<!-- <el-row class="pagination-container">-->
<el-row v-if="isPage" class="pagination-container">
<el-col :span="24">
@ -29,30 +55,69 @@
background
layout="prev, pager, next"
@current-change="handleCurrentChange"
:total="1000" />
:total="state.total" />
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { itemPage } from '~/api/itemApi'
import { Search } from '@element-plus/icons-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:""
},
getList:[]
})
/**
* 初始化
*/
function init() {
if (props.isPage){
itemPage(state.page).then(res =>{
state.getList = res.data.list
state.total = res.data.total
})
}
}
/**
*分页
* @param val
*/
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
state.page.page = val
init()
}
const to = () => {
router.push("/front/info")
/**
* 跳转
*/
const to = (id:number) => {
router.push(`/info/${id}`)
}
onMounted(()=>{
init()
})
</script>
<style scoped>
.box-card{
@ -76,6 +141,7 @@ const to = () => {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
}
.price-box{
text-align: center;

View File

@ -4,18 +4,18 @@
<el-col :span="24">
<el-space wrap>
<el-card
v-for="i in 5" :key="i"
v-for="item in state.commit" :key="state.id"
class="box-card"
@click="to()"
@click="to(item.id)"
shadow="hover" >
<!--图片-->
<img class="img" src="https://img2.epetbar.com/common/upload/commonfile/2020/03/20/0104650_205628.jpg" fit="fill" />
<img class="img" :src="item.largePic" fit="fill" />
<!-- 标题-->
<div class="item-title ">澳大利亚原装进口自然馈赠Natures Gift 牛肉配方成犬粮 18kg</div>
<div class="item-title ">{{item.title}}</div>
<!-- 价格&& 评分-->
<div class="price-box">
<span class="price-now ft16 " >¥998.00</span>
<span class="ft14 c999 price-old" >¥1098.00</span>
<span class="price-now ft16 " >{{item.movieType}}</span>
<span class="ft14 c999 price-old" >{{item.ratingValue}} </span>
</div>
</el-card>
</el-space>
@ -25,9 +25,24 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const to = () => {
router.push("/front/info")
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{
@ -53,6 +68,7 @@ const to = () => {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-align: center;
}
.price-box{
text-align: center;
@ -68,4 +84,7 @@ const to = () => {
.ft14 {
font-size: 14px;
}
.price-old{
color: #fce04b;
}
</style>

View File

@ -1,13 +1,27 @@
<!--商品-->
<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">
<el-image style="width: 232px;height: 300px" v-if="index === 0 || index === 5" src="https://img2.epetbar.com/common/upload/commonfile/2020/03/20/0104650_205628.jpg" fit="fill" />
<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"
@ -56,12 +70,17 @@ const to = () => {
width: 232px;
height: 300px
}
.box-card1{
cursor: pointer;
width: 100%;
height: 300px
}
:deep(.el-image){
display: block;
width: 170px;
height: 180px;
margin: 0 auto;
margin-bottom: 20px;
}
.item-title{
color: #333;

View File

@ -20,25 +20,27 @@
</el-menu>
</el-col>
<el-col :span="3">
<el-button v-if="userStore().frontUserInfo" style="margin-top: 12px" type="primary" round @click="router.push('/login')">登录</el-button>
<el-button v-if="!userStore().frontIsLogin"
style="margin-top: 12px" type="primary"
round @click="router.push('/login')">登录</el-button>
<el-dropdown v-else>
<el-row :gutter="20">
<el-col :span="8">
<!-- <el-avatar :src="userStore().frontUserInfo.avatar" />-->
</el-col>
<el-col :span="16">
<h6>{{ userStore().frontUserInfo.username }}</h6>
<el-col :span="24">
<h6>{{ userStore().frontUserInfo.nickName }}</h6>
</el-col>
</el-row>
<template #dropdown>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item @click="to">个人中心</el-dropdown-item>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { getFrontList } from '~/utils/utils'
@ -58,6 +60,10 @@ const logout = () => {
router.push('/login')
})
}
function to() {
router.push('/user')
}
</script>
<style scoped>
:deep(.el-menu--horizontal) {

View File

@ -7,6 +7,7 @@ export const frontRequest = axios.create({
frontRequest.interceptors.request.use(
function (config) {
const token = userStore().frontToken
if (token !== null || token !== undefined) {
//添加header
config.headers.Authorization = token

View File

@ -1,164 +1,60 @@
<template>
<div class="main">
<div class="nav_left">
<div class="logo">
<img src="/logo.png" alt="后台管理系统">
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">导航管理</div>
<div class="rArrl"></div>
</div>
<div class="nav_li">
<ul>
<li v-for="r of routes" :key="r.path">
<img src="/logo.png" alt="图标"><RouterLink style="width: 100%; height: 45px; line-height: 45px" :to="r.path">{{ te(r.name) ? t(r.name) : r.name }}</RouterLink>
</li>
</ul>
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">权限管理</div>
<div class="rArrl"></div>
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">模型设置</div>
<div class="rArrl"></div>
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">内容管理</div>
<div class="rArrl"></div>
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">会员管理</div>
<div class="rArrl"></div>
</div>
<div class="nav_list">
<div class="nav_title"><img src="/logo.png" alt="图标">模版管理</div>
<div class="rArrl"></div>
</div>
</div>
<div class="nav_right">
<Heads />
<div class="content">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</div>
</div>
<!-- <Navigation />-->
<!-- <div class="w-screen flex flex-col items-center justify-center">-->
<!-- <router-view v-slot="{ Component }">-->
<!-- <transition name="fade" mode="out-in">-->
<!-- <component :is="Component" />-->
<!-- </transition>-->
<!-- </router-view>-->
<!-- </div>-->
<el-container>
<el-header>
<heads></heads>
</el-header>
<el-container>
<el-aside width="200px" >
<el-menu
:default-active="state.activeIndex"
router
@select="handleSelect"
>
<el-menu-item
v-for="r in getAdminList()"
:key="r.name"
:index="r.path"
>
<component class="icons" :is="r.icon" />
<template #title>{{ r.name }}</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="main">
<router-view ></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import { getAdminList} from '~/utils/utils'
import { useRoute } from 'vue-router'
//
const route = useRoute()
const state = reactive({
activeIndex:'/admin'
})
const handleSelect = (key: string, keyPath: string[]) => {
state.activeIndex = key
}
onMounted(()=>{
// activeIndexpath便<EFBFBD><EFBFBD>
state.activeIndex = route.path
})
</script>
<style scoped>
.main{
width: 100%;
display: flex;
justify-content: space-between;
height: 100vh;
overflow: hidden;
.nav_left{
width: 15%;
height: 100vh;
background: #111c43;
overflow-y: auto;
.logo{
width: 100%;
height: 60px;
border-bottom: 0.5px solid #293356;
display: flex;
align-items: center;
justify-content: center;
}
.logo img{
width: 85%;
height: 40px;
background: #293356;
color: #FFFFFF;
font-size: 12px;
}
.nav_list{
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
height: 45px;
.nav_title{
font-size: 14px;
color: #a3aed1;
display: flex;
align-items: center;
}
.nav_title img{
width: 20px;
height: 20px;
color: #333333;
margin-right: 8px;
}
.rArrl{
width: 7px;
height: 7px;
border-top: 1px solid #a3aed1;
border-right: 1px solid #a3aed1;
margin-top: 9px;
-webkit-transform: translate3d(0, -50%, 0) rotate(45deg);
transform: translate3d(0, -50%, 0) rotate(45deg);
}
}
.nav_li{
width: 100%;
padding-left: 16%;
background: #1a223f;
}
.nav_li ul li{
font-size: 12px;
color: #a3aed1;
display: flex;
align-items: center;
height: 45px;
width: 100%;
}
.nav_li ul li img{
width: 20px;
height: 20px;
color: #333333;
margin-right: 8px;
}
}
.nav_right{
width: 85%;
height: 100vh;
background: #f0f1f7;
.content{
width: 100%;
height: auto;
overflow-y: auto;
}
}
height: calc(100vh - 80px);
background-color: #f2f4f8;
}
.icons{
width: 18px;
height: 18px;
margin-right: 5px;
}
</style>
<script setup lang="ts">
import Heads from '~/components/Heads.vue'
import { getRoutes } from '@/plugins/router'
const { te, t } = useI18n()
const routes = getRoutes()
.filter((r) => !r.path.includes('notFound'))
.map((r) => {
let { path, name } = r
if (path === safeResolve('/')) {
return { path, name: 'home' }
}
if (!name) {
name = path
}
return { path, name: name.toString().slice(1).replaceAll('/', ' · ') }
})
const $route = useRoute()
</script>

View File

@ -1,59 +1,5 @@
<template>
<el-container>
<el-header>
<heads></heads>
</el-header>
<el-container>
<el-aside width="200px" >
<el-menu
:default-active="state.activeIndex"
router
@select="handleSelect"
>
<el-menu-item
v-for="r in getAdminList()"
:key="r.name"
:index="r.path"
>
<component class="icons" :is="r.icon" />
<template #title>{{ r.name }}</template>
</el-menu-item>
</el-menu>
</el-aside>
<el-main class="main">
<router-view ></router-view>
</el-main>
</el-container>
</el-container>
<router-view />
</template>
<script setup lang="ts">
import { getAdminList} from '~/utils/utils'
import { useRoute } from 'vue-router'
//
const route = useRoute()
const state = reactive({
activeIndex:'/admin'
})
const handleSelect = (key: string, keyPath: string[]) => {
state.activeIndex = key
}
onMounted(()=>{
// activeIndexpath便<EFBFBD><EFBFBD>
state.activeIndex = route.path
})
</script>
<style scoped>
.main{
width: 100%;
height: calc(100vh - 80px);
background-color: #f2f4f8;
}
.icons{
width: 18px;
height: 18px;
margin-right: 5px;
}
</style>

View File

@ -4,7 +4,7 @@
<div class="common-layout">
<el-container>
<el-header>
<nav-navigation1></nav-navigation1>
<nav-navigation></nav-navigation>
</el-header>
<el-main class="main">
<div class="container">

View File

@ -3,14 +3,14 @@
<div class="common-layout">
<el-container>
<el-header>
<nav-navigation1></nav-navigation1>
<nav-navigation></nav-navigation>
</el-header>
<el-main class="main">
<div class="container">
<el-row :gutter="20">
<el-col :span="6">
<el-menu
default-active=""
:default-active="state.activeIndex"
router
>
<el-menu-item v-for="item in state.userMenu" :index="item.path">
@ -28,19 +28,19 @@
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive({
activeIndex:'/user',
userMenu:[
{name: '个人中心', path: '/front/user'},
{name: '用户订单', path: '/front/user/order'},
{name: '个人中心', path: '/user'},
{name: '用户收藏', path: '/order'},
{name: '用户文创', path: '/fronttopic'},
]
})
</script>
<style scoped>
.main{
width: 100%;
height: calc(100vh - 60px);

198
ui/src/pages/admin/cate.vue Normal file
View File

@ -0,0 +1,198 @@
<template>
<div>
<!--查交易记录的按钮-->
<el-button type="primary" round @click="openAddDialog" size="small">添加</el-button>
<!--表格-->
<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:{}
})
//
function handleImageUrl(path: string) {
state.formData.image = path
}
//
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

@ -0,0 +1,272 @@
<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" width="120px"/>
<el-table-column prop="itemDTO.title" label="电影名称" align="center" width="120px"/>
<el-table-column prop="nickName" label="用户昵称" align="center" width="120px" />
<el-table-column prop="itemDTO.largePic" label="电影图片" align="center" width="100" >
<template #default="{ row }">
<!-- 点击图片后显示弹框预览 -->
<el-image
:src="row.itemDTO.largePic"
fit="cover"
/>
</template>
</el-table-column>
<el-table-column prop="score" label="评分" align="center" width="120px"/>
<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>
<!--列表结束-->
<el-table-column label="操作" align="center" width="120px">
<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="formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="电影名称" prop="title" :rules="[{ required: true, message: '请输入电影名称', trigger: 'blur' }]">
<el-input v-model="formData.title"/>
</el-form-item>
<el-form-item label="电影副标题" prop="cardSubtitle" :rules="[{ required: true, message: '请输入电影副标题', trigger: 'blur' }]">
<el-input v-model="formData.cardSubtitle"/>
</el-form-item>
<el-form-item label="国家" prop="country" :rules="[{ required: true, message: '请输入国家', trigger: 'blur' }]">
<el-input v-model="formData.country"/>
</el-form-item>
<el-form-item label="电影类型" prop="movieType" :rules="[{ required: true, message: '请输入电影类型', trigger: 'blur' }]">
<el-input v-model="formData.movieType" placeholder="多个请空格分隔"/>
</el-form-item>
<el-form-item label="导演" prop="director" :rules="[{ required: true, message: '请输入导演', trigger: 'blur' }]">
<el-input v-model="formData.director" placeholder="多个请空格分隔"/>
</el-form-item>
<el-form-item label="主演" prop="actor" :rules="[{ required: true, message: '请输入主演', trigger: 'blur' }]">
<el-input v-model="formData.actor" placeholder="多个请空格分隔"/>
</el-form-item>
<el-form-item label="上映时间" prop="year" :rules="[{ required: true, message: '请输入上映时间', trigger: 'blur' }]">
<el-select v-model="formData.year" placeholder="请选择上映年份">
<!-- 使用 v-for 动态生成年份列表 -->
<el-option
v-for="year in years"
:key="year"
:label="year"
:value="year"
/>
</el-select>
</el-form-item>
<el-form-item label="图片" prop="largePic" :rules="[{ required: true, message: '请插入图片', trigger: 'blur' }]">
<el-upload
class="avatar-uploader"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<img v-if="formData.largePic" :src="formData.largePic" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="评分" prop="ratingValue" :rules="[{ required: true, message: '请输入评分', trigger: 'blur' }]">
<el-input-number v-model="formData.ratingValue" :min="1" :max="10" :precision="1" />
</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 { UploadProps } from 'element-plus'
import type { FormInstance } from 'element-plus'
const formRef = ref<FormInstance>()
const state = reactive({
route:"sys/comment",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
title:"",
type:"0",
}
})
// 1990-2015
const years = [];
for (let year = 1990; year <= 2015; year++) {
years.push(year.toString());
}
const handleAvatarSuccess: UploadProps['onSuccess'] = (
response,
uploadFile
) => {
formData.largePic = URL.createObjectURL(uploadFile.raw!)
}
//
let formData = reactive(<any>{})
//
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 openAddDialog = () => {
formData = {}
state.dialogVisible = true
}
//
const edit = (row: any) => {
formData = row
state.dialogVisible = true
}
//
const saveTransaction = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
if (formData.id) {
//
adminRequest.put(`${state.route}`, formData)
} else {
//
adminRequest.post(`${state.route}`, 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,100 +1,122 @@
<template>
<!-- 客户类型销售情况饼图 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="customerTypeSalesOption" autoresize />
<el-card>
<template #header>
<div class="card-header">
<span>平台情况</span>
</div>
</template>
<el-row :gutter="20">
<el-col v-for="item in state.list" :span="12" :key="item.name">
<div class="list-box">
<p class="first-p">{{ item.name }}</p>
<p class="second-p">
<span>{{ item.value }}</span>{{ item.tag }}
</p>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<!-- 客户购买频次周期金额漏斗图 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="customerPurchaseFunnelOption" autoresize />
<bar-chart />
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import { PieChart, FunnelChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { use } from 'echarts/core'
// 使
use([CanvasRenderer, PieChart, FunnelChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
import BarChart from '~/components/BarChart.vue'
//
const customerData = ref([
{ customerType: '家庭用户', salesAmount: 5000 },
{ customerType: '商用用户', salesAmount: 8000 },
{ customerType: '工业用户', salesAmount: 12000 },
// ...
])
const state = reactive(<any>{
list: [],
row: []
});
const purchaseData = ref([
{ stage: '潜在客户', value: 300 },
{ stage: '关注产品', value: 200 },
{ stage: '询价客户', value: 150 },
{ stage: '下单客户', value: 100 },
{ stage: '支付客户', value: 70 },
// ...
])
//
const customerTypeSalesOption = ref({
title: { text: '客户类型销售情况', left: 'center' },
tooltip: { trigger: 'item' },
legend: {
orient: 'vertical',
left: 'left',
data: customerData.value.map(item => item.customerType)
},
series: [
{
name: '销售额',
type: 'pie',
radius: '55%',
data: customerData.value.map(item => ({
value: item.salesAmount,
name: item.customerType
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
//
onMounted(() => {
adminRequest.get("/sys/item/query").then(res => {
state.list = res.data;
});
});
const chartData = ref({
columns: ['date', 'sales'],
rows: [
{ date: '2021-01-01', sales: 100 },
{ date: '2021-02-01', sales: 200 },
{ date: '2021-03-01', sales: 150 },
{ date: '2021-04-01', sales: 220 },
{ date: '2021-05-01', sales: 180 }
]
})
});
//
const customerPurchaseFunnelOption = ref({
title: { text: '客户购买漏斗分析', left: 'center' },
tooltip: { trigger: 'item' },
legend: {
data: ['客户转化情况']
//
const chartSettings = ref({
xAxis: {
type: 'category',
name: '日期'
},
series: [
{
name: '客户转化情况',
type: 'funnel',
left: '10%',
width: '80%',
data: purchaseData.value.map(item => ({
value: item.value,
name: item.stage
}))
}
]
})
yAxis: {
type: 'value',
name: '销售量'
},
title: {
text: '销售趋势'
},
tooltip: {
trigger: 'axis'
}
});
</script>
<style scoped>
.chart {
height: 400px;
}
.list-box {
width: 100%;
height: 150px;
border-radius: 10px;
background-image: url('/a1.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.first-p {
color: white;
padding-bottom: 0.5vw;
font-size: 1.5vw;
text-align: center;
padding-top: 30px;
}
.second-p {
color: white;
display: flex;
margin-bottom: 5px;
font-size: 1.4vw;
align-items: center;
justify-content: center;
span {
margin-right: 0.3vw;
font-size: 2vw;
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "admin"
}
}
</route>

View File

@ -5,8 +5,8 @@
<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.itemId" placeholder="请输入序号" clearable @input="init" />
<el-form-item label="图书名称">
<el-input v-model="state.query.title" placeholder="请输入图书名称" clearable @input="init" />
</el-form-item>
</el-form>
</el-col>
@ -14,133 +14,141 @@
<!--表格-->
<el-row>
<el-col :span="24">
<el-table :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="id" label="标号"/>
<el-table-column prop="itemId" label="序号"/>
<el-table-column prop="orderId" label="订单唯一标识"/>
<el-table-column prop="orderDistance" label="订单距离"/>
<el-table-column prop="orderStartTime" label="订单开始时间"/>
<el-table-column prop="orderEndTime" label="订单结束时间"/>
<el-table-column prop="longitude" label="经度"/>
<el-table-column prop="dimension" label="维度"/>
<el-table-column prop="insertTime" label="插入时间"/>
<el-table-column prop="carId" label="车辆编号"/>
<!--列表结束-->
<el-table-column label="操作">
<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-tabs v-model="state.query.status" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane v-for="item in state.getStatus" :label="item.name" :name="item.status">
<el-table v-loading="loading" :data="state.getList">
<!-- 列表结开始-->
<el-table-column prop="id" label="编号" align="center" />
<el-table-column prop="title" label="名称" align="center" />
<el-table-column prop="categoryName" label="类别" align="center" />
<el-table-column prop="largePic" label="图片" align="center" >
<template #default="{ row }">
<!-- 点击图片后显示弹框预览 -->
<el-image
style="width: 50px;height: 50px"
: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="stockQuantity" label="库存数量" align="center" />
<el-table-column prop="view" label="预览" align="center" />
<el-table-column prop="tag" label="作者" align="center" />
<el-table-column prop="sort" label="排序" align="center" />
<!--列表结束-->
<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-tab-pane>
</el-tabs>
<!-- 分页控件 -->
<el-pagination
v-if="state.query.total > 0"
:current-page="state.query.page"
:page-size="state.query.size"
: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="formData" ref="form" label-width="100px">
<el-dialog v-model="state.dialogVisible" title="新增" width="50%">
<el-form
:model="formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="图片" prop="image" :rules="[{ required: true, message: '请插入图片', trigger: 'blur' }]">
<el-form-item label="标号" prop="id" :rules="[{ required: true, message: '请输入标号', trigger: 'blur' }]">
<el-input v-model="formData.id" />
<image-upload @update:imageUrl="handleImageUrl" :image-url="state.formData.image"></image-upload>
</el-form-item>
<el-form-item label="图书名称" prop="title" :rules="[{ required: true, message: '请输入图书名称', trigger: 'blur' }]">
<el-input v-model="formData.title"/>
</el-form-item>
<el-form-item label="序号" prop="itemId" :rules="[{ required: true, message: '请输入序号', trigger: 'blur' }]">
<el-input v-model="formData.itemId" />
<el-form-item label="排行榜" prop="sort" :rules="[{ required: true, message: '请输入排行榜', trigger: 'blur' }]">
<el-input-number v-model="state.formData.sort" :min="1" :max="500000" />
</el-form-item>
<el-form-item label="订单唯一标识" prop="orderId" :rules="[{ required: true, message: '请输入订单唯一标识', trigger: 'blur' }]">
<el-input v-model="formData.orderId" />
</el-form-item>
<el-form-item label="订单距离" prop="orderDistance" :rules="[{ required: true, message: '请输入订单距离', trigger: 'blur' }]">
<el-input v-model="formData.orderDistance" />
</el-form-item>
<el-form-item label="订单开始时间" prop="orderStartTime" :rules="[{ required: true, message: '请输入订单开始时间', trigger: 'blur' }]">
<el-input v-model="formData.orderStartTime" />
</el-form-item>
<el-form-item label="订单结束时间" prop="orderEndTime" :rules="[{ required: true, message: '请输入订单结束时间', trigger: 'blur' }]">
<el-input v-model="formData.orderEndTime" />
</el-form-item>
<el-form-item label="经度" prop="longitude" :rules="[{ required: true, message: '请输入经度', trigger: 'blur' }]">
<el-input v-model="formData.longitude" />
</el-form-item>
<el-form-item label="维度" prop="dimension" :rules="[{ required: true, message: '请输入维度', trigger: 'blur' }]">
<el-input v-model="formData.dimension" />
</el-form-item>
<el-form-item label="插入时间" prop="insertTime" :rules="[{ required: true, message: '请输入插入时间', trigger: 'blur' }]">
<el-input v-model="formData.insertTime" />
</el-form-item>
<el-form-item label="车辆编号" prop="carId" :rules="[{ required: true, message: '请输入车辆编号', trigger: 'blur' }]">
<el-input v-model="formData.carId" />
</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">保存</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({
route:"/api/transactions",
route:"sys/item",
getStatus:['上架','下架','库存警告','售空'],
getStatus:[
{name:"上架",status:0},
{name:"下架",status:1},
{name:"库存警告",status:2},
{name:"售空",status:3},
],
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
size: 10, //
itemId:"",
}
limit: 5, //
title:"",
status:0,
},
formData:{}
})
//
let formData = reactive(<any>{})
//
function handleImageUrl(path: string) {
state.formData.path = path
}
//
const init = () => {
adminRequest.get(`${state.route}`, {
adminRequest.get(`${state.route}/page`, {
params: state.query
}).then((res:any) => {
state.getList = res.data.data
state.query.total = res.data.page.total
state.getList = res.data.list
state.query.total = res.data.total
}).finally(() => {
loading.value = false
})
}
//
const openAddDialog = () => {
formData = {}
formData = {
sort:1
}
state.dialogVisible = true
}
//
@ -148,21 +156,30 @@ const edit = (row: any) => {
formData = row
state.dialogVisible = true
}
//
const saveTransaction = async (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
if (formData.id) {
//
adminRequest.put(`${state.route}`, formData).then(() =>{
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
})
} else {
//
adminRequest.post(`${state.route}`, formData).then(() =>{
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
})
}
//
const saveTransaction = async () => {
if (formData.id) {
//
await adminRequest.put(`${state.route}/${formData.id}`, formData)
} else {
//
await adminRequest.post(`${state.route}`, formData)
}
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
}
})
}
//
const del = async (id: number) => {
try {
@ -179,6 +196,14 @@ 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()
@ -188,4 +213,37 @@ onMounted(() => {
.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

@ -0,0 +1,211 @@
<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

@ -0,0 +1,201 @@
<template>
<div>
<!--查交易记录的按钮-->
<el-button type="primary" round @click="openAddDialog" size="small">添加</el-button>
<!--表格-->
<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 }">
<!-- 点击图片后显示弹框预览 -->
<el-image
:src="row.path"
fit="cover"
style="width: 200px;height: 100px"
/>
</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="path" :rules="[{ required: true, message: '请插入图片', trigger: 'blur' }]">
<image-upload @update:imageUrl="handleImageUrl" :image-url="state.formData.path"></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/slides",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
},
formData:{}
})
//
function handleImageUrl(path: string) {
state.formData.path = path
}
//
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

@ -0,0 +1,211 @@
<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:0,
},
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 = 0
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>

207
ui/src/pages/admin/user.vue Normal file
View File

@ -0,0 +1,207 @@
<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.username" 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="id"/>
<el-table-column prop="username" label="账号"/>
<el-table-column prop="password" label="密码"/>
<el-table-column prop="createDate" label="创建时间"/>
<el-table-column prop="nickName" label="昵称"/>
<el-table-column prop="introduce" label="介绍"/>
<!--列表结束-->
<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="formData"
ref="formRef"
label-width="100px"
>
<!-- 表单开始===============================================================================================================================-->
<el-form-item label="账号" prop="username" :rules="[{ required: true, message: '请输入账号', trigger: 'blur' }]">
<el-input v-model="formData.username"/>
</el-form-item>
<el-form-item label="密码" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
<el-input v-model="formData.password"/>
</el-form-item>
<el-form-item label="昵称" prop="nickName" :rules="[{ required: true, message: '请输入昵称', trigger: 'blur' }]">
<el-input v-model="formData.nickName"/>
</el-form-item>
<el-form-item label="介绍" prop="introduce" :rules="[{ required: true, message: '请输入介绍', trigger: 'blur' }]">
<el-input v-model="formData.introduce"/>
</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/user-front",
dialogVisible:false,
getList: [],//
query:{
total: 0, //
page: 1, //
limit: 10, //
username: "",
}
})
// 1990-2015
const years = [];
for (let year = 1990; year <= 2015; year++) {
years.push(year.toString());
}
//
let formData = reactive(<any>{})
//
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 = {}
state.dialogVisible = true
}
//
const edit = (row: any) => {
formData = row
state.dialogVisible = true
}
//
const saveTransaction = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
if (formData.id) {
//
adminRequest.put(`${state.route}`, formData)
} else {
//
adminRequest.post(`${state.route}`, 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,122 +0,0 @@
<template>
<!-- 销售情况按地区分布 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="salesByRegionOption" autoresize />
</el-col>
</el-row>
<!-- 地区需求对比 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="demandByRegionOption" autoresize />
</el-col>
</el-row>
<!-- 客户购买频次分析 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="mapOption" autoresize />
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import { BarChart, PieChart, LineChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { use } from 'echarts/core'
// 使
use([CanvasRenderer, BarChart, PieChart, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent])
//
const regionSalesData = ref([
{ region: '华北', sales: 1500, demand: 2000, frequency: 30 },
{ region: '华东', sales: 2500, demand: 3000, frequency: 40 },
{ region: '华南', sales: 1200, demand: 1500, frequency: 25 },
{ region: '西南', sales: 800, demand: 1000, frequency: 15 },
{ region: '西北', sales: 600, demand: 900, frequency: 10 },
{ region: '东北', sales: 900, demand: 1100, frequency: 20 },
{ region: '华中', sales: 1800, demand: 2200, frequency: 35 },
{ region: '西部', sales: 700, demand: 900, frequency: 18 },
{ region: '东南', sales: 2200, demand: 2700, frequency: 50 }
])
//
const salesByRegionOption = ref({
title: { text: '各地区销售情况' },
tooltip: { trigger: 'axis' },
legend: { data: ['销售量'] },
xAxis: {
type: 'category',
data: regionSalesData.value.map(item => item.region)
},
yAxis: { type: 'value' },
series: [
{
name: '销售量',
type: 'bar',
data: regionSalesData.value.map(item => item.sales)
}
]
})
// 线
const demandByRegionOption = ref({
title: { text: '各地区需求对比' },
tooltip: { trigger: 'axis' },
legend: { data: ['需求量'] },
xAxis: {
type: 'category',
data: regionSalesData.value.map(item => item.region)
},
yAxis: { type: 'value' },
series: [
{
name: '需求量',
type: 'line',
data: regionSalesData.value.map(item => item.demand)
}
]
})
//
const purchaseFrequencyOption = ref({
title: {
text: '各地区客户购买频次分析',
left: 'center' //
},
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' },
legend: {
orient: 'vertical',
left: 'left',
data: regionSalesData.value.map(item => item.region)
},
series: [
{
name: '购买频次',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: regionSalesData.value.map(item => ({ value: item.frequency, name: item.region })),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
})
</script>
<style scoped>
.chart {
height: 400px;
}
</style>

View File

@ -1,173 +0,0 @@
<template>
<!-- 销售热力图 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="heatmapOption" autoresize />
</el-col>
</el-row>
<div style="height: 20px"></div>
<!-- 产品销售表现雷达图 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="radarOption" autoresize />
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import { HeatmapChart, RadarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, GridComponent, VisualMapComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { use } from 'echarts/core'
// 使
use([CanvasRenderer, HeatmapChart, RadarChart, TitleComponent, TooltipComponent, GridComponent, VisualMapComponent])
//
const productSalesData = ref([
{ type: '液化气瓶A', specification: '5L', quality: '优质', sales: 200 },
{ type: '液化气瓶A', specification: '10L', quality: '优质', sales: 150 },
{ type: '液化气瓶A', specification: '20L', quality: '中等', sales: 180 },
{ type: '液化气瓶B', specification: '5L', quality: '良好', sales: 130 },
{ type: '液化气瓶B', specification: '10L', quality: '中等', sales: 220 },
{ type: '液化气瓶B', specification: '20L', quality: '优质', sales: 250 },
{ type: '液化气瓶C', specification: '5L', quality: '优质', sales: 190 },
{ type: '液化气瓶C', specification: '10L', quality: '良好', sales: 160 },
{ type: '液化气瓶C', specification: '20L', quality: '中等', sales: 200 },
{ type: '液化气瓶D', specification: '5L', quality: '中等', sales: 140 },
{ type: '液化气瓶D', specification: '10L', quality: '优质', sales: 270 },
{ type: '液化气瓶D', specification: '20L', quality: '良好', sales: 230 },
{ type: '液化气瓶E', specification: '5L', quality: '良好', sales: 110 },
{ type: '液化气瓶E', specification: '10L', quality: '中等', sales: 130 },
{ type: '液化气瓶E', specification: '20L', quality: '优质', sales: 220 },
{ type: '液化气瓶F', specification: '5L', quality: '中等', sales: 180 },
{ type: '液化气瓶F', specification: '10L', quality: '良好', sales: 160 },
{ type: '液化气瓶F', specification: '20L', quality: '优质', sales: 200 },
{ type: '液化气瓶G', specification: '5L', quality: '优质', sales: 210 },
{ type: '液化气瓶G', specification: '10L', quality: '优质', sales: 230 },
{ type: '液化气瓶G', specification: '20L', quality: '中等', sales: 220 }
])
//
const heatmapOption = ref({
title: { text: '销售热力图' },
tooltip: { position: 'top' },
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['液化气瓶A', '液化气瓶B', '液化气瓶C', '液化气瓶D', '液化气瓶E', '液化气瓶F', '液化气瓶G']
},
yAxis: {
type: 'category',
data: ['5L', '10L', '20L']
},
visualMap: {
min: 0,
max: 250, //
calculable: true,
orient: 'horizontal',
left: 'center',
inRange: {
color: ['#FFFFFF', '#FF0000'] //
}
},
series: [
{
name: '销售量',
type: 'heatmap',
data: [
[0, 0, 200], // [x, y, value]
[0, 1, 150],
[0, 2, 180],
[1, 0, 130],
[1, 1, 220],
[1, 2, 250],
[2, 0, 190],
[2, 1, 160],
[2, 2, 200],
[3, 0, 140],
[3, 1, 210],
[3, 2, 180],
[4, 0, 170],
[4, 1, 200],
[4, 2, 230],
[5, 0, 120],
[5, 1, 180],
[5, 2, 160],
[6, 0, 250],
[6, 1, 170],
[6, 2, 190]
]
}
]
})
//
const radarOption = ref({
title: {
text: '液化气产品销售表现',
left: 'center'
},
tooltip: {},
radar: {
indicator: [
{ name: '液化气瓶A', max: 300 },
{ name: '液化气瓶B', max: 300 },
{ name: '液化气瓶C', max: 300 },
{ name: '液化气瓶D', max: 300 },
{ name: '液化气瓶E', max: 300 },
{ name: '液化气瓶F', max: 300 },
{ name: '液化气瓶G', max: 300 }
]
},
series: [{
name: '销售表现',
type: 'radar',
data: [
{
value: productSalesData.value.filter(item => item.type === '液化气瓶A').map(item => item.sales),
name: '液化气瓶A'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶B').map(item => item.sales),
name: '液化气瓶B'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶C').map(item => item.sales),
name: '液化气瓶C'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶D').map(item => item.sales),
name: '液化气瓶D'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶E').map(item => item.sales),
name: '液化气瓶E'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶F').map(item => item.sales),
name: '液化气瓶F'
},
{
value: productSalesData.value.filter(item => item.type === '液化气瓶G').map(item => item.sales),
name: '液化气瓶G'
}
]
}]
})
</script>
<style scoped>
.chart {
height: 400px;
}
</style>

View File

@ -1,123 +0,0 @@
<template>
<!-- 销售额与毛利对比 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="salesMarginOption" ref="salesMarginChart" autoresize />
</el-col>
</el-row>
<!-- 线上与线下收入对比 -->
<el-row>
<el-col :span="24">
<v-chart class="chart" :option="revenueSourceOption" ref="revenueSourceChart" autoresize />
</el-col>
</el-row>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VChart from 'vue-echarts'
import { LineChart, BarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { use } from 'echarts/core'
// 使
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent])
//
const salesData = ref([
{ date: '2024-01-01', salesAmount: 5000, profit: 1200, onlineRevenue: 2500, offlineRevenue: 2500 },
{ date: '2024-01-02', salesAmount: 6000, profit: 1500, onlineRevenue: 3000, offlineRevenue: 3000 },
{ date: '2024-01-03', salesAmount: 4000, profit: 1000, onlineRevenue: 2000, offlineRevenue: 2000 },
{ date: '2024-01-04', salesAmount: 7000, profit: 1800, onlineRevenue: 3500, offlineRevenue: 3500 },
{ date: '2024-01-05', salesAmount: 5500, profit: 1300, onlineRevenue: 2700, offlineRevenue: 2800 },
// ...
])
// 线
const salesMarginOption = ref({
title: { text: '销售额与毛利对比' },
tooltip: { trigger: 'axis' },
legend: { data: ['销售额', '毛利'] },
xAxis: {
type: 'category',
data: salesData.value.map(item => item.date)
},
yAxis: { type: 'value' },
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
pixelRatio: 2, //
backgroundColor: '#ffffff' //
}
}
},
series: [
{
name: '销售额',
type: 'bar',
data: salesData.value.map(item => item.salesAmount),
itemStyle: { color: 'rgba(255, 127, 80, 0.6)' } //
},
{
name: '毛利',
type: 'line',
data: salesData.value.map(item => item.profit),
itemStyle: { color: 'rgba(135, 206, 250, 0.6)' }, //
emphasis: { itemStyle: { color: '#87cefa' } }
}
]
})
// 线线
const revenueSourceOption = ref({
title: { text: '线上与线下收入对比' },
tooltip: { trigger: 'axis' },
legend: { data: ['线上收入', '线下收入'] },
xAxis: {
type: 'category',
data: salesData.value.map(item => item.date)
},
yAxis: { type: 'value' },
toolbox: {
show: true,
feature: {
magicType: { show: true, type: ['line', 'bar'] },
saveAsImage: {
show: true,
title: '保存为图片',
type: 'png',
pixelRatio: 2, //
backgroundColor: '#ffffff',
}
}
},
series: [
{
name: '线上收入',
type: 'bar',
data: salesData.value.map(item => item.onlineRevenue),
itemStyle: { color: 'rgba(50, 205, 50, 0.6)' } //
},
{
name: '线下收入',
type: 'bar',
data: salesData.value.map(item => item.offlineRevenue),
itemStyle: { color: 'rgba(255, 99, 71, 0.6)' } //
}
]
})
</script>
<style scoped>
.chart {
height: 400px;
}
</style>

View File

@ -0,0 +1,375 @@
<template>
<div class="main-container">
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧内容 -->
<div class="main-content">
<h2 class="section-title">人气创作 / 为你推荐</h2>
<!-- 推荐内容列表 -->
<div class="recommend-list">
<article
v-for="(post, index) in state.getList"
:key="index"
class="post-card"
>
<!-- 小组信息 -->
<div class="group-info">
<el-tag
:type="post.groupType === 'default' ? '' : 'warning'"
size="small"
>
{{ post.nickName }}
</el-tag>
<span class="post-time">{{ post.createTime }}</span>
</div>
<!-- 内容主体 -->
<div class="post-content">
<p class="post-excerpt">
{{ post.title }}
<a
v-if="post.title"
class="expand-link"
@click="toggleExpand(index)"
>
{{ expandedIndexes.includes(index) ? '收起' : '查看全文' }}
</a>
</p>
<transition name="el-fade-in">
<p
v-show="expandedIndexes.includes(index)"
class="post-fulltext"
>
<div v-html="post.content "></div>
</p>
</transition>
</div>
<!-- 互动数据 -->
<div class="interaction-data" @click="to(post.id)">
<div class="stats">
<span class="stat-item">
<el-icon><comment /></el-icon>
{{ post.replies }} 回复
</span>
</div>
</div>
</article>
</div>
</div>
<!-- 右侧边栏 -->
<div class="sidebar">
<div class="trending-topics">
<h3 class="sidebar-title">热门话题 ······</h3>
<el-button class="more-btn" @click="state.dialogVisible = true">添加文创</el-button>
<div class="topic-list">
<div
v-for="(topic, index) in state.trendingTopics"
:key="index"
class="topic-item"
>
<div class="topic-meta">
<span class="topic-index">{{ index + 1 }}</span>
<span class="topic-name">{{ topic.title }}</span>
</div>
<div class="topic-stats">
<span class="stat-number">{{ topic.view }}次浏览</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<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>
<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>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElTag, ElButton, ElIcon, type FormInstance } from 'element-plus'
import { Comment } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const formRef = ref<FormInstance>()
//
const state = reactive({
route:"api/topic",
dialogVisible:false,
query: {
total: 0, //
page: 1, //
limit: 9999, //
title: '',
type: '1'
},
getList: [],
trendingTopics:[],
formData:{},
})
/**
* 跳转
*/
const to = (id:number) => {
router.push(`/topicInfo/${id}`)
}
//
function handleImageUrl(content: string) {
state.formData.content = content
}
function init() {
frontRequest.get('/api/topic/page', {
params: state.query
}).then(res => {
state.getList = res.data.list
loading.close()
})
}
onMounted(() => {
if (!userStore().frontIsLogin){
toast.warning("登录失效")
router.push("/login")
}
init()
let query = state.query
query.view='view'
frontRequest.get("/api/topic/page",{
params: query
}).then(res =>{
state.trendingTopics = res.data.list
})
})
const saveTransaction = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
state.formData.type = 1
//
frontRequest.post(`${state.route}`, state.formData).then(()=>{
init()
state.dialogVisible = false
ElMessage.success("提交成功~")
})
}
})
}
//
const expandedIndexes = ref<number[]>([])
//
const toggleExpand = (index: number) => {
const idx = expandedIndexes.value.indexOf(index)
idx === -1
? expandedIndexes.value.push(index)
: expandedIndexes.value.splice(idx, 1)
}
</script>
<style lang="scss" scoped>
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
.content-wrapper {
display: grid;
grid-template-columns: 1fr 300px;
gap: 32px;
}
.section-title {
font-size: 20px;
color: #1a1a1a;
margin: 0 0 24px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.post-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
padding: 20px;
transition: transform 0.2s;
&:hover {
transform: translateY(-2px);
}
.group-info {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
.post-time {
color: #8590a6;
font-size: 12px;
}
}
.post-title {
font-size: 16px;
color: #1a1a1a;
margin: 0 0 12px;
}
.post-excerpt {
color: #646464;
line-height: 1.6;
margin: 0 0 8px;
.expand-link {
color: #06a;
cursor: pointer;
margin-left: 8px;
&:hover {
text-decoration: underline;
}
}
}
.post-fulltext {
color: #444;
line-height: 1.7;
margin: 12px 0 0;
padding-top: 12px;
border-top: 1px dashed #eee;
}
.interaction-data {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f5f5f5;
.stats {
display: flex;
gap: 24px;
.stat-item {
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
color: #8590a6;
font-size: 13px;
}
}
.follow-btn {
color: #06a;
padding: 4px 8px;
}
}
}
.sidebar {
.trending-topics {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.sidebar-title {
font-size: 16px;
color: #1a1a1a;
margin: 0 0 16px;
}
.topic-item {
padding: 12px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.topic-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
.topic-index {
width: 20px;
height: 20px;
background: #f5f5f5;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #8590a6;
}
.topic-name {
font-size: 14px;
color: #1a1a1a;
}
}
.topic-stats {
display: flex;
gap: 8px;
font-size: 12px;
color: #8590a6;
.stat-divider {
color: #ddd;
}
}
}
.more-btn {
width: 100%;
margin-top: 16px;
color: #06a;
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -1,33 +0,0 @@
<template>
<!--轮播图-->
<el-row :gutter="20">
<el-col :span="12">
<div class="h-300px">
<carousel></carousel>
</div>
</el-col>
<el-col :span="12">
<div class="h-350px">
<carousel></carousel>
</div>
</el-col>
</el-row>
<!-- 推荐商品列表-->
<item></item>
</template>
<script setup lang="ts">
import Carousel from '~/components/front/carousel.vue'
import Item from '~/components/front/item.vue'
</script>
<style scoped>
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -1,76 +0,0 @@
<!--用户详情-->
<template>
<el-tabs v-model="state.activeName" @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="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
<el-table-column label="操作" min-width="120">
<template #default>
<el-button link type="primary" size="small" v-if="state.activeName == 1">立即付款</el-button>
<el-button link type="primary" size="small" v-if="state.activeName == 3">确认收货</el-button>
<el-button link type="primary" size="small" v-if="state.activeName == 4">评价</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive({
activeName:1,
getOrderStatus:[
{name:"待付款",status:1},
{name:"未发货",status:2},
{name:"待收货",status:3},
{name:"待评价",status:4},
],
getList:[
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
})
/**
* 获取列表数据
*/
function getList() {
}
</script>
<style scoped>
</style>
<route lang="json">
{
"meta": {
"layout": "frontUserInfo"
}
}
</route>

View File

@ -0,0 +1,207 @@
<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 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>
<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: 9999, //
title:"",
type:1,
},
formData:{}
})
const info = (info:string) => {
console.log(info)
state.info = info
dialogVisible.value = true
}
//
const init = () => {
frontRequest.get(`/api/topic/page1`, {
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": "frontUserInfo"
}
}
</route>

View File

@ -2,8 +2,12 @@
<!--轮播图-->
<el-row :gutter="20">
<el-col :span="12">
<div class="h-300px">
<carousel></carousel>
<div class="h-250px hot" >
<el-space wrap>
<div v-for="item in state.commit" :key="i" style="margin-top: 10px;">
<el-button text style="width: 290px" @click="to(item.id)">{{item.title}} </el-button>
</div>
</el-space>
</div>
</el-col>
<el-col :span="12">
@ -12,23 +16,53 @@
</div>
</el-col>
</el-row>
<!-- 推荐商品列表-->
<item></item>
<hot1></hot1>
<!-- 推荐商品列表-->
<item :get-list=state.getList></item>
</template>
<script setup lang="ts">
import Carousel from '~/components/front/carousel.vue'
import Item from '~/components/front/item.vue'
import Hot1 from '~/components/hot/Hot1.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const state = reactive({
getList: [],
commit: [],
})
// 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}`)
}
</script>
<style scoped>
.hot {
background-color: rgba(0, 0, 0, 0.2); /* 更浅的黑色 */
border-radius: 10px; /* 四周圆弧,值可以根据需要调整 */
}
:deep(.el-button){
color: #2e191e;
font-weight: 700;
}
:deep(.el-button:hover) {
background-color: black;
}
</style>
<route lang="json">
{

View File

@ -15,9 +15,17 @@
</template>
<script setup lang="ts">
import InfoTop from '~/components/front/info-top.vue'
import InfoBottom from '~/components/front/info-bottom.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
onMounted(()=>{
if (!userStore().frontIsLogin){
toast.warning("登录失效")
router.push("/login")
}
})
</script>
<style scoped>

View File

@ -0,0 +1,148 @@
<template>
<el-row :gutter="20">
<el-col :span="24">
<div class="movie-ranking">
<div class="header">
<h1>图书排行榜</h1>
</div>
<div class="movie-list">
<div class="movie-item" v-for="(movie, index) in state.getList" :key="index" @click="to(movie.id)">
<div class="ranking-badge">{{ index + 1 }}</div> <!-- Display rank at the top right -->
<img :src="movie.image" class="poster" />
<div class="movie-details">
<h3>{{ movie.title }}</h3>
<p>{{ movie.movieType }}</p>
<p style="text-align: center">价格: {{ movie.price }} | 作者{{ movie.tag }}</p>
</div>
</div>
</div>
</div>
</el-col>
<!-- <el-col :span="6">-->
<!-- &lt;!&ndash; Placeholder for additional content or sidebar &ndash;&gt;-->
<!-- d-->
<!-- </el-col>-->
</el-row>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const state = reactive({
route: "api/item",
dialogVisible: false,
getList: [], // Data fetching
query: {
total: 0, // Total number of records
page: 1, // Current page number
limit: 100, // Number of records per page
}
})
// Fetch movie data
const init = () => {
frontRequest.get(`${state.route}/page`, {
params: state.query
}).then((res: any) => {
state.getList = res.data.list
state.query.total = res.data.total
})
}
// Navigate to movie details page
const to = (id: number) => {
router.push(`/info/${id}`)
}
// Fetch data on component mount
onMounted(() => {
init()
})
</script>
<style scoped>
.movie-ranking {
font-family: Arial, sans-serif;
padding: 20px;
}
.header h1 {
text-align: center;
font-size: 48px;
font-weight: bold;
background: linear-gradient(45deg, #ca067d, #e12304); /* Gradient background */
color: transparent;
background-clip: text; /* Make the text color match the gradient */
-webkit-background-clip: text; /* Safari support */
animation: glowing 1.5s infinite alternate; /* Glowing effect */
text-shadow: 0 0 5px rgba(255, 255, 255, 0.6), 0 0 10px rgba(255, 255, 255, 0.6), 0 0 15px rgba(255, 255, 255, 0.6); /* Glowing effect */
}
@keyframes glowing {
0% {
text-shadow: 0 0 5px rgba(255, 255, 255, 0.6), 0 0 10px rgba(255, 255, 255, 0.6), 0 0 15px rgba(255, 255, 255, 0.6);
}
50% {
text-shadow: 0 0 10px rgb(246, 226, 226), 0 0 20px rgba(255, 255, 255, 0.8), 0 0 30px rgba(255, 255, 255, 0.5);
}
100% {
text-shadow: 0 0 5px rgba(255, 255, 255, 0.6), 0 0 10px rgba(255, 255, 255, 0.6), 0 0 15px rgba(255, 255, 255, 0.6);
}
}
.movie-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.movie-item {
display: flex;
flex-direction: column;
border: 1px solid #ddd;
border-radius: 8px;
padding: 10px;
position: relative;
cursor: pointer;
background-color: #fff;
}
.poster {
width: 100%;
height: auto;
border-radius: 4px;
}
.movie-details h3 {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-top: 10px;
}
.movie-details p {
margin: 5px 0;
color: #555;
}
/* Rank badge style */
.ranking-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: rgb(252, 224, 75);
color: #2e191e;
padding: 5px 10px;
border-radius: 50%;
font-size: 16px;
font-weight: bold;
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -6,6 +6,7 @@
<div class="module_mian">
<div class="module_title">登录帐户</div>
<div class="module_desc">输入用户名 & 登录密码</div>
<div class="module_m">
<div class="module_text">用户名</div>
<input class="module_input" type="text" placeholder="输入用户名" v-model="login.username" />
@ -23,9 +24,9 @@
<!-- </div>-->
<div class="module_m">
<div class="module_code">
<el-radio-group v-model="login.role" class="ml-4">
<el-radio :label=0 size="large">普通用户</el-radio>
<el-radio :label=1 size="large">管理员</el-radio>
<el-radio-group v-model="state.role" class="ml-4">
<el-radio :label=false size="large">普通用户</el-radio>
<el-radio :label=true size="large">管理员</el-radio>
</el-radio-group>
</div>
</div>
@ -41,32 +42,48 @@
import { useRouter } from 'vue-router'
import { loginAdmin } from '~/api/user/adminUserApi'
import { getUuid } from '~/utils/utils'
import { loginFront } from '~/api/user/frontUserApi'
const router = useRouter()
const state = reactive({
role: false,
captchaUrl: '',
loginFrom: {},
loading: false
})
const login = reactive({ username: 'admin', password: 'admin', captcha: '', uuid: '',role: 0,})
const login = reactive({
username: '',
password: '',
captcha: '',
uuid: '' })
const onLogin = () => {
state.loading = true
if (login.role == 1) {
console.log("管理员")
if (state.role) {
loginAdmin(login).then(response => {
state.loading = false
ElMessage.success('登录成功')
userStore().adminIsLogin = true
userStore().adminToken = response.data.token
router.push('/admin')
adminRequest.get("/sys/user/info").then(response => {
userStore().adminUserInfo = response.data
router.push('/admin')
ElMessage.success("登录成功~")
})
}).catch(() => {
state.loading = false
onRefreshCode()
})
} else {
loginFront(login)
ElMessage.success("登录成功~")
router.push('/')
frontRequest.post("/api/user/login", login).then(response =>{
const user = userStore()
user.frontToken = response.data.token
frontRequest.get("/api/user/userInfo").then(response =>{
user.frontIsLogin = true
user.frontUserInfo = response.data
ElMessage.success("登录成功~")
router.push('/')
})
})
}
}
/**
@ -82,6 +99,8 @@ const onRefreshCode = () => {
}
onMounted(() => {
getCaptchaUrl()
})
</script>
@ -91,6 +110,7 @@ onMounted(() => {
height: 100vh;
background: #FFFFFF;
}
.module {
width: 100%;
display: flex;
@ -98,10 +118,12 @@ onMounted(() => {
justify-content: space-between;
height: 100vh;
}
.module_img {
width: 60%;
height: auto;
}
.module_r {
width: 40%;
background: #e5efee;
@ -109,6 +131,7 @@ onMounted(() => {
display: flex;
align-items: center;
justify-content: center;
.module_mian {
width: 65%;
background: #FFFFFF;
@ -117,18 +140,21 @@ onMounted(() => {
overflow: hidden;
padding-top: 40px;
padding-bottom: 40px;
.module_title {
font-size: 18px;
font-weight: 500;
text-align: center;
color: #333333;
}
.module_desc {
font-size: 12px;
text-align: center;
color: #a7a7a7;
margin-bottom: 20px;
}
.module_m {
margin: 0 auto;
width: 80%;
@ -140,6 +166,7 @@ onMounted(() => {
color: #333333;
margin-bottom: 5px;
}
.module_input {
width: 96%;
height: 40px;
@ -149,10 +176,12 @@ onMounted(() => {
border-radius: 5px;
font-size: 12px;
}
.module_code {
width: 96%;
display: flex;
align-items: center;
.module_code_input {
width: 60%;
height: 40px;
@ -162,6 +191,7 @@ onMounted(() => {
padding-left: 2%;
padding-right: 2%;
}
.module_code_img {
width: 130px;
height: 40px;
@ -171,9 +201,11 @@ onMounted(() => {
}
}
}
.module_radio input {
margin-right: 5px;
}
.forgetpwd {
margin: 0 auto;
width: 80%;
@ -182,6 +214,7 @@ onMounted(() => {
margin-top: 10px;
cursor: pointer;
}
.module_button {
margin: 0 auto;
display: block;
@ -194,6 +227,7 @@ onMounted(() => {
font-weight: 500;
cursor: pointer;
}
.module_button:active {
opacity: 0.4;
}

View File

@ -0,0 +1,86 @@
<!--用户详情-->
<template>
<el-tabs v-model="state.activeName" @tab-click="handleClick">
<el-tab-pane v-for="status in state.getOrderStatus" :label="status.name" :name="status.status">
<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="year" label="上映时间" align="center" />
<el-table-column prop="largePic" label="图片" align="center" >
<template #default="{ row }">
<!-- 点击图片后显示弹框预览 -->
<el-image
:src="row.largePic"
fit="cover"
:preview-src-list="row.largePic.split(',')"
:preview-teleported="true"
/>
</template>
</el-table-column>
<el-table-column prop="country" label="国家" align="center" />
<el-table-column prop="movieType" label="电影类型" align="center" />
<el-table-column prop="director" label="导演" align="center" />
<el-table-column prop="actor" label="主演" align="center" />
<el-table-column prop="ratingValue" label="评分" align="center" />
<el-table-column prop="ratingCount" 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-tab-pane>
</el-tabs>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive({
activeName:1,
getOrderStatus:[
{name:"全部",status:1},
],
getList:[]
})
/**
* 获取列表数据
*/
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": "frontUserInfo"
}
}
</route>

View File

@ -3,6 +3,10 @@
<div class="module_mian">
<div class="module_title">注册帐户</div>
<div class="module_desc">输入用户名 & 密码</div>
<div class="module_m">
<div class="module_text">用户昵称</div>
<input class="module_input" type="text" placeholder="输入昵称" v-model="register.nickName" />
</div>
<div class="module_m">
<div class="module_text">用户名</div>
<input class="module_input" type="text" placeholder="输入用户名" v-model="register.username" />

View File

@ -0,0 +1,216 @@
<template>
<div class="topic-square">
<!-- 顶部导航 -->
<header class="header">
<div class="search-bar">
<el-input placeholder="搜索话题" v-model="state.query.title" @input="init">
<template #prefix>
<el-icon><search /></el-icon>
</template>
</el-input>
</div>
<nav class="nav-tabs">
<div class="tab active">话题总览</div>
</nav>
</header>
<!-- 主体内容 -->
<main class="main-content">
<!-- 左侧动态列表 -->
<div class="dynamic-list">
<div v-for="topic in state.getList" :key="topic.id" class="topic-item" @click="to(topic.id)">
<div class="content">
<h3 class="title" style="color: #34cdfe">{{ topic.title }}</h3>
<p class="text"><div v-html=" topic.content"></div></p>
<div class="topic-tag">
<el-tag type="info" size="small">来自话题 {{ topic.topic }}</el-tag>
</div>
<div class="interaction">
<span class="stats"> 我要回应 </span>
<div class="actions">
<el-button type="text">
{{topic.view}} 预览
</el-button>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧热门话题 -->
<div class="trending-topics">
<div class="section-title">话题榜单</div>
<div v-for="(topic, index) in state.trendingTopics" :key="topic.id" class="trend-item" @click="to(topic.id)">
<div class="rank">{{ index + 1 }}</div>
<div class="info">
<div class="title">{{ topic.title }}</div>
<div class="views">{{ topic.view }}次浏览</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
import { ElLoading } from 'element-plus'
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
const state = reactive({
getList:[],
trendingTopics:[],
query:{
total: 0, //
page: 1, //
limit: 9999, //
title:"",
type:"0",
}
})
function init() {
frontRequest.get("/api/topic/page",{
params: state.query
}).then(res =>{
state.getList = res.data.list
loading.close()
})
}
/**
* 跳转
*/
const to = (id:number) => {
router.push(`/topicInfo/${id}`)
}
onMounted(()=>{
init()
let query = state.query
query.view='view'
frontRequest.get("/api/topic/page",{
params: query
}).then(res =>{
state.trendingTopics = res.data.list
})
})
//
const currentPage = ref(1)
const pageSize = ref(10)
</script>
<style scoped>
.topic-square {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
margin-bottom: 30px;
}
.nav-tabs {
display: flex;
margin-top: 20px;
border-bottom: 1px solid #eee;
.tab {
padding: 12px 20px;
cursor: pointer;
&.active {
border-bottom: 2px solid #07a;
color: #07a;
}
}
}
.main-content {
display: grid;
grid-template-columns: 1fr 300px;
gap: 30px;
}
.dynamic-list {
.topic-item {
padding: 20px;
margin-bottom: 30px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
cursor: pointer;
.topic-tag {
margin: 15px 0;
.el-tag {
margin-right: 8px;
}
}
.interaction {
display: flex;
justify-content: space-between;
align-items: center;
color: #666;
font-size: 0.9em;
.stats {
flex: 1;
}
}
}
}
.trending-topics {
cursor: pointer;
.section-title {
font-weight: 500;
margin-bottom: 15px;
}
.trend-item {
display: flex;
align-items: center;
padding: 12px;
margin-bottom: 10px;
background: #f8f9fa;
border-radius: 4px;
.rank {
width: 24px;
height: 24px;
background: #07a;
color: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.info {
flex: 1;
.views {
color: #666;
font-size: 0.9em;
}
}
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -0,0 +1,263 @@
<template>
<div class="topic-container">
<!-- 话题头部 -->
<div class="topic-header">
<div class="header-main">
<h1 class="title">{{ state.info.title }}</h1>
</div>
<div class="stats">{{state.info.view }}次浏览</div>
</div>
<!-- 话题描述 -->
<div class="topic-description">
<p><div v-html="state.info.content"></div></p>
</div>
<!-- 发帖入口 -->
<div class="post-actions">
<el-button type="text" icon="edit" @click="dialogVisible = true">说点什么</el-button>
</div>
<!-- 内容筛选 -->
<div class="filter-tabs">
<div
v-for="tab in tabs"
:key="tab"
:class="['tab', { active: activeTab === tab }]"
@click="activeTab = tab"
>
{{ tab }}
</div>
</div>
<!-- 广播列表 -->
<div v-if="state.info.commentEntityList" class="broadcast-list">
<div
v-for="(item, index) in state.info.commentEntityList"
:key="index"
class="broadcast-item"
>
<div class="user-info">
<div class="meta">
<div class="username">{{ item.nickName }}</div>
<div class="time">{{item.createTime}}</div>
</div>
</div>
<div class="content">
<p class="text"><div v-html="item.content" class="rich-text-container"></div></p>
<!-- <div class="interactions">-->
<!-- <span class="response">{{ item.responses }} 回应</span>-->
<!-- </div>-->
</div>
</div>
</div>
<el-empty v-else description="评论哪去了?" />
<el-dialog
v-model="dialogVisible"
title="评论"
width="500"
>
<e-editor :content="state.form.content" @update:content="handleImageUrl" />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="comment">
提交
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElButton, ElAvatar, ElLoading } from 'element-plus'
import { useRoute } from 'vue-router'
const route = useRoute()
const itemId = route.params.id
const dialogVisible = ref(false)
import { useRouter } from 'vue-router'
const router = useRouter()
const activeTab = ref('评论')
const tabs = ['评论']
// 广
const state = reactive({
info: {},
broadcasts: [
{
author: 'lolus',
avatar: 'https://example.com/avatar1.jpg',
time: '2025-02-10T13:38:18',
content: '你穿着酥皮大衣坐在那里,我感觉你要碎了...',
responses: 154,
reposts: 1,
},
{
author: '饮水少年',
avatar: 'https://example.com/avatar2.jpg',
time: '2025-02-03T10:02:34',
content: '只有懂得自我激励自我认可我们才能够度过人生漫漫长路...',
responses: 89,
reposts: 2,
},
],
form:{}
})
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
const comment = () => {
state.form.topicId = itemId
if (state.form.content == '<p><br></p>' || state.form.content == ''){
ElMessage.warning("不能为空!")
return
}
frontRequest.post("/api/comment",{itemId:itemId,content:state.form.content}).then(response =>{
ElMessage.success("发表成功")
state.form.content= ""
init()
dialogVisible.value = false
})
}
function init() {
frontRequest.get(`/api/topic/${itemId}`).then((res) => {
state.info = res.data
loading.close()
})
}
onMounted(() => {
if (!userStore().frontIsLogin){
toast.warning("登录失效")
router.push("/login")
}
init()
})
//
function handleImageUrl(content: string) {
state.form.content = content
}
</script>
<style scoped>
.rich-text-container {
width: 100%; /* 设置宽度为容器的100%或指定的宽度 */
overflow: hidden; /* 内容超出部分将被隐藏 */
text-overflow: ellipsis; /* 如果需要,可以加上省略号 */
word-wrap: break-word; /* 防止长单词或链接溢出 */
white-space: normal; /* 允许换行 */
box-sizing: border-box; /* 确保padding不会影响布局 */
}
.topic-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.topic-header {
margin-bottom: 30px;
.header-main {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 10px;
.title {
margin: 0;
font-size: 28px;
}
}
.stats {
color: #666;
font-size: 14px;
}
}
.topic-description {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
margin-bottom: 20px;
p {
margin: 0;
line-height: 1.6;
color: #444;
}
}
.post-actions {
display: flex;
gap: 15px;
margin-bottom: 30px;
border-bottom: 1px solid #eee;
padding-bottom: 20px;
}
.filter-tabs {
display: flex;
margin-bottom: 20px;
.tab {
padding: 8px 20px;
cursor: pointer;
color: #666;
&.active {
color: #07a;
border-bottom: 2px solid #07a;
}
}
}
.broadcast-item {
padding: 20px;
margin-bottom: 25px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.user-info {
display: flex;
align-items: center;
margin-bottom: 15px;
.meta {
margin-left: 12px;
.username {
font-weight: 500;
}
.time {
color: #666;
font-size: 12px;
}
}
}
.content {
.text {
margin: 0 0 15px 0;
line-height: 1.6;
color: #333;
}
.interactions {
display: flex;
align-items: center;
color: #666;
.response {
margin-right: 20px;
}
}
}
}
</style>
<route lang="json">
{
"meta": {
"layout": "front"
}
}
</route>

View File

@ -5,7 +5,6 @@
style="max-width: 600px"
:model="state.userInfo"
label-width="auto"
>
<el-form-item
prop="username"
@ -16,7 +15,6 @@
message: '用户名不能为空',
trigger: 'blur',
},
]"
>
<el-input v-model="state.userInfo.username" />
@ -47,6 +45,19 @@
>
<el-input v-model="state.userInfo.password" />
</el-form-item>
<el-form-item
prop="confirmPassword"
label="确认密码"
:rules="[
{
required: true,
message: '确认密码不能为空',
trigger: 'blur',
},
]"
>
<el-input v-model="state.userInfo.confirmPassword" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(formRef)">提交</el-button>
</el-form-item>
@ -55,10 +66,29 @@
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const formRef = ref<FormInstance>()
const state = reactive({
userInfo: {}
})
function init() {
frontRequest.get("/api/user/userInfo").then(res =>{
state.userInfo = res.data
})
}
onMounted(() =>{
// if (!userStore().frontIsLogin){
// toast.warning("")
// router.push("/login")
// }
init()
})
/**
* 提交
* @param formEl
@ -67,17 +97,17 @@ const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid) => {
if (valid) {
console.log('submit!')
} else {
console.log('error submit!')
frontRequest.put("/api/user/update",state.userInfo).then(res =>{
ElMessage.success("修改成功")
router.push("/login")
})
}
})
}
</script>
<style scoped>
</style>

View File

@ -12,10 +12,18 @@ export const router = createRouter({
})
// 路由拦截
router.beforeEach((to, from, next) => {
// 前台处理逻辑
if (to.fullPath.includes("/front")){
console.log(to.fullPath)
console.log(to.fullPath)
if (to.fullPath.includes("/front/info")){
console.log("公告管理~")
//判断有没有登录
if (!userStore().frontIsLogin){
ElMessage.warning("请先登录~")
next('/login');
}
}
}
///////////////////////////////////////////////////////////////////////////////
// 管理员全部限制
else if (to.fullPath.includes("/admin")){
console.log("管理员认证~")

View File

@ -3,8 +3,8 @@ import { defineStore } from 'pinia'
export default defineStore('navStore', {
state() {
return {
adminPath: "/",
frontPath: "/front/",
adminPath: "/admin",
frontPath: "/front",
}
},
actions: {

View File

@ -4,7 +4,7 @@ export default defineStore('userStore', {
state() {
return {
adminIsLogin: false,
isLogin: false,
frontIsLogin: false,
adminToken: "",
frontToken: "",
adminUserInfo:{},

Some files were not shown because too many files have changed in this diff Show More