index.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. import React, {Component} from 'react'
  2. import './index.css'
  3. import {
  4. Picker,
  5. NavBar,
  6. Accordion,
  7. List,
  8. InputItem,
  9. ImagePicker,
  10. Button,
  11. ActivityIndicator,
  12. Stepper,
  13. Modal
  14. } from 'antd-mobile'
  15. import {Switch, Row, Col, Icon} from 'antd'
  16. import {withRouter} from 'react-router-dom'
  17. import {
  18. create_product,
  19. update_product,
  20. category_by_props,
  21. productbyprops,
  22. update_category,
  23. delete_category,
  24. create_category
  25. } from "../../../../utils/gql"
  26. import {Query, Mutation} from "react-apollo"
  27. import gql from "graphql-tag"
  28. import {idGen} from "../../../../utils/func"
  29. import moment from 'moment'
  30. import {storeFile} from "../../../../configs/url"
  31. import axios from 'axios'
  32. import classNames from 'classnames'
  33. const Item = List.Item
  34. const categoryFilterRefetch = {
  35. "status": "1",
  36. "limit": 7,
  37. "sort_by": {"order": "asc"}
  38. }
  39. const categoryFilter = {
  40. "sort_by": {"order": "asc"}
  41. }
  42. class Goods extends Component {
  43. constructor(props) {
  44. super(props)
  45. this.state = {
  46. accordionKey: undefined
  47. }
  48. }
  49. render() {
  50. let {accordionKey} = this.state
  51. return (
  52. <div className='goods-wrap'>
  53. <NavBar
  54. className='navbar'
  55. mode="light"
  56. icon={<Icon type="left"/>}
  57. onLeftClick={() => {
  58. this.props.history.go(-2)
  59. }}
  60. >商品管理</NavBar>
  61. <div className='content-wrap'>
  62. <div className='my-list-subtitle' style={{color: 'grey'}}><Icon type="bulb" style={{marginRight: 10}}/>{accordionKey? '折叠单项以展开更多分类':'请选择需要打开的分类'}</div>
  63. <Accordion className="my-accordion" onChange={(key) => {
  64. this.setState({
  65. accordionKey: key[0]
  66. })
  67. }}>
  68. <Accordion.Panel header="全部分类"
  69. className={classNames({'hidden': accordionKey === '1' || accordionKey === '2'})}>
  70. <AllCategory/>
  71. </Accordion.Panel>
  72. <Accordion.Panel header="全部商品"
  73. className={classNames({'hidden': accordionKey === '0' || accordionKey === '2'})}>
  74. <AllGoods/>
  75. </Accordion.Panel>
  76. <Accordion.Panel header="添加商品"
  77. className={classNames({'hidden': accordionKey === '0' || accordionKey === '1'})}>
  78. <AddGoods/>
  79. </Accordion.Panel>
  80. </Accordion>
  81. </div>
  82. </div>
  83. )
  84. }
  85. }
  86. class AddGoods extends Component {
  87. constructor(props) {
  88. super(props)
  89. let state = {
  90. files: [],
  91. imgDatas: [],
  92. }
  93. if (props.good === undefined) {
  94. this.state = {
  95. ...state,
  96. name: '',
  97. price: 0,
  98. intro: '',
  99. stock: 20,
  100. category: '',
  101. category_id: '',
  102. newGood: true
  103. }
  104. } else {
  105. // console.log(props.good)
  106. let {name, price, intro, stock, id} = props.good
  107. this.state = {
  108. ...state,
  109. id,
  110. name,
  111. price,
  112. intro,
  113. stock,
  114. category: props.good.category_id.name,
  115. category_id: [props.good.category_id.id],
  116. newGood: false
  117. }
  118. }
  119. }
  120. onChange = (id) => (files, operationType) => {
  121. let imgDatas = []
  122. files.forEach((file, index) => {
  123. let base64Cont = files[index].url.split(',')[1]
  124. let imgType = files[index].file.type.split('/')[1]
  125. let imgNewName = `good_id_${id}.${imgType}`
  126. const imgData = {
  127. 'file-name': `e-commerce/images/${imgNewName}`,
  128. 'bucket': 'case',
  129. 'cont': base64Cont,
  130. 'public': true,
  131. 'format': 'base64'
  132. }
  133. imgDatas.push(imgData)
  134. })
  135. this.setState({
  136. imgDatas,
  137. files
  138. })
  139. }
  140. uploadImg = () => {
  141. let {imgDatas} = this.state
  142. return imgDatas.map((imgData) => (
  143. axios({
  144. url: storeFile,
  145. method: 'post',
  146. data: imgData
  147. })
  148. ))
  149. }
  150. render() {
  151. let {files, imgDatas, name, intro, stock, price, category_id, newGood} = this.state
  152. let id = newGood ? idGen('goods') : this.state.id
  153. return (
  154. <List className="my-add-goods-list">
  155. <InputItem onChange={(e) => {
  156. this.setState({name: e})
  157. }} value={name} placeholder="请输入名称">名称</InputItem>
  158. <Query query={gql(category_by_props)} variables={categoryFilter}>
  159. {
  160. ({loading, error, data}) => {
  161. if (loading) {
  162. return (
  163. <div className="loading-center">
  164. <ActivityIndicator text="Loading..." size="large"/>
  165. </div>
  166. )
  167. }
  168. if (error) {
  169. return 'error!'
  170. }
  171. let categoryList = data.categorybyprops.map(category => {
  172. category.value = category.id
  173. category.label = category.text
  174. return category
  175. })
  176. return (
  177. <Picker data={categoryList}
  178. cols={1}
  179. value={this.state.category_id}
  180. onChange={v => {
  181. this.setState({category_id: v})
  182. }}
  183. >
  184. <List.Item arrow="horizontal">选择种类</List.Item>
  185. </Picker>
  186. )
  187. }
  188. }
  189. </Query>
  190. <InputItem onChange={(e) => {
  191. this.setState({intro: e})
  192. }} value={intro} placeholder="请输入简介">简介</InputItem>
  193. <InputItem onChange={(e) => {
  194. this.setState({price: e})
  195. }} value={price} placeholder="请输入价格">价格</InputItem>
  196. <Item extra={<Stepper onChange={(e) => {
  197. this.setState({stock: e})
  198. }} value={stock} style={{width: '100%', minWidth: '100px'}} showNumber size="small"/>}>库存</Item>
  199. <div className='list-others'>
  200. <div className='list-others-subtitle'>商品图片</div>
  201. <ImagePicker
  202. files={files}
  203. onChange={this.onChange(id)}
  204. onImageClick={(index, fs) => console.log(index, fs)}
  205. selectable={true}
  206. multiple={false}
  207. />
  208. {
  209. newGood ?
  210. <Mutation mutation={gql(create_product)} refetchQueries={[
  211. {query: gql(productbyprops), variables: {}},
  212. {query: gql(productbyprops), variables: {status: '1', recommend: 1}}
  213. ]}>
  214. {(createproduct, {loading, error}) => {
  215. if (loading)
  216. return (
  217. <div className="loading">
  218. <div className="align">
  219. <ActivityIndicator text="Loading..." size="large"/>
  220. </div>
  221. </div>
  222. )
  223. if (error)
  224. return 'error'
  225. let varObj = {
  226. id,
  227. unit: '1件',
  228. status: '1',
  229. recommend: 0,
  230. category_id: category_id[0],
  231. name,
  232. stock,
  233. intro,
  234. price,
  235. createdAt: moment().format('YYYY-MM-DD HH:mm:ss'),
  236. updatedAt: ''
  237. }
  238. return (
  239. <Button type="primary" size="small" inline onClick={() => {
  240. Promise.all(this.uploadImg()).then(res => {
  241. let prefix = 'https://case-1254337200.cos.ap-beijing.myqcloud.com/'
  242. let img = imgDatas.length === 1 ? prefix + imgDatas[0]['file-name'] : imgDatas.map((imgData, index) => (
  243. prefix + imgDatas[index]['file-name']
  244. ))
  245. let variables = {...varObj}
  246. if (imgDatas.length !== 0) {
  247. variables.img = img
  248. }
  249. createproduct({variables})
  250. })
  251. }}>创建</Button>
  252. )
  253. }}
  254. </Mutation>
  255. :
  256. <Mutation mutation={gql(update_product)} refetchQueries={[
  257. {query: gql(productbyprops), variables: {}},
  258. {query: gql(productbyprops), variables: {status: '1', recommend: 1}}
  259. ]}>
  260. {(updateproduct, {loading, error}) => {
  261. if (loading)
  262. return (
  263. <div className="loading">
  264. <div className="align">
  265. <ActivityIndicator text="Loading..." size="large"/>
  266. </div>
  267. </div>
  268. )
  269. if (error)
  270. return 'error'
  271. let varObj = {
  272. id,
  273. unit: '1件',
  274. status: '1',
  275. recommend: 0,
  276. category_id: category_id[0],
  277. name,
  278. stock,
  279. intro,
  280. price,
  281. updatedAt: moment().format('YYYY-MM-DD HH:mm:ss')
  282. }
  283. return (
  284. <Button type="primary" size="small" inline onClick={() => {
  285. Promise.all(this.uploadImg()).then(res => {
  286. let prefix = 'https://case-1254337200.cos.ap-beijing.myqcloud.com/'
  287. let img = imgDatas.length === 1 ? prefix + imgDatas[0]['file-name'] : imgDatas.map((imgData, index) => (
  288. prefix + imgDatas[index]['file-name']
  289. ))
  290. let variables = {...varObj}
  291. if (imgDatas.length !== 0) {
  292. variables.img = img
  293. }
  294. updateproduct({variables})
  295. })
  296. }}>更新</Button>
  297. )
  298. }}
  299. </Mutation>
  300. }
  301. </div>
  302. </List>
  303. )
  304. }
  305. }
  306. class AllGoods extends Component {
  307. constructor(props) {
  308. super(props)
  309. this.state = {
  310. modal: false,
  311. product: {}
  312. }
  313. }
  314. controlModal = (bool) => () => {
  315. this.setState({
  316. modal: bool
  317. })
  318. if (!bool) {
  319. this.setState({
  320. product: {}
  321. })
  322. }
  323. }
  324. render() {
  325. let {modal, product} = this.state
  326. return (
  327. <Query query={gql(productbyprops)} variables={{}}>
  328. {
  329. ({loading, error, data}) => {
  330. if (loading) {
  331. return (
  332. <div className="loading-center">
  333. <ActivityIndicator text="Loading..." size="large"/>
  334. </div>
  335. )
  336. }
  337. if (error) {
  338. return 'error!'
  339. }
  340. let products = data.productbyprops
  341. return (
  342. <div>
  343. <div className='all-goods'>
  344. {
  345. products.map(product => {
  346. return (
  347. <Row className='good-block' key={product.id}>
  348. <Col span={6}>
  349. <div className='good-image'
  350. style={{backgroundImage: `url(${product.img})`}}/>
  351. </Col>
  352. <Col span={11} offset={1}>{product.name}</Col>
  353. <Col span={5} style={{display: 'flex', justifyContent: 'space-around'}}>
  354. <Mutation mutation={gql(update_product)} refetchQueries={[
  355. {query: gql(productbyprops), variables: {}},
  356. {query: gql(productbyprops), variables: {status: '1', recommend: 1}}
  357. ]}>
  358. {(updateproduct, {loading, error}) => {
  359. if (loading)
  360. return (
  361. <div className="loading">
  362. <div className="align">
  363. <ActivityIndicator text="Loading..." size="large"/>
  364. </div>
  365. </div>
  366. )
  367. if (error)
  368. return 'error'
  369. let {id, recommend} = product
  370. let variables = {
  371. id,
  372. recommend: Number(!recommend),
  373. updatedAt: moment().format('YYYY-MM-DD HH:mm:ss')
  374. }
  375. return (
  376. <Icon type="like" className={classNames('not-like', {'like': recommend===1})} onClick={() => {
  377. updateproduct({variables})
  378. }}/>
  379. )
  380. }}
  381. </Mutation>
  382. <Icon type="form" onClick={() => {
  383. this.setState({modal: true, product})
  384. }}/>
  385. </Col>
  386. </Row>
  387. )
  388. })
  389. }
  390. </div>
  391. <Modal
  392. popup
  393. visible={modal}
  394. onClose={this.controlModal(false)}
  395. animationType="slide-up"
  396. className='modify-goods-modal'
  397. >
  398. <div className='close-popup' onClick={this.controlModal(false)}>X</div>
  399. <div style={{paddingTop: 52}}>
  400. <AddGoods good={product}/>
  401. </div>
  402. </Modal>
  403. </div>
  404. )
  405. }
  406. }
  407. </Query>
  408. )
  409. }
  410. }
  411. class AllCategory extends Component {
  412. constructor(props) {
  413. super(props)
  414. this.state = {}
  415. }
  416. render() {
  417. return (
  418. <div>
  419. <Query query={gql(category_by_props)} variables={categoryFilter}>
  420. {
  421. ({loading, error, data}) => {
  422. if (loading) {
  423. return (
  424. <div className="loading-center">
  425. <ActivityIndicator text="Loading..." size="large"/>
  426. </div>
  427. )
  428. }
  429. if (error) {
  430. return 'error!'
  431. }
  432. let categoryList = data.categorybyprops
  433. return (
  434. <div>
  435. <List>
  436. {
  437. categoryList.map(category => {
  438. return (
  439. <Item key={category.id} extra={
  440. <div className='list-extra'>
  441. <AllCategorySwitch category={category}/>
  442. <AllCategoryButton category={category}/>
  443. </div>
  444. }>
  445. {category.text}
  446. </Item>
  447. )
  448. })
  449. }
  450. </List>
  451. <AllCategoryInput order={categoryList.length + 1}/>
  452. </div>
  453. )
  454. }
  455. }
  456. </Query>
  457. </div>
  458. )
  459. }
  460. }
  461. class AllCategorySwitch extends Component {
  462. constructor(props) {
  463. super(props)
  464. this.state = {
  465. checked: props.category.status === '1'
  466. }
  467. }
  468. render() {
  469. let {category} = this.props
  470. let {checked} = this.state
  471. return (
  472. <Mutation mutation={gql(update_category)} refetchQueries={[
  473. {query: gql(category_by_props), variables: categoryFilter},
  474. {query: gql(category_by_props), variables: categoryFilterRefetch}
  475. ]}>
  476. {(updatecategory, {loading, error}) => {
  477. if (loading)
  478. return (
  479. <div className="loading">
  480. <div className="align">
  481. <ActivityIndicator text="Loading..." size="large"/>
  482. </div>
  483. </div>
  484. )
  485. if (error)
  486. return 'error'
  487. let obj = {
  488. id: category.id,
  489. updatedAt: moment().format('YYYY-MM-DD HH:mm:ss')
  490. }
  491. return (
  492. <Switch checked={checked} onChange={(bool) => {
  493. this.setState({checked: bool})
  494. updatecategory({variables: {...obj, status: bool ? '1' : '0'}})
  495. }}/>
  496. )
  497. }}
  498. </Mutation>
  499. )
  500. }
  501. }
  502. class AllCategoryButton extends Component {
  503. constructor(props) {
  504. super(props)
  505. this.state = {}
  506. }
  507. render() {
  508. let {category} = this.props
  509. return (
  510. <Mutation mutation={gql(delete_category)} refetchQueries={[
  511. {query: gql(category_by_props), variables: categoryFilter},
  512. {query: gql(category_by_props), variables: categoryFilterRefetch}
  513. ]}>
  514. {(deletecategory, {loading, error}) => {
  515. if (loading)
  516. return (
  517. <div className="loading">
  518. <div className="align">
  519. <ActivityIndicator text="Loading..." size="large"/>
  520. </div>
  521. </div>
  522. )
  523. if (error)
  524. return 'error'
  525. return (
  526. <Button type='warning' inline size="small" onClick={() => {
  527. deletecategory({variables: {id: category.id}})
  528. }}>删除</Button>
  529. )
  530. }}
  531. </Mutation>
  532. )
  533. }
  534. }
  535. class AllCategoryInput extends Component {
  536. constructor(props) {
  537. super(props)
  538. this.state = {
  539. newCategory: ''
  540. }
  541. }
  542. render() {
  543. let {order} = this.props
  544. let {newCategory} = this.state
  545. return (
  546. <Mutation mutation={gql(create_category)} refetchQueries={[
  547. {query: gql(category_by_props), variables: categoryFilter},
  548. {query: gql(category_by_props), variables: categoryFilterRefetch}
  549. ]}>
  550. {(createcategory, {loading, error}) => {
  551. if (loading)
  552. return (
  553. <div className="loading">
  554. <div className="align">
  555. <ActivityIndicator text="Loading..." size="large"/>
  556. </div>
  557. </div>
  558. )
  559. if (error)
  560. return 'error'
  561. let obj = {
  562. id: idGen('category'),
  563. name: newCategory,
  564. img: '',
  565. order,
  566. status: '1',
  567. createdAt: moment().format('YYYY-MM-DD HH:mm:ss'),
  568. updatedAt: ''
  569. }
  570. return (
  571. <InputItem
  572. onChange={(e) => {
  573. this.setState({newCategory: e})
  574. }}
  575. value={newCategory}
  576. placeholder="请输入分类名称"
  577. extra={
  578. <Button type='primary' inline size="small"
  579. onClick={() => {
  580. createcategory({variables: obj})
  581. }}>添加</Button>
  582. }>
  583. 新的分类
  584. </InputItem>
  585. )
  586. }}
  587. </Mutation>
  588. )
  589. }
  590. }
  591. export default withRouter(Goods)