Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Y
yudao-cloud
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
hblj
yudao-cloud
Commits
2519cf00
提交
2519cf00
authored
5月 05, 2019
作者:
YunaiV
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
前端:抽出 HtmlEditor 组件
前端:完善商品编辑,基本算完成了
上级
35077dcf
隐藏空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
187 行增加
和
94 行删除
+187
-94
HtmlEditor.js
admin-web/src/components/Editor/HtmlEditor.js
+116
-0
PicturesWall.js
admin-web/src/components/Image/PicturesWall.js
+0
-1
ProductAttrSelectFormItem.js
...n-web/src/components/Product/ProductAttrSelectFormItem.js
+1
-1
productSpuAddOrUpdate.js
admin-web/src/models/product/productSpuAddOrUpdate.js
+3
-1
ProductSpuAddOrUpdate.js
admin-web/src/pages/Product/ProductSpuAddOrUpdate.js
+58
-90
功能列表-管理后台.md
docs/guides/功能列表/功能列表-管理后台.md
+1
-1
ProductErrorCodeEnum.java
...coder/mall/product/api/constant/ProductErrorCodeEnum.java
+1
-0
ProductSpuServiceImpl.java
...n/iocoder/mall/product/service/ProductSpuServiceImpl.java
+7
-0
没有找到文件。
admin-web/src/components/Editor/HtmlEditor.js
0 → 100644
浏览文件 @
2519cf00
import
React
from
"react"
;
import
'braft-editor/dist/index.css'
import
BraftEditor
from
'braft-editor'
import
{
ContentUtils
}
from
'braft-utils'
import
{
ImageUtils
}
from
'braft-finder'
import
{
fileGetQiniuToken
}
from
"../../services/admin"
;
import
uuid
from
"js-uuid"
;
import
*
as
qiniu
from
"qiniu-js"
;
import
{
Icon
,
Upload
}
from
"antd"
;
class
HtmlEditor
extends
React
.
Component
{
state
=
{
editorState
:
BraftEditor
.
createEditorState
(
null
),
};
handleChange
=
(
editorState
)
=>
{
this
.
setState
({
editorState
})
};
uploadHandler
=
async
(
param
)
=>
{
if
(
!
param
.
file
)
{
return
false
}
debugger
;
const
tokenResult
=
await
fileGetQiniuToken
();
if
(
tokenResult
.
code
!==
0
)
{
alert
(
'获得七牛上传 Token 失败'
);
return
false
;
}
let
token
=
tokenResult
.
data
;
let
that
=
this
;
const
reader
=
new
FileReader
();
const
file
=
param
.
file
;
reader
.
readAsArrayBuffer
(
file
);
let
fileData
=
null
;
reader
.
onload
=
(
e
)
=>
{
let
key
=
uuid
.
v4
();
// TODO 芋艿,可能后面要优化。MD5?
let
observable
=
qiniu
.
upload
(
file
,
key
,
token
);
// TODO 芋艿,最后后面去掉 qiniu 的库依赖,直接 http 请求,这样更轻量
observable
.
subscribe
(
function
()
{
// next
},
function
(
e
)
{
// error
// TODO 芋艿,后续补充
// debugger;
},
function
(
response
)
{
// complete
that
.
setState
({
editorState
:
ContentUtils
.
insertMedias
(
that
.
state
.
editorState
,
[{
type
:
'IMAGE'
,
url
:
'http://static.shop.iocoder.cn/'
+
response
.
key
,
}])
})
});
}
};
getHtml
()
{
return
this
.
state
.
editorState
.
toHTML
();
}
setHtml
=
(
html
)
=>
{
this
.
setState
({
editorState
:
BraftEditor
.
createEditorState
(
html
),
})
};
isEmpty
=
()
=>
{
return
this
.
state
.
editorState
.
isEmpty
();
};
render
()
{
// const controls = ['bold', 'italic', 'underline', 'text-color', 'separator', 'link', 'separator'];
const
extendControls
=
[
{
key
:
'antd-uploader'
,
type
:
'component'
,
component
:
(
<
Upload
accept
=
"image/*"
showUploadList
=
{
false
}
customRequest
=
{
this
.
uploadHandler
}
>
{
/* 这里的按钮最好加上type="button",以避免在表单容器中触发表单提交,用Antd的Button组件则无需如此 */
}
<
button
type
=
"button"
className
=
"control-item button upload-button"
data
-
title
=
"插入图片"
>
<
Icon
type
=
"picture"
theme
=
"filled"
/>
<
/button
>
<
/Upload
>
)
}
];
return
(
<
div
style
=
{{
border
:
'1px solid #d1d1d1'
,
'border-radius'
:
'5px'
}}
>
<
BraftEditor
value
=
{
this
.
state
.
editorState
}
onChange
=
{
this
.
handleChange
}
defaultValue
=
{
this
.
state
.
initialContent
}
// controls={controls}
extendControls
=
{
extendControls
}
contentStyle
=
{{
height
:
200
}}
/
>
<
/div
>
)
}
}
{
/**/
}
// </div>
export
default
HtmlEditor
;
admin-web/src/components/Image/PicturesWall.js
浏览文件 @
2519cf00
...
...
@@ -77,7 +77,6 @@ class PicturesWall extends React.Component {
// });
// 使用 FileReader 将上传的文件转换成二进制流,满足 'application/octet-stream' 格式的要求
debugger
;
const
reader
=
new
FileReader
();
reader
.
readAsArrayBuffer
(
file
);
let
fileData
=
null
;
...
...
admin-web/src/components/Product/ProductAttrSelectFormItem.js
浏览文件 @
2519cf00
...
...
@@ -68,7 +68,7 @@ class AttrValueSelect extends Select {
export
default
class
ProductAttrSelectFormItem
extends
PureComponent
{
handleSelectAttr
=
(
value
,
option
)
=>
{
debugger
;
//
debugger;
// console.log(value);
// console.log(option);
// debugger;
...
...
admin-web/src/models/product/productSpuAddOrUpdate.js
浏览文件 @
2519cf00
...
...
@@ -35,7 +35,8 @@ export default {
// price: // 价格
// quantity: // 数量
// }
]
],
},
effects
:
{
...
...
@@ -308,6 +309,7 @@ export default {
...
state
,
skus
:
[],
attrTree
:
[],
spu
:
{},
}
},
changeLoading
(
state
,
{
payload
})
{
...
...
admin-web/src/pages/Product/ProductSpuAddOrUpdate.js
浏览文件 @
2519cf00
...
...
@@ -5,12 +5,8 @@ import React, {PureComponent, Fragment, Component} from 'react';
// import fs from 'fs';
import
{
connect
}
from
'dva'
;
import
moment
from
'moment'
;
import
{
Card
,
Form
,
Input
,
Radio
,
Button
,
Modal
,
Select
,
Upload
,
Icon
,
Spin
}
from
'antd'
;
import
{
Card
,
Form
,
Input
,
Radio
,
Button
,
Modal
,
Select
,
Upload
,
Icon
,
Spin
,
TreeSelect
}
from
'antd'
;
import
PageHeaderWrapper
from
'@/components/PageHeaderWrapper'
;
import
'braft-editor/dist/index.css'
import
BraftEditor
from
'braft-editor'
import
{
ContentUtils
}
from
'braft-utils'
import
{
ImageUtils
}
from
'braft-finder'
// import * as qiniu from 'qiniu-js'
// import uuid from 'js-uuid';
...
...
@@ -23,13 +19,14 @@ import PicturesWall from "../../components/Image/PicturesWall";
import
{
fileGetQiniuToken
}
from
"../../services/admin"
;
import
uuid
from
"js-uuid"
;
import
*
as
qiniu
from
"qiniu-js"
;
import
HtmlEditor
from
"../../components/Editor/HtmlEditor"
;
const
FormItem
=
Form
.
Item
;
const
RadioGroup
=
Radio
.
Group
;
const
Option
=
Select
.
Option
;
// roleList
@
connect
(({
productAttrList
,
productSpuAddOrUpdate
,
})
=>
({
@
connect
(({
productAttrList
,
productSpuAddOrUpdate
,
productCategoryList
})
=>
({
// list: productSpuList.list.spus,
// loading: loading.models.productSpuList,
productAttrList
,
...
...
@@ -39,6 +36,7 @@ const Option = Select.Option;
spu
:
productSpuAddOrUpdate
.
spu
,
attrTree
:
productSpuAddOrUpdate
.
attrTree
,
skus
:
productSpuAddOrUpdate
.
skus
,
categoryTree
:
productCategoryList
.
list
,
}))
@
Form
.
create
()
...
...
@@ -47,12 +45,16 @@ class ProductSpuAddOrUpdate extends Component {
// modalVisible: false,
modalType
:
'add'
,
//add update
// initValues: {},
editorState
:
BraftEditor
.
createEditorState
(
null
)
,
htmlEditor
:
undefined
,
};
componentDidMount
()
{
const
{
dispatch
}
=
this
.
props
;
const
that
=
this
;
// 重置表单
dispatch
({
type
:
'productSpuAddOrUpdate/clear'
,
});
// 判断是否是更新
const
params
=
new
URLSearchParams
(
this
.
props
.
location
.
search
);
if
(
params
.
get
(
"id"
))
{
...
...
@@ -66,6 +68,8 @@ class ProductSpuAddOrUpdate extends Component {
payload
:
parseInt
(
id
),
callback
:
function
(
data
)
{
that
.
refs
.
picturesWall
.
setUrls
(
data
.
picUrls
);
// TODO 后续找找,有没更合适的做法
// debugger;
that
.
state
.
htmlEditor
.
setHtml
(
data
.
description
);
}
})
}
...
...
@@ -78,53 +82,13 @@ class ProductSpuAddOrUpdate extends Component {
pageSize
:
10
,
},
});
//
重置表单
//
获得商品分类
dispatch
({
type
:
'productSpuAddOrUpdate/clear'
,
})
type
:
'productCategoryList/tree'
,
payload
:
{},
});
}
handleChange
=
(
editorState
)
=>
{
this
.
setState
({
editorState
})
};
uploadHandler
=
async
(
param
)
=>
{
if
(
!
param
.
file
)
{
return
false
}
debugger
;
const
tokenResult
=
await
fileGetQiniuToken
();
if
(
tokenResult
.
code
!==
0
)
{
alert
(
'获得七牛上传 Token 失败'
);
return
false
;
}
let
token
=
tokenResult
.
data
;
let
that
=
this
;
const
reader
=
new
FileReader
();
const
file
=
param
.
file
;
reader
.
readAsArrayBuffer
(
file
);
let
fileData
=
null
;
reader
.
onload
=
(
e
)
=>
{
let
key
=
uuid
.
v4
();
// TODO 芋艿,可能后面要优化。MD5?
let
observable
=
qiniu
.
upload
(
file
,
key
,
token
);
// TODO 芋艿,最后后面去掉 qiniu 的库依赖,直接 http 请求,这样更轻量
observable
.
subscribe
(
function
()
{
// next
},
function
(
e
)
{
// error
// TODO 芋艿,后续补充
// debugger;
},
function
(
response
)
{
// complete
that
.
setState
({
editorState
:
ContentUtils
.
insertMedias
(
that
.
state
.
editorState
,
[{
type
:
'IMAGE'
,
url
:
'http://static.shop.iocoder.cn/'
+
response
.
key
,
}])
})
});
}
};
handleAddAttr
=
e
=>
{
// alert('你猜');
const
{
dispatch
}
=
this
.
props
;
...
...
@@ -139,6 +103,11 @@ class ProductSpuAddOrUpdate extends Component {
e
.
preventDefault
();
const
{
skus
,
dispatch
}
=
this
.
props
;
const
{
modalType
,
id
}
=
this
.
state
;
if
(
this
.
state
.
htmlEditor
.
isEmpty
())
{
alert
(
'请设置商品描述!'
);
return
;
}
const
description
=
this
.
state
.
htmlEditor
.
getHtml
();
// 获得图片
let
picUrls
=
this
.
refs
.
picturesWall
.
getUrls
();
// TODO 芋艿,后续找找其他做法
if
(
picUrls
.
length
===
0
)
{
...
...
@@ -166,9 +135,11 @@ class ProductSpuAddOrUpdate extends Component {
alert
(
'请设置商品规格!'
);
return
;
}
// debugger;
this
.
props
.
form
.
validateFields
((
err
,
values
)
=>
{
// debugger;
// 获得富文本编辑的描述
if
(
!
err
)
{
if
(
modalType
===
'add'
)
{
dispatch
({
...
...
@@ -177,7 +148,8 @@ class ProductSpuAddOrUpdate extends Component {
body
:
{
...
values
,
picUrls
:
picUrls
.
join
(
','
),
skuStr
:
JSON
.
stringify
(
skuStr
)
skuStr
:
JSON
.
stringify
(
skuStr
),
description
,
}
},
});
...
...
@@ -189,7 +161,8 @@ class ProductSpuAddOrUpdate extends Component {
...
values
,
id
,
picUrls
:
picUrls
.
join
(
','
),
skuStr
:
JSON
.
stringify
(
skuStr
)
skuStr
:
JSON
.
stringify
(
skuStr
),
description
,
}
},
});
...
...
@@ -201,27 +174,26 @@ class ProductSpuAddOrUpdate extends Component {
render
()
{
// debugger;
const
{
form
,
skus
,
attrTree
,
allAttrTree
,
loading
,
spu
,
dispatch
}
=
this
.
props
;
const
{
form
,
skus
,
attrTree
,
allAttrTree
,
loading
,
spu
,
categoryTree
,
dispatch
}
=
this
.
props
;
// const that = this;
const
controls
=
[
'bold'
,
'italic'
,
'underline'
,
'text-color'
,
'separator'
,
'link'
,
'separator'
];
const
extendControls
=
[
{
key
:
'antd-uploader'
,
type
:
'component'
,
component
:
(
<
Upload
accept
=
"image/*"
showUploadList
=
{
false
}
customRequest
=
{
this
.
uploadHandler
}
>
{
/* 这里的按钮最好加上type="button",以避免在表单容器中触发表单提交,用Antd的Button组件则无需如此 */
}
<
button
type
=
"button"
className
=
"control-item button upload-button"
data
-
title
=
"插入图片"
>
<
Icon
type
=
"picture"
theme
=
"filled"
/>
<
/button
>
<
/Upload
>
)
}
];
// 处理分类筛选
const
buildSelectTree
=
(
list
)
=>
{
return
list
.
map
(
item
=>
{
let
children
=
[];
if
(
item
.
children
)
{
children
=
buildSelectTree
(
item
.
children
);
}
return
{
title
:
item
.
name
,
value
:
item
.
id
,
key
:
item
.
id
,
children
,
selectable
:
item
.
pid
>
0
};
});
};
let
categoryTreeSelect
=
buildSelectTree
(
categoryTree
);
// 添加规格
// debugger;
...
...
@@ -254,6 +226,7 @@ class ProductSpuAddOrUpdate extends Component {
dispatch
:
dispatch
,
};
// console.log(productSkuProps);
// let htmlEditor = undefined;
return
(
<
PageHeaderWrapper
title
=
""
>
...
...
@@ -275,8 +248,16 @@ class ProductSpuAddOrUpdate extends Component {
<
FormItem
labelCol
=
{{
span
:
5
}}
wrapperCol
=
{{
span
:
15
}}
label
=
"分类编号"
>
{
form
.
getFieldDecorator
(
'cid'
,
{
rules
:
[{
required
:
true
,
message
:
'请输入分类编号!'
}],
initialValue
:
spu
.
cid
,
// TODO 芋艿,和面做成下拉框
})(
<
Input
placeholder
=
"请输入"
/>
)}
initialValue
:
spu
.
cid
,
})(
<
TreeSelect
showSearch
style
=
{{
width
:
300
}}
dropdownStyle
=
{{
maxHeight
:
400
,
overflow
:
'auto'
}}
treeData
=
{
categoryTreeSelect
}
placeholder
=
"选择父分类"
/>
)}
<
/FormItem
>
<
FormItem
labelCol
=
{{
span
:
5
}}
wrapperCol
=
{{
span
:
15
}}
label
=
"商品主图"
extra
=
"建议尺寸:800*800PX,单张大小不超过 2M,最多可上传 10 张"
>
...
...
@@ -307,21 +288,8 @@ class ProductSpuAddOrUpdate extends Component {
<
ProductSkuAddOrUpdateTable
{...
productSkuProps
}
/
>
<
/FormItem> : '
'
}
<
FormItem
labelCol
=
{{
span
:
5
}}
wrapperCol
=
{{
span
:
15
}}
label
=
"商品描述"
>
{
form
.
getFieldDecorator
(
'description'
,
{
rules
:
[{
required
:
true
,
message
:
'请输入商品描述!'
}],
initialValue
:
spu
.
description
,
// TODO 修改
})(
<
div
style
=
{{
border
:
'1px solid #d1d1d1'
,
'border-radius'
:
'5px'
}}
>
<
BraftEditor
value
=
{
this
.
state
.
editorState
}
onChange
=
{
this
.
handleChange
}
controls
=
{
controls
}
extendControls
=
{
extendControls
}
contentStyle
=
{{
height
:
200
}}
/
>
<
/div
>
)}
<
FormItem
labelCol
=
{{
span
:
5
}}
wrapperCol
=
{{
span
:
15
}}
label
=
"商品描述"
required
=
{
false
}
>
<
HtmlEditor
ref
=
{(
node
)
=>
this
.
state
.
htmlEditor
=
node
}
/
>
<
Button
type
=
"primary"
htmlType
=
"submit"
style
=
{{
marginLeft
:
8
}}
onSubmit
=
{
this
.
handleSubmit
}
>
保存
<
/Button
>
<
/FormItem
>
<
/Form
>
...
...
docs/guides/功能列表/功能列表-管理后台.md
浏览文件 @
2519cf00
...
...
@@ -9,7 +9,7 @@
-
[
]
店铺资产
-
[
]
TODO 未开始
-
[
]
商品管理
-
[
]
发布商品
-
[
x
]
发布商品
-
[
]
商品管理
-
[
x
]
展示类目
-
[
]
品牌管理
...
...
product/product-service-api/src/main/java/cn/iocoder/mall/product/api/constant/ProductErrorCodeEnum.java
浏览文件 @
2519cf00
...
...
@@ -21,6 +21,7 @@ public enum ProductErrorCodeEnum {
PRODUCT_SPU_ATTR_NUMBERS_MUST_BE_EQUALS
(
1003002001
,
"一个 Spu 下的每个 Sku ,其规格数必须一致"
),
PRODUCT_SPU_SKU__NOT_DUPLICATE
(
1003002002
,
"一个 Spu 下的每个 Sku ,必须不重复"
),
PRODUCT_SPU_NOT_EXISTS
(
1003002003
,
"Spu 不存在"
),
PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2
(
1003002003
,
"Spu 只能添加在二级分类下"
),
// ========== PRODUCT ATTR + ATTR_VALUE 模块 ==========
PRODUCT_ATTR_VALUE_NOT_EXIST
(
1003003000
,
"商品属性值不存在"
),
...
...
product/product-service-impl/src/main/java/cn/iocoder/mall/product/service/ProductSpuServiceImpl.java
浏览文件 @
2519cf00
...
...
@@ -7,6 +7,7 @@ import cn.iocoder.common.framework.util.StringUtil;
import
cn.iocoder.common.framework.vo.CommonResult
;
import
cn.iocoder.mall.product.api.ProductSpuService
;
import
cn.iocoder.mall.product.api.bo.*
;
import
cn.iocoder.mall.product.api.constant.ProductCategoryConstants
;
import
cn.iocoder.mall.product.api.constant.ProductErrorCodeEnum
;
import
cn.iocoder.mall.product.api.constant.ProductSpuConstants
;
import
cn.iocoder.mall.product.api.dto.ProductSkuAddOrUpdateDTO
;
...
...
@@ -107,6 +108,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
if
(
validCategoryResult
.
isError
())
{
return
CommonResult
.
error
(
validCategoryResult
);
}
if
(
ProductCategoryConstants
.
PID_ROOT
.
equals
(
validCategoryResult
.
getData
().
getPid
()))
{
// 商品只能添加到二级分类下
return
ServiceExceptionUtil
.
error
(
ProductErrorCodeEnum
.
PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2
.
getCode
());
}
// 校验规格是否存在
Set
<
Integer
>
productAttrValueIds
=
new
HashSet
<>();
productSpuAddDTO
.
getSkus
().
forEach
(
productSkuAddDTO
->
productAttrValueIds
.
addAll
(
productSkuAddDTO
.
getAttrs
()));
...
...
@@ -167,6 +171,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
if
(
validCategoryResult
.
isError
())
{
return
CommonResult
.
error
(
validCategoryResult
);
}
if
(
ProductCategoryConstants
.
PID_ROOT
.
equals
(
validCategoryResult
.
getData
().
getPid
()))
{
// 商品只能添加到二级分类下
return
ServiceExceptionUtil
.
error
(
ProductErrorCodeEnum
.
PRODUCT_SPU_CATEGORY_MUST_BE_LEVEL2
.
getCode
());
}
// 校验规格是否存在
Set
<
Integer
>
productAttrValueIds
=
new
HashSet
<>();
productSpuUpdateDTO
.
getSkus
().
forEach
(
productSkuAddDTO
->
productAttrValueIds
.
addAll
(
productSkuAddDTO
.
getAttrs
()));
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论