diff --git a/admin-web/config/proxy/proxy.dev.js b/admin-web/config/proxy/proxy.dev.js
index 0608bd7b3..a995408ba 100644
--- a/admin-web/config/proxy/proxy.dev.js
+++ b/admin-web/config/proxy/proxy.dev.js
@@ -7,6 +7,12 @@ export default {
changeOrigin: true,
pathRewrite: {},
},
+ '/product-api/': {
+ // target: 'http://180.167.213.26:18083/',
+ target: 'http://127.0.0.1:18081/',
+ changeOrigin: true,
+ pathRewrite: {},
+ },
'/order-api/': {
target: 'http://127.0.0.1:18084/',
changeOrigin: true,
diff --git a/admin-web/config/router.config.js b/admin-web/config/router.config.js
index 932f5bee0..caeb2563c 100644
--- a/admin-web/config/router.config.js
+++ b/admin-web/config/router.config.js
@@ -110,11 +110,11 @@ export default [
name: 'promotion-banner-list',
component: './Promotion/BannerList',
},
- // {
- // path: '/product/product-spu-add',
- // name: 'product-spu-add',
- // component: './Product/ProductSpuAddOrUpdate',
- // },
+ {
+ path: '/promotion/product-recommend-list',
+ name: 'product-recommend-list',
+ component: './Promotion/ProductRecommendList',
+ },
// {
// path: '/product/product-category-list',
// name: 'product-category-list',
diff --git a/admin-web/src/locales/zh-CN/menu.js b/admin-web/src/locales/zh-CN/menu.js
index 9695fe996..ec963f8b7 100644
--- a/admin-web/src/locales/zh-CN/menu.js
+++ b/admin-web/src/locales/zh-CN/menu.js
@@ -55,4 +55,5 @@ export default {
'menu.order.order-refunds': '退货维权',
// 营销相关
'menu.promotion.promotion-banner-list': 'Banner 管理',
-};
+ 'menu.promotion.product-recommend-list': '商品推荐',
+};
\ No newline at end of file
diff --git a/admin-web/src/models/promotion/bannerList.js b/admin-web/src/models/promotion/bannerList.js
index ce4d46ada..4f139c05a 100644
--- a/admin-web/src/models/promotion/bannerList.js
+++ b/admin-web/src/models/promotion/bannerList.js
@@ -1,15 +1,10 @@
-import {message} from 'antd';
-import {buildTreeNode, findCheckedKeys} from '../../utils/tree.utils';
-import {
+import {message} from 'antd';import {
addBanner,
- adminRoleAssign,
deleteBanner,
queryBanner,
- queryBannerRoleList,
updateBanner,
updateBannerStatus,
} from '../../services/promotion';
-import {arrayToStringParams} from '../../utils/request.qs';
import PaginationHelper from '../../../helpers/PaginationHelper';
const SEARCH_PARAMS_DEFAULT = {
@@ -31,12 +26,6 @@ export default {
modalType: undefined, // 'add' or 'update' 表单
formVals: {}, // 当前表单值
modalLoading: false,
-
- // 分配角色表单相关
- roleList: [],
- roleModalVisible: false,
- roleCheckedKeys: [], // 此处的 Key ,就是角色编号
- roleAssignLoading: false,
},
effects: {
@@ -161,78 +150,10 @@ export default {
}
},
- * queryRoleList({ payload }, { call, put }) {
- // 显示加载中
- yield put({
- type: 'changeRoleAssignLoading',
- payload: true,
- });
-
- // 请求
- const response = yield call(queryBannerRoleList, payload);
- // 响应
- if (response.code === 0) {
- const roleList = response.data;
- const roleTreeData = buildTreeNode(roleList, 'name', 'id');
- const roleCheckedKeys = findCheckedKeys(roleList);
- yield put({
- type: 'setAll',
- payload: {
- roleList: roleTreeData,
- roleCheckedKeys,
- },
- });
- }
-
- // 隐藏加载中
- yield put({
- type: 'changeRoleAssignLoading',
- payload: false,
- });
- },
-
- * roleAssign({ payload }, { call, put }) {
- const { callback, body } = payload;
- // 显示加载中
- yield put({
- type: 'changeRoleAssignLoading',
- payload: true,
- });
-
- // 请求
- const response = yield call(adminRoleAssign, {
- id: body.id,
- roleIds: arrayToStringParams(body.roleIds),
- });
- // 响应
- if (response.code === 0) {
- if (callback) {
- callback(response);
- }
- }
-
- // 隐藏加载中
- yield put({
- type: 'changeRoleAssignLoading',
- payload: false,
- });
- },
},
reducers: {
- changeRoleCheckedKeys(state, { payload }) {
- return {
- ...state,
- roleCheckedKeys: payload,
- };
- },
// 修改加载中的状态
- changeRoleAssignLoading(state, { payload }) {
- return {
- ...state,
- roleAssignLoading: payload,
- };
- },
changeModalLoading(state, { payload }) {
return {
...state,
diff --git a/admin-web/src/models/promotion/productRecommendList.js b/admin-web/src/models/promotion/productRecommendList.js
new file mode 100644
index 000000000..d5011e05f
--- /dev/null
+++ b/admin-web/src/models/promotion/productRecommendList.js
@@ -0,0 +1,178 @@
+import {message} from 'antd';
+import {
+ addProductRecommend,
+ deleteProductRecommend,
+ queryProductRecommend,
+ updateProductRecommend,
+ updateProductRecommendStatus,
+} from '../../services/promotion';
+import PaginationHelper from '../../../helpers/PaginationHelper';
+
+const SEARCH_PARAMS_DEFAULT = {
+ type: undefined,
+};
+
+export default {
+ namespace: 'productRecommendList',
+
+ state: {
+ // 分页列表相关
+ list: [],
+ listLoading: false,
+ pagination: PaginationHelper.defaultPaginationConfig,
+ searchParams: SEARCH_PARAMS_DEFAULT,
+
+ // 添加 or 修改表单相关
+ modalVisible: false,
+ modalType: undefined, // 'add' or 'update' 表单
+ formVals: {}, // 当前表单值
+ modalLoading: false,
+ },
+
+ effects: {
+ // 查询列表
+ * query({ payload }, { call, put }) {
+ // 显示加载中
+ yield put({
+ type: 'changeListLoading',
+ payload: true,
+ });
+
+ // 请求
+ const response = yield call(queryProductRecommend, payload);
+ // 响应
+ yield put({
+ type: 'setAll',
+ payload: {
+ list: response.data.list,
+ pagination: PaginationHelper.formatPagination(response.data, payload),
+ searchParams: {
+ type: payload.type
+ }
+ },
+ });
+
+ // 隐藏加载中
+ yield put({
+ type: 'changeListLoading',
+ payload: false,
+ });
+ },
+ * add({ payload }, { call, put }) {
+ const { callback, body } = payload;
+ // 显示加载中
+ yield put({
+ type: 'changeModalLoading',
+ payload: true,
+ });
+
+ // 请求
+ const response = yield call(addProductRecommend, body);
+ // 响应
+ if (response.code === 0) {
+ if (callback) {
+ callback(response);
+ }
+ // 刷新列表
+ yield put({
+ type: 'query',
+ payload: {
+ ...PaginationHelper.defaultPayload
+ },
+ });
+ }
+
+ // 隐藏加载中
+ yield put({
+ type: 'changeModalLoading',
+ payload: false,
+ });
+ },
+ * update({ payload }, { call, put }) {
+ const { callback, body } = payload;
+ // 显示加载中
+ yield put({
+ type: 'changeModalLoading',
+ payload: true,
+ });
+
+ // 请求
+ const response = yield call(updateProductRecommend, body);
+ // 响应
+ if (response.code === 0) {
+ if (callback) {
+ callback(response);
+ }
+ // 刷新列表
+ yield put({
+ type: 'query',
+ payload: {
+ ...PaginationHelper.defaultPayload
+ },
+ });
+ }
+
+ // 隐藏加载中
+ yield put({
+ type: 'changeModalLoading',
+ payload: false,
+ });
+ },
+
+ * updateStatus({ payload }, { call, put }) {
+ // 请求
+ const response = yield call(updateProductRecommendStatus, payload);
+ // 响应
+ if (response.code === 0) {
+ message.info('更新状态成功!');
+ // 刷新列表
+ yield put({
+ type: 'query',
+ payload: {
+ ...PaginationHelper.defaultPayload
+ },
+ });
+ }
+ },
+
+ * delete({ payload }, { call, put }) {
+ // 请求
+ const response = yield call(deleteProductRecommend, payload);
+ // 响应
+ if (response.code === 0) {
+ message.info('删除成功!');
+ // 刷新列表
+ yield put({
+ type: 'query',
+ payload: {
+ ...PaginationHelper.defaultPayload
+ },
+ });
+ }
+ },
+
+ },
+
+ reducers: {
+ // 修改加载中的状态
+ changeModalLoading(state, { payload }) {
+ return {
+ ...state,
+ modalLoading: payload,
+ };
+ },
+ changeListLoading(state, { payload }) {
+ return {
+ ...state,
+ listLoading: payload,
+ };
+ },
+ // 设置所有属性
+ setAll(state, { payload }) {
+ return {
+ ...state,
+ ...payload,
+ };
+ }
+ },
+};
diff --git a/admin-web/src/pages/Promotion/BannerList.js b/admin-web/src/pages/Promotion/BannerList.js
index 3643eca25..2ed408adb 100644
--- a/admin-web/src/pages/Promotion/BannerList.js
+++ b/admin-web/src/pages/Promotion/BannerList.js
@@ -342,24 +342,12 @@ class BannerList extends PureComponent {
});
};
- handleRoleAssignModalVisible = (roleModalVisible, record) => {
- const { dispatch } = this.props;
- dispatch({
- type: 'bannerList/setAll',
- payload: {
- roleModalVisible: roleModalVisible,
- formVals: record || {}
- },
- });
- };
-
render() {
// let that = this;
const { dispatch,
list, listLoading, searchParams, pagination,
modalVisible, modalType, formVals,
- confirmLoading,
- roleList, roleModalVisible, roleAssignLoading, roleCheckedKeys } = this.props;
+ confirmLoading,} = this.props;
// 列表属性
const listProps = {
diff --git a/admin-web/src/pages/Promotion/ProductRecommendList.js b/admin-web/src/pages/Promotion/ProductRecommendList.js
new file mode 100644
index 000000000..4d8cf5857
--- /dev/null
+++ b/admin-web/src/pages/Promotion/ProductRecommendList.js
@@ -0,0 +1,398 @@
+/* eslint-disable */
+
+import React, { PureComponent, Fragment } from 'react';
+import { connect } from 'dva';
+import {
+ Card,
+ Form,
+ Input,
+ Button,
+ Modal,
+ message,
+ Table,
+ Divider,
+ Tree,
+ Spin,
+ Row,
+ Col,
+ Select,
+ Icon,
+ InputNumber
+} from 'antd';
+import { checkTypeWithEnglishAndNumbers } from '../../../helpers/validator'
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+
+import styles from './BannerList.less';
+import moment from "moment";
+import PaginationHelper from "../../../helpers/PaginationHelper";
+
+const FormItem = Form.Item;
+const SelectOption = Select.Option;
+const { TreeNode } = Tree;
+const status = ['未知', '正常', '禁用'];
+const types = ['未知', '新品推荐', '热卖推荐'];
+
+// 列表
+function List ({ dataSource, loading, pagination, searchParams, dispatch,
+ handleModalVisible}) {
+
+ function handleStatus(record) {
+ Modal.confirm({
+ title: record.status === 1 ? '确认禁用' : '取消禁用',
+ content: `${record.productSpuId}`,
+ onOk() {
+ dispatch({
+ type: 'productRecommendList/updateStatus',
+ payload: {
+ id: record.id,
+ status: record.status === 1 ? 2 : 1,
+ },
+ });
+ },
+ onCancel() {},
+ });
+ }
+
+ function handleDelete(record) {
+ Modal.confirm({
+ title: `确认删除?`,
+ content: `${record.productSpuId}`,
+ onOk() {
+ dispatch({
+ type: 'productRecommendList/delete',
+ payload: {
+ id: record.id,
+ },
+ });
+ },
+ onCancel() {},
+ });
+ }
+
+ const columns = [
+ {
+ title: '推荐类型',
+ dataIndex: 'type',
+ render(val) {
+ return {types[val]}; // TODO 芋艿,此处要改
+ },
+ },
+ {
+ title: '商品',
+ dataIndex: 'productSpuId',
+ },
+ {
+ title: '排序值',
+ dataIndex: 'sort',
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ render(val) {
+ return {status[val]}; // TODO 芋艿,此处要改
+ },
+ },
+ {
+ title: '备注',
+ dataIndex: 'memo',
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createTime',
+ render: val => {moment(val).format('YYYY-MM-DD HH:mm')},
+ },
+ {
+ title: '操作',
+ width: 360,
+ render: (text, record) => {
+ const statusText = record.status === 1 ? '禁用' : '开启'; // TODO 芋艿,此处要改
+ return (
+
+ handleModalVisible(true, 'update', record)}>编辑
+
+ handleStatus(record)}>
+ {statusText}
+
+ {
+ record.status === 2 ?
+
+
+ handleDelete(record)}>
+ 删除
+
+ : null
+ }
+
+ );
+ },
+ },
+ ];
+
+ function onPageChange(page) { // 翻页
+ dispatch({
+ type: 'productRecommendList/query',
+ payload: {
+ pageNo: page.current,
+ pageSize: page.pageSize,
+ ...searchParams
+ }
+ })
+ }
+
+ return (
+
+ )
+}
+
+// 搜索表单
+// TODO 芋艿,有没办法换成上面那种写法
+const SearchForm = Form.create()(props => {
+ const {
+ form,
+ form: { getFieldDecorator },
+ dispatch
+ } = props;
+
+ function search() {
+ dispatch({
+ type: 'productRecommendList/query',
+ payload: {
+ ...PaginationHelper.defaultPayload,
+ ...form.getFieldsValue()
+ }
+ })
+ }
+
+ // 提交搜索
+ function handleSubmit(e) {
+ // 阻止默认事件
+ e.preventDefault();
+ // 提交搜索
+ search();
+ }
+
+ // 重置搜索
+ function handleReset() {
+ // 重置表单
+ form.resetFields();
+ // 执行搜索
+ search();
+ }
+
+ return (
+
+ );
+});
+
+// 添加 or 修改 Form 表单
+const AddOrUpdateForm = Form.create()(props => {
+ const { dispatch, modalVisible, form, handleModalVisible, modalType, formVals } = props;
+
+ const okHandle = () => {
+ form.validateFields((err, fields) => {
+ if (err) return;
+ // 添加表单
+ if (modalType === 'add') {
+ dispatch({
+ type: 'productRecommendList/add',
+ payload: {
+ body: {
+ ...fields,
+ },
+ callback: () => {
+ // 清空表单
+ form.resetFields();
+ // 提示
+ message.success('添加成功');
+ // 关闭弹窗
+ handleModalVisible();
+ },
+ },
+ });
+ // 修改表单
+ } else {
+ dispatch({
+ type: 'productRecommendList/update',
+ payload: {
+ body: {
+ id: formVals.id,
+ ...fields,
+ },
+ callback: () => {
+ // 清空表单
+ form.resetFields();
+ // 提示
+ message.success('更新成功');
+ // 关闭弹窗
+ handleModalVisible();
+ },
+ },
+ });
+ }
+ });
+ };
+
+ const title = modalType === 'add' ? '新建 Banner' : '更新 Banner';
+ return (
+ handleModalVisible()}
+ >
+
+ {form.getFieldDecorator('type', {
+ rules: [{ required: true, message: '请输入推荐类型!'},
+ ],
+ initialValue: formVals.type,
+ })(
+
+ )}
+
+
+ {form.getFieldDecorator('productSpuId', {
+ rules: [{ required: true, message: '请输入商品!'}, // TODO 芋艿,临时先输入商品编号,后面做成搜索。
+ ],
+ initialValue: formVals.productSpuId,
+ })()}
+
+
+ {form.getFieldDecorator('sort', {
+ rules: [{ required: true, message: '请输入排序值!' }],
+ initialValue: formVals.sort,
+ })()}
+
+
+ {form.getFieldDecorator('memo', {
+ rules: [{ required: false, message: '请输入备注!' },
+ {max: 255, message: '最大长度为 255 位'},
+ ],
+ initialValue: formVals.memo,
+ })()}
+
+
+ );
+});
+
+@connect(({ productRecommendList }) => ({
+ // list: productRecommend.list,
+ // pagination: productRecommend.pagination,
+ ...productRecommendList,
+}))
+
+// 主界面
+@Form.create()
+class BannerList extends PureComponent {
+
+ componentDidMount() {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'productRecommendList/query',
+ payload: {
+ ...PaginationHelper.defaultPayload
+ },
+ });
+ }
+
+ handleModalVisible = (modalVisible, modalType, record) => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'productRecommendList/setAll',
+ payload: {
+ modalVisible,
+ modalType,
+ formVals: record || {}
+ },
+ });
+ };
+
+ render() {
+ // let that = this;
+ const { dispatch,
+ list, listLoading, searchParams, pagination,
+ modalVisible, modalType, formVals,
+ confirmLoading, } = this.props;
+
+ // 列表属性
+ const listProps = {
+ dataSource: list,
+ pagination,
+ searchParams,
+ dispatch,
+ loading: listLoading,
+ confirmLoading,
+ handleModalVisible: this.handleModalVisible, // Function
+ };
+
+ // 搜索表单属性
+ const searchFormProps = {
+ dispatch,
+ };
+
+ // 添加 or 更新表单属性
+ const addOrUpdateFormProps = {
+ modalVisible,
+ modalType,
+ formVals,
+ dispatch,
+ handleModalVisible: this.handleModalVisible, // Function
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default BannerList;
\ No newline at end of file
diff --git a/admin-web/src/pages/Promotion/ProductRecommendList.less b/admin-web/src/pages/Promotion/ProductRecommendList.less
new file mode 100644
index 000000000..7ad3dac3f
--- /dev/null
+++ b/admin-web/src/pages/Promotion/ProductRecommendList.less
@@ -0,0 +1,47 @@
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
+
+.tableList {
+ .tableListOperator {
+ margin-bottom: 16px;
+ button {
+ margin-right: 8px;
+ }
+ }
+}
+
+.tableDelete {
+ color: red;
+}
+
+.tableListForm {
+ :global {
+ .ant-form-item {
+ display: flex;
+ margin-right: 0;
+ margin-bottom: 24px;
+ > .ant-form-item-label {
+ width: auto;
+ padding-right: 8px;
+ line-height: 32px;
+ }
+ .ant-form-item-control {
+ line-height: 32px;
+ }
+ }
+ .ant-form-item-control-wrapper {
+ flex: 1;
+ }
+ }
+ .submitButtons {
+ display: block;
+ margin-bottom: 24px;
+ white-space: nowrap;
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .tableListForm :global(.ant-form-item) {
+ margin-right: 24px;
+ }
+}
\ No newline at end of file
diff --git a/admin-web/src/services/promotion.js b/admin-web/src/services/promotion.js
index 40cdb6b0c..8269f341e 100644
--- a/admin-web/src/services/promotion.js
+++ b/admin-web/src/services/promotion.js
@@ -31,4 +31,36 @@ export async function deleteBanner(params) {
return request(`/promotion-api/admins/banner/delete?${stringify(params)}`, {
method: 'POST',
});
+}
+
+// product recommend
+
+export async function queryProductRecommend(params) {
+ return request(`/promotion-api/admins/product_recommend/page?${stringify(params)}`, {
+ method: 'GET',
+ });
+}
+
+export async function addProductRecommend(params) {
+ return request(`/promotion-api/admins/product_recommend/add?${stringify(params)}`, {
+ method: 'POST',
+ });
+}
+
+export async function updateProductRecommend(params) {
+ return request(`/promotion-api/admins/product_recommend/update?${stringify(params)}`, {
+ method: 'POST',
+ });
+}
+
+export async function updateProductRecommendStatus(params) {
+ return request(`/promotion-api/admins/product_recommend/update_status?${stringify(params)}`, {
+ method: 'POST',
+ });
+}
+
+export async function deleteProductRecommend(params) {
+ return request(`/promotion-api/admins/product_recommend/delete?${stringify(params)}`, {
+ method: 'POST',
+ });
}
\ No newline at end of file
diff --git a/mobile-web/src/api/promotion.js b/mobile-web/src/api/promotion.js
index 9b1528117..c44ea1106 100644
--- a/mobile-web/src/api/promotion.js
+++ b/mobile-web/src/api/promotion.js
@@ -7,4 +7,13 @@ export function getBannerList() {
url: 'promotion-api/users/banner/list',
method: 'get',
});
+}
+
+// Product Recommend
+
+export function getProductRecommendList() {
+ return request({
+ url: 'promotion-api/users/product_recommend/list',
+ method: 'get',
+ });
}
\ No newline at end of file
diff --git a/mobile-web/src/config/env.js b/mobile-web/src/config/env.js
index cd3425bf5..aab5a5714 100644
--- a/mobile-web/src/config/env.js
+++ b/mobile-web/src/config/env.js
@@ -19,8 +19,8 @@ if (process.env.NODE_ENV == 'development') {
// baseUrl = 'http://127.0.0.1';
// baseUrl = 'http://180.167.213.26:18099';
-// dataSources = 'remote';
-dataSources = 'local';
+dataSources = 'remote';
+// dataSources = 'local';
export {
baseUrl,
diff --git a/mobile-web/src/page/page/index.vue b/mobile-web/src/page/page/index.vue
index 46d84d550..d50465234 100644
--- a/mobile-web/src/page/page/index.vue
+++ b/mobile-web/src/page/page/index.vue
@@ -13,14 +13,15 @@
-
-
+
+
-
-
+
@@ -39,7 +40,7 @@ import imageAd from "../../components/page/imageAd.vue";
import imageText from "../../components/page/imageText.vue";
import product from "../../components/page/product.vue";
import { GetPage } from "../../api/page.js";
-import { getBannerList } from '../../api/promotion.js';
+import { getBannerList, getProductRecommendList } from '../../api/promotion.js';
export default {
name:"page",
@@ -60,6 +61,7 @@ export default {
topheight:0,
page:{},
banners: [], // Banner 列表
+ productRecommends: [], // 推荐商品列表
}
},
created:function(){
@@ -69,18 +71,30 @@ export default {
},
mounted: function() {
// 加载 Banner
- let response = getBannerList();
- response.then(data => {
- this.banners = data;
- });
+ {
+ let response = getBannerList();
+ response.then(data => {
+ this.banners = data;
+ });
+ }
+ // 加载 getProductRecommendList
+ {
+ let response = getProductRecommendList();
+ response.then(data => {
+ this.productRecommends = data;
+ });
+ }
},
methods:{
- onBannerClick: function(event, index) {
- debugger;
- console.log(event);
- },
+ // onBannerClick: function(event, index) {
+ // debugger;
+ // console.log(event);
+ // },
settopheight:function(value){
this.topheight=value;
+ },
+ showProduct(product){
+ this.$router.push('/product/'+product.id);
}
}
}
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrAndValuePairBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrAndValuePairBO.java
index 527d3865f..dfea5d9f5 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrAndValuePairBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrAndValuePairBO.java
@@ -1,9 +1,11 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
+
/**
* 商品规格明细 BO
*/
-public class ProductAttrAndValuePairBO {
+public class ProductAttrAndValuePairBO implements Serializable {
/**
* 规格编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrBO.java
index 1c1759624..2da299c38 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.Date;
/**
* 商品规格 VO
*/
-public class ProductAttrBO {
+public class ProductAttrBO implements Serializable {
/**
* 规格编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrDetailBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrDetailBO.java
index dcb6c78df..6a3c5edf3 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrDetailBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrDetailBO.java
@@ -1,12 +1,13 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 商品规格明细 VO
*/
-public class ProductAttrDetailBO {
+public class ProductAttrDetailBO implements Serializable {
/**
* 规格编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrPageBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrPageBO.java
index 14923f57f..bf6176f5c 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrPageBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrPageBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
/**
* 商品规格明细分页 BO
*/
-public class ProductAttrPageBO {
+public class ProductAttrPageBO implements Serializable {
/**
* 规格数组
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrSimpleBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrSimpleBO.java
index 1a0ecb399..9eda2af66 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrSimpleBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrSimpleBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
/**
* 商品规格精简 VO
*/
-public class ProductAttrSimpleBO {
+public class ProductAttrSimpleBO implements Serializable {
/**
* 规格编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueBO.java
index 0cb4a77f0..3fe44272e 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.Date;
/**
* 商品规格值 VO
*/
-public class ProductAttrValueBO {
+public class ProductAttrValueBO implements Serializable {
/**
* 规格值编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueDetailBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueDetailBO.java
index 5e3954fed..c70690e99 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueDetailBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueDetailBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.Date;
/**
* 商品规格值 VO
*/
-public class ProductAttrValueDetailBO {
+public class ProductAttrValueDetailBO implements Serializable {
/**
* 规格值编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueSimpleBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueSimpleBO.java
index ea23bc301..7d1bb0ec9 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueSimpleBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductAttrValueSimpleBO.java
@@ -1,9 +1,11 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
+
/**
* 商品规格值 VO
*/
-public class ProductAttrValueSimpleBO {
+public class ProductAttrValueSimpleBO implements Serializable {
/**
* 规格值编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductCategoryBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductCategoryBO.java
index ae56b4d2d..6450145cc 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductCategoryBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductCategoryBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.Date;
/**
* 商品分类 BO
*/
-public class ProductCategoryBO {
+public class ProductCategoryBO implements Serializable {
/**
* 分类编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuBO.java
index 7bc35b788..7ef2ea4ff 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuBO.java
@@ -1,6 +1,8 @@
package cn.iocoder.mall.product.api.bo;
-public class ProductSkuBO {
+import java.io.Serializable;
+
+public class ProductSkuBO implements Serializable {
private Integer id;
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuDetailBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuDetailBO.java
index 6435cc69c..e6e9d5ab4 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuDetailBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSkuDetailBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
/**
* 商品 Sku 明细 BO
*/
-public class ProductSkuDetailBO {
+public class ProductSkuDetailBO implements Serializable {
/**
* sku 编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuBO.java
index 711e7bfb1..c40702e1a 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuBO.java
@@ -1,8 +1,9 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
-public class ProductSpuBO {
+public class ProductSpuBO implements Serializable {
/**
* SPU 编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuDetailBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuDetailBO.java
index 0b068aad3..2ad313429 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuDetailBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuDetailBO.java
@@ -1,11 +1,12 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
/**
* 商品 Spu 明细 BO(包括 Sku 明细)
*/
-public class ProductSpuDetailBO {
+public class ProductSpuDetailBO implements Serializable {
/**
* SPU 编号
diff --git a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuPageBO.java b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuPageBO.java
index 89ca0756d..0d6093743 100644
--- a/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuPageBO.java
+++ b/product/product-service-api/src/main/java/cn/iocoder/mall/product/api/bo/ProductSpuPageBO.java
@@ -1,8 +1,9 @@
package cn.iocoder.mall.product.api.bo;
+import java.io.Serializable;
import java.util.List;
-public class ProductSpuPageBO {
+public class ProductSpuPageBO implements Serializable {
/**
* Spu 数组
diff --git a/product/product-service-impl/src/main/java/cn/iocoder/mall/product/dao/ProductSpuMapper.java b/product/product-service-impl/src/main/java/cn/iocoder/mall/product/dao/ProductSpuMapper.java
index 4428dd9ce..aab399241 100644
--- a/product/product-service-impl/src/main/java/cn/iocoder/mall/product/dao/ProductSpuMapper.java
+++ b/product/product-service-impl/src/main/java/cn/iocoder/mall/product/dao/ProductSpuMapper.java
@@ -12,7 +12,7 @@ public interface ProductSpuMapper {
ProductSpuDO selectById(Integer id);
- List
selectByIds(Collection ids);
+ List selectByIds(@Param("ids") Collection ids);
void insert(ProductSpuDO productSpuDO);
diff --git a/promotion/promotion-application/src/main/java/cn/iocoder/mall/promotion/application/controller/users/UsersProductRecommendController.java b/promotion/promotion-application/src/main/java/cn/iocoder/mall/promotion/application/controller/users/UsersProductRecommendController.java
index 7e2dbd873..1b3c015e3 100644
--- a/promotion/promotion-application/src/main/java/cn/iocoder/mall/promotion/application/controller/users/UsersProductRecommendController.java
+++ b/promotion/promotion-application/src/main/java/cn/iocoder/mall/promotion/application/controller/users/UsersProductRecommendController.java
@@ -17,13 +17,14 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
-@RequestMapping("users/banner")
+@RequestMapping("users/product_recommend")
@Api("商品推荐模块")
public class UsersProductRecommendController {
@@ -34,7 +35,7 @@ public class UsersProductRecommendController {
@GetMapping("/list")
@ApiOperation("获得所有 Banner 列表")
- public CommonResult> list() {
+ public CommonResult