diff --git a/admin-web/config/router.config.js b/admin-web/config/router.config.js index 476db0680..027f60080 100644 --- a/admin-web/config/router.config.js +++ b/admin-web/config/router.config.js @@ -152,7 +152,7 @@ export default [ path: '/promotion/full-privilege-list', name: 'full-privilege-list', component: './Promotion/FullPrivilegeList', - } + }, ], }, // pay @@ -173,6 +173,24 @@ export default [ }, ], }, + // sms + { + path: '/sms', + name: 'sms', + icon: 'user', + routes: [ + { + path: '/sms/sign-list', + name: 'sign-list', + component: './Sms/SignList', + }, + { + path: '/sms/template-list', + name: 'template-list', + component: './Sms/TemplateList', + }, + ], + }, { path: '/dashboard', name: 'dashboard', diff --git a/admin-web/src/components/Dictionary/DictionarySelect.d.ts b/admin-web/src/components/Dictionary/DictionarySelect.d.ts index b3df84c75..aed5d9f31 100644 --- a/admin-web/src/components/Dictionary/DictionarySelect.d.ts +++ b/admin-web/src/components/Dictionary/DictionarySelect.d.ts @@ -1,9 +1,10 @@ -import * as React from 'react'; import { Select } from 'antd'; +import * as React from 'react'; export interface IDictionarySelectProps extends Select { dicKey?: string; defaultValue?: string | number | boolean; } +// eslint-disable-next-line react/prefer-stateless-function export default class DictionarySelectD extends React.Component {} diff --git a/admin-web/src/components/Dictionary/DictionarySelect.js b/admin-web/src/components/Dictionary/DictionarySelect.js index 629737c2e..358fbf525 100644 --- a/admin-web/src/components/Dictionary/DictionarySelect.js +++ b/admin-web/src/components/Dictionary/DictionarySelect.js @@ -4,7 +4,13 @@ import DictionaryContext from './DictionaryContext'; export default class DictionarySelect extends PureComponent { renderSelect(children) { - return ; + // eslint-disable-next-line react/destructuring-assignment + // const { initialValue } = this.props['data-__meta']; + const propsX = this.props; + if (propsX.defaultValue === 'undefined' || propsX.defaultValue === 'null') { + propsX.defaultValue = undefined; + } + return ; } render() { diff --git a/admin-web/src/components/Dictionary/DictionaryText.d.ts b/admin-web/src/components/Dictionary/DictionaryText.d.ts index a107dd9a9..4c2b4eaa2 100644 --- a/admin-web/src/components/Dictionary/DictionaryText.d.ts +++ b/admin-web/src/components/Dictionary/DictionaryText.d.ts @@ -1,9 +1,10 @@ -import * as React from 'react'; import { Select } from 'antd'; +import * as React from 'react'; export interface IDictionaryTextProps extends Select { dicKey?: string; dicValue?: string | number | boolean | Array; } +// eslint-disable-next-line react/prefer-stateless-function export default class DictionaryText extends React.Component {} diff --git a/admin-web/src/locales/zh-CN/menu.js b/admin-web/src/locales/zh-CN/menu.js index 0c549bcdd..6c8c93050 100644 --- a/admin-web/src/locales/zh-CN/menu.js +++ b/admin-web/src/locales/zh-CN/menu.js @@ -55,14 +55,21 @@ export default { 'menu.order.order-list': '订单管理', 'menu.order.order-refunds': '退货维权', // 营销相关 + 'menu.promotion': '首页管理', 'menu.promotion.promotion-banner-list': '首页广告', 'menu.promotion.product-recommend-list': '商品推荐', 'menu.promotion.coupon-card-template-list': '优惠劵管理', 'menu.promotion.time-limit-discount-list': '限时折扣', 'menu.promotion.full-privilege-list': '满减送', // 会员相关 + 'menu.user': '会员管理', 'menu.user.user-list': '会员资料', // 支付相关 + 'menu.pay': '支付管理', 'menu.pay.pay-transaction-list': '支付单', 'menu.pay.pay-refund-list': '退款单', + // 短信服务 + 'menu.sms': '短信服务', + 'menu.sms.sign-list': '签名模板', + 'menu.sms.template-list': '短信模板', }; diff --git a/admin-web/src/models/sms/smsSignList.js b/admin-web/src/models/sms/smsSignList.js new file mode 100644 index 000000000..f50a8fdd5 --- /dev/null +++ b/admin-web/src/models/sms/smsSignList.js @@ -0,0 +1,56 @@ +import { pageSign, addSign, updateSign, deletedSign } from '../../services/sms'; + +export default { + namespace: 'smsSignList', + + state: { + list: [], + }, + + effects: { + *page({ payload }, { call, put }) { + const response = yield call(pageSign, payload); + if (response.code === 0) { + yield put({ + type: 'pageSuccess', + payload: response.data, + }); + } + }, + *add({ payload }, { call }) { + const { params, callback } = payload; + const response = yield call(addSign, params); + if (response.code === 0) { + if (callback) { + callback(response); + } + } + }, + *update({ payload }, { call }) { + const { params, callback } = payload; + const response = yield call(updateSign, params); + if (response.code === 0) { + if (callback) { + callback(response); + } + } + }, + *deleted({ payload }, { call }) { + const { params, callback } = payload; + const response = yield call(deletedSign, params); + if (callback) { + callback(response); + } + }, + }, + + reducers: { + pageSuccess(state, { payload }) { + const { data } = payload; + return { + ...state, + list: data, + }; + }, + }, +}; diff --git a/admin-web/src/pages/Sms/SignList.js b/admin-web/src/pages/Sms/SignList.js new file mode 100644 index 000000000..cc14c9ea3 --- /dev/null +++ b/admin-web/src/pages/Sms/SignList.js @@ -0,0 +1,265 @@ +import React, { PureComponent } from 'react'; +import { Card, Divider, Table, Modal, Button } from 'antd'; +import { connect } from 'dva'; + +import PageHeaderWrapper from '@/components/PageHeaderWrapper'; +import DictionaryText from '../../components/Dictionary/DictionaryText'; +import dictionary from '../../utils/dictionary'; +import styles from '../List/TableList.less'; +import SignListSearch from './SignListSearch'; +import SignListUpdate from './SignListUpdate'; + +@connect(({ smsSignList, loading }) => ({ + smsSignList, + loading: loading.models.smsSignList, +})) +class SignList extends PureComponent { + state = { + visible: false, + title: '添加签名', // 添加签名 修改签名 + type: 'add', // add update + id: '', + sign: {}, + }; + + componentDidMount() { + // init page 参数 + this.current = 1; + this.total = 0; + this.size = 20; + this.searchParams = {}; + + // 查询 page + this.queryPage(); + } + + queryPage = () => { + const { dispatch } = this.props; + const { current, total, size, searchParams } = this; + + dispatch({ + type: 'smsSignList/page', + payload: { + current, + total, + size, + ...searchParams, + }, + }); + }; + + handleSearch = searchParams => { + this.searchParams = searchParams; + this.queryPage(); + }; + + handleAddShow = () => { + this.setState({ + visible: true, + type: 'add', + title: '添加签名', + }); + }; + + handleUpdateShow = sign => { + const { id } = sign; + this.setState({ + visible: true, + type: 'update', + title: '更新签名', + id, + sign, + }); + }; + + handleDeleted = ({ id, sign }) => { + const { dispatch } = this.props; + Modal.confirm({ + title: `提示消息`, + content: `确认删除 ${sign} 签名吗`, + okText: '确认', + cancelText: '取消', + onOk: () => { + console.log('OK'); + dispatch({ + type: 'smsSignList/deleted', + payload: { + params: { + id, + }, + callback: () => { + this.queryPage(); + }, + }, + }); + }, + onCancel() { + console.log('Cancel'); + }, + }); + }; + + handleCancel = () => { + this.setState({ + visible: false, + }); + }; + + handleOk = fields => { + const { dispatch } = this.props; + const { type, id } = this.state; + + if (type === 'add') { + dispatch({ + type: 'smsSignList/add', + payload: { + params: { + ...fields, + }, + callback: () => { + this.handleCancel(); + this.queryPage(); + }, + }, + }); + } else if (type === 'update') { + dispatch({ + type: 'smsSignList/update', + payload: { + params: { + id, + ...fields, + }, + callback: () => { + this.handleCancel(); + this.queryPage(); + }, + }, + }); + } + }; + + handleTableChange = pagination => { + const { pageSize, current } = pagination; + this.size = pageSize; + this.current = current; + this.queryPage(); + }; + + render() { + // props + const { loading, smsSignList } = this.props; + const { list, total, index, size } = smsSignList; + const { visible, title, type, sign } = this.state; + + const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + }, + { + title: '签名', + dataIndex: 'sign', + key: 'sign', + }, + { + title: '短信平台', + dataIndex: 'platform', + key: 'platform', + render(platform) { + return ( +
+ +
+ ); + }, + }, + { + title: '审核状态', + dataIndex: 'applyStatus', + key: 'applyStatus', + render(applyStatus) { + return ( +
+ +
+ ); + }, + }, + { + title: '更新时间', + dataIndex: 'updateTime', + key: 'updateTime', + width: 200, + render(updateTime) { + return
{updateTime}
; + }, + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + width: 200, + render(createTime) { + return
{createTime}
; + }, + }, + { + title: '操作', + render: row => { + return ( +
+ 修改 + + 删除 +
+ ); + }, + }, + ]; + + const pagination = { + total, + index, + pageSize: size, + }; + + return ( + + +
+ +
+
+
+ +
+
+ + + + + + + ); + } +} + +export default SignList; diff --git a/admin-web/src/pages/Sms/SignList.less b/admin-web/src/pages/Sms/SignList.less new file mode 100644 index 000000000..afe16ef56 --- /dev/null +++ b/admin-web/src/pages/Sms/SignList.less @@ -0,0 +1,313 @@ +@import '~antd/lib/style/themes/default.less'; +@import '~@/utils/utils.less'; + +.standardList { + :global { + .ant-card-head { + border-bottom: none; + } + .ant-card-head-title { + padding: 24px 0; + line-height: 32px; + } + .ant-card-extra { + padding: 24px 0; + } + .ant-list-pagination { + margin-top: 24px; + text-align: right; + } + .ant-avatar-lg { + width: 48px; + height: 48px; + line-height: 48px; + } + } + .headerInfo { + position: relative; + text-align: center; + & > span { + display: inline-block; + margin-bottom: 4px; + color: @text-color-secondary; + font-size: @font-size-base; + line-height: 22px; + } + & > p { + margin: 0; + color: @heading-color; + font-size: 24px; + line-height: 32px; + } + & > em { + position: absolute; + top: 0; + right: 0; + width: 1px; + height: 56px; + background-color: @border-color-split; + } + } + .listContent { + display: flex; + flex: 1; + flex-direction: row; + font-size: 0; + + .listContentItem { + flex: 1; + margin-left: 40px; + color: @text-color-secondary; + font-size: @font-size-base; + vertical-align: middle; + > span { + line-height: 20px; + } + > p { + margin-top: 4px; + margin-bottom: 0; + line-height: 22px; + } + } + } + .extraContentSearch { + width: 272px; + margin-left: 16px; + } +} + +@media screen and (max-width: @screen-xs) { + .standardList { + :global { + .ant-list-item-content { + display: block; + flex: none; + width: 100%; + } + .ant-list-item-action { + margin-left: 0; + } + } + .listContent { + margin-left: 0; + & > div { + margin-left: 0; + } + } + .listCard { + :global { + .ant-card-head-title { + overflow: visible; + } + } + } + } +} + +@media screen and (max-width: @screen-sm) { + .standardList { + .extraContentSearch { + width: 100%; + margin-left: 0; + } + .headerInfo { + margin-bottom: 16px; + & > em { + display: none; + } + } + } +} + +@media screen and (max-width: @screen-md) { + .standardList { + .listContent { + & > div { + display: block; + } + & > div:last-child { + top: 0; + width: 100%; + } + } + } + .listCard { + :global { + .ant-radio-group { + display: block; + margin-bottom: 8px; + } + } + } +} + +@media screen and (max-width: @screen-lg) and (min-width: @screen-md) { + .standardList { + .listContent { + & > div { + display: block; + } + & > div:last-child { + top: 0; + width: 100%; + } + } + } +} + +@media screen and (max-width: @screen-xl) { + .standardList { + .listContent { + & > div { + margin-left: 24px; + } + & > div:last-child { + top: 0; + } + } + } +} + +@media screen and (max-width: 1400px) { + .standardList { + .listContent { + text-align: right; + & > div:last-child { + top: 0; + } + } + } +} + +.standardListForm { + :global { + .ant-form-item { + margin-bottom: 12px; + &:last-child { + margin-bottom: 32px; + padding-top: 4px; + } + } + } +} + +.formResult { + width: 100%; + [class^='title'] { + margin-bottom: 8px; + } +} + +.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; + } +} + +@media screen and (max-width: @screen-md) { + .tableListForm :global(.ant-form-item) { + margin-right: 8px; + } +} + +// 订单content +.orderGroup { + @padding-slid: 10px; + @solid-color: rgba(167, 157, 160, 0.92); + @header-background: rgba(210, 219, 238, 0.99); + + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + + .header { + display: flex; + flex: 1; + justify-content: space-between; + padding-right: @padding-slid; + padding-left: @padding-slid; + font-weight: bold; + font-size: 15px; + line-height: 35px; + background-color: @header-background; + } + + .goodsContainer { + :first-child { + border-top: none; + border-bottom: none; + } + + :last-child { + border-bottom: none; + } + } + + .orderGoods { + display: flex; + flex: 2; + flex-direction: row; + width: 500px; + border: 1px solid @solid-color; + } + + .order { + display: flex; + flex: 1; + flex-direction: row; + padding-right: @padding-slid; + padding-left: @padding-slid; + line-height: 100px; + border: 1px solid @solid-color; + + .contentItem { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + + > div { + line-height: 30px; + } + + .columnName { + font-weight: bold; + font-size: 12px; + } + } + + .image { + width: 80px; + height: 80px; + } + } +} diff --git a/admin-web/src/pages/Sms/SignListSearch.js b/admin-web/src/pages/Sms/SignListSearch.js new file mode 100644 index 000000000..cbbc4b805 --- /dev/null +++ b/admin-web/src/pages/Sms/SignListSearch.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { Button, Col, Form, Input, Row } from 'antd'; + +const FormItem = Form.Item; + +/** + * table 查询 + * + * @type {React.ComponentClass>} + */ +const SignListSearch = Form.create()(props => { + const { handleSearch } = props; + const { getFieldDecorator, validateFields, form } = props.form; + + function onSubmit(e) { + e.preventDefault(); + + validateFields((err, fields) => { + const searchParams = fields; + if (handleSearch) { + handleSearch(searchParams); + } + }); + } + + function handleFormReset() { + form.resetFields(); + } + + return ( +
+ +
+ + {getFieldDecorator('id')()} + + + + + {getFieldDecorator('sign')()} + + + + + + + + + + + + + + ); +}); + +export default SignListSearch; diff --git a/admin-web/src/pages/Sms/SignListUpdate.js b/admin-web/src/pages/Sms/SignListUpdate.js new file mode 100644 index 000000000..790ba0279 --- /dev/null +++ b/admin-web/src/pages/Sms/SignListUpdate.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { Form, Input, Modal } from 'antd'; +import DictionarySelect from '../../components/Dictionary/DictionarySelect'; +import dictionary from '../../utils/dictionary'; + +/** + * table 查询 + * + * @type {React.ComponentClass>} + */ +const SignListUpdate = Form.create()(props => { + const { onOk, onCancel, visible, title, form, initData = {} } = props; + const { getFieldDecorator, validateFields } = props.form; + + function handleOk(e) { + e.preventDefault(); + + validateFields((err, fields) => { + const searchParams = fields; + if (onOk) { + onOk(searchParams); + form.resetFields(); + } + }); + } + + function handleCancel() { + if (onCancel) { + onCancel(); + } + } + + const formItemLayout = { + labelCol: { span: 4 }, + wrapperCol: { span: 18 }, + }; + + return ( + +
+ + {getFieldDecorator('sign', { + rules: [ + { + required: true, + message: '请输入签名', + }, + ], + initialValue: initData.sign, + })()} + + + {getFieldDecorator('platform', { + rules: [ + { + required: true, + message: '请选择平台', + }, + ], + initialValue: `${initData.platform}`, + })()} + + +
+ ); +}); + +export default SignListUpdate; diff --git a/admin-web/src/pages/Sms/TemplateList.js b/admin-web/src/pages/Sms/TemplateList.js new file mode 100644 index 000000000..c5d30633a --- /dev/null +++ b/admin-web/src/pages/Sms/TemplateList.js @@ -0,0 +1,11 @@ +import React, { PureComponent } from 'react'; + +import PageHeaderWrapper from '@/components/PageHeaderWrapper'; + +class TemplateList extends PureComponent { + render() { + return template-list; + } +} + +export default TemplateList; diff --git a/admin-web/src/pages/Sms/TemplateList.less b/admin-web/src/pages/Sms/TemplateList.less new file mode 100644 index 000000000..afe16ef56 --- /dev/null +++ b/admin-web/src/pages/Sms/TemplateList.less @@ -0,0 +1,313 @@ +@import '~antd/lib/style/themes/default.less'; +@import '~@/utils/utils.less'; + +.standardList { + :global { + .ant-card-head { + border-bottom: none; + } + .ant-card-head-title { + padding: 24px 0; + line-height: 32px; + } + .ant-card-extra { + padding: 24px 0; + } + .ant-list-pagination { + margin-top: 24px; + text-align: right; + } + .ant-avatar-lg { + width: 48px; + height: 48px; + line-height: 48px; + } + } + .headerInfo { + position: relative; + text-align: center; + & > span { + display: inline-block; + margin-bottom: 4px; + color: @text-color-secondary; + font-size: @font-size-base; + line-height: 22px; + } + & > p { + margin: 0; + color: @heading-color; + font-size: 24px; + line-height: 32px; + } + & > em { + position: absolute; + top: 0; + right: 0; + width: 1px; + height: 56px; + background-color: @border-color-split; + } + } + .listContent { + display: flex; + flex: 1; + flex-direction: row; + font-size: 0; + + .listContentItem { + flex: 1; + margin-left: 40px; + color: @text-color-secondary; + font-size: @font-size-base; + vertical-align: middle; + > span { + line-height: 20px; + } + > p { + margin-top: 4px; + margin-bottom: 0; + line-height: 22px; + } + } + } + .extraContentSearch { + width: 272px; + margin-left: 16px; + } +} + +@media screen and (max-width: @screen-xs) { + .standardList { + :global { + .ant-list-item-content { + display: block; + flex: none; + width: 100%; + } + .ant-list-item-action { + margin-left: 0; + } + } + .listContent { + margin-left: 0; + & > div { + margin-left: 0; + } + } + .listCard { + :global { + .ant-card-head-title { + overflow: visible; + } + } + } + } +} + +@media screen and (max-width: @screen-sm) { + .standardList { + .extraContentSearch { + width: 100%; + margin-left: 0; + } + .headerInfo { + margin-bottom: 16px; + & > em { + display: none; + } + } + } +} + +@media screen and (max-width: @screen-md) { + .standardList { + .listContent { + & > div { + display: block; + } + & > div:last-child { + top: 0; + width: 100%; + } + } + } + .listCard { + :global { + .ant-radio-group { + display: block; + margin-bottom: 8px; + } + } + } +} + +@media screen and (max-width: @screen-lg) and (min-width: @screen-md) { + .standardList { + .listContent { + & > div { + display: block; + } + & > div:last-child { + top: 0; + width: 100%; + } + } + } +} + +@media screen and (max-width: @screen-xl) { + .standardList { + .listContent { + & > div { + margin-left: 24px; + } + & > div:last-child { + top: 0; + } + } + } +} + +@media screen and (max-width: 1400px) { + .standardList { + .listContent { + text-align: right; + & > div:last-child { + top: 0; + } + } + } +} + +.standardListForm { + :global { + .ant-form-item { + margin-bottom: 12px; + &:last-child { + margin-bottom: 32px; + padding-top: 4px; + } + } + } +} + +.formResult { + width: 100%; + [class^='title'] { + margin-bottom: 8px; + } +} + +.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; + } +} + +@media screen and (max-width: @screen-md) { + .tableListForm :global(.ant-form-item) { + margin-right: 8px; + } +} + +// 订单content +.orderGroup { + @padding-slid: 10px; + @solid-color: rgba(167, 157, 160, 0.92); + @header-background: rgba(210, 219, 238, 0.99); + + display: flex; + flex: 1; + flex-direction: column; + justify-content: flex-start; + + .header { + display: flex; + flex: 1; + justify-content: space-between; + padding-right: @padding-slid; + padding-left: @padding-slid; + font-weight: bold; + font-size: 15px; + line-height: 35px; + background-color: @header-background; + } + + .goodsContainer { + :first-child { + border-top: none; + border-bottom: none; + } + + :last-child { + border-bottom: none; + } + } + + .orderGoods { + display: flex; + flex: 2; + flex-direction: row; + width: 500px; + border: 1px solid @solid-color; + } + + .order { + display: flex; + flex: 1; + flex-direction: row; + padding-right: @padding-slid; + padding-left: @padding-slid; + line-height: 100px; + border: 1px solid @solid-color; + + .contentItem { + display: flex; + flex: 1; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + + > div { + line-height: 30px; + } + + .columnName { + font-weight: bold; + font-size: 12px; + } + } + + .image { + width: 80px; + height: 80px; + } + } +} diff --git a/admin-web/src/services/sms.js b/admin-web/src/services/sms.js new file mode 100644 index 000000000..2d71721ef --- /dev/null +++ b/admin-web/src/services/sms.js @@ -0,0 +1,28 @@ +import { stringify } from '@/utils/request.qs'; +import request from '@/utils/request'; + +// sign + +export async function pageSign(params) { + return request(`/admin-api/admins/sms/sign/page?${stringify(params)}`, { + method: 'GET', + }); +} + +export async function addSign(params) { + return request(`/admin-api/admins/sms/sign/add?${stringify(params)}`, { + method: 'POST', + }); +} + +export async function updateSign(params) { + return request(`/admin-api/admins/sms/sign/update?${stringify(params)}`, { + method: 'PUT', + }); +} + +export async function deletedSign(params) { + return request(`/admin-api/admins/sms/sign/deleted?${stringify(params)}`, { + method: 'DELETE', + }); +} diff --git a/admin-web/src/utils/dictionary.js b/admin-web/src/utils/dictionary.js index 77a607fc4..e642abe75 100644 --- a/admin-web/src/utils/dictionary.js +++ b/admin-web/src/utils/dictionary.js @@ -10,6 +10,10 @@ const DictionaryConstants = { ORDER_RETURN_STATUS: 'order_return_status', ORDER_RETURN_REASON: 'order_return_reason', ORDER_RETURN_SERVICE_TYPE: 'order_return_service_type', + + // sms + SMS_PLATFORM: 'sms_platform', + SMS_APPLY_STATUS: 'sms_apply_status', }; export default DictionaryConstants;