modal.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <template>
  2. <el-drawer :title="title" :visible.sync="visible" direction="rtl" size="85%" :wrapperClosable="false"
  3. :destroy-on-close="true">
  4. <div class="main-layout">
  5. <CompPreview :form-data="formModel" />
  6. <div class="activity-container">
  7. <el-steps :active="currentStep" simple>
  8. <el-step title="基础设置" icon="el-icon-edit"></el-step>
  9. <el-step title="派奖方式" icon="el-icon-setting"></el-step>
  10. <el-step title="奖励设置" icon="el-icon-present"></el-step>
  11. <el-step title="高级设置" icon="el-icon-s-operation"></el-step>
  12. </el-steps>
  13. <div class="form-content-area">
  14. <el-form ref="form" :model="formModel" :rules="rules" :disabled="title === '详情'" label-width="120px" label-suffix=":" size="small">
  15. <div v-if="currentStep === 0">
  16. <el-form-item label="活动标题" prop="title">
  17. <el-input v-model="formModel.title" placeholder="请输入活动标题" maxlength="20"
  18. show-word-limit></el-input>
  19. </el-form-item>
  20. <el-form-item label="活动时间" prop="timeRange">
  21. <el-date-picker ref="timeRange"
  22. type="datetimerange" value-format="yyyy-MM-dd HH:mm:ss"
  23. range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
  24. :picker-options="pickerOptions" v-model="formModel.timeRange"
  25. ></el-date-picker>
  26. </el-form-item>
  27. <el-form-item label="关联商户" prop="merchantId">
  28. <el-select v-model="formModel.merchantId" placeholder="请选择商户" filterable @change="onChange">
  29. <el-option v-for="item in dicts.merchantId" :key="item.val" :label="item.name" :value="item.val"></el-option>
  30. </el-select>
  31. </el-form-item>
  32. <el-form-item label="活动负责人" prop="leaderUserId">
  33. <el-select v-model="formModel.leaderUserId" placeholder="请选择活动负责人" filterable>
  34. <el-option label="测试商户A" value="1"></el-option>
  35. <el-option label="测试商户B" value="2"></el-option>
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item label="关联企微群" prop="wecomGroupId">
  39. <el-select v-model="formModel.wecomGroupId" placeholder="请选择关联企微群" filterable>
  40. <el-option v-for="item in dicts.wecomGroupId" :key="item.val" :label="item.name" :value="item.val"></el-option>
  41. </el-select>
  42. </el-form-item>
  43. <el-form-item label="关联员工企微" prop="wecomEmployeeId">
  44. <el-select v-model="formModel.wecomEmployeeId" placeholder="请选择关联员工企微" filterable>
  45. <el-option v-for="item in dicts.wecomEmployeeId" :key="item.val" :label="item.name" :value="item.val"></el-option>
  46. </el-select>
  47. </el-form-item>
  48. <el-form-item label="新客户判定" prop="newUserRule">
  49. <el-radio-group v-model="formModel.newUserRule">
  50. <el-radio v-for="item in dicts.newUserRule" :key="item.dictValue" :label="item.dictValue">{{ item.dictLabel }}</el-radio>
  51. </el-radio-group>
  52. </el-form-item>
  53. <el-form-item label="活动说明" prop="activityInstructions">
  54. <el-input type="textarea" v-model="formModel.activityInstructions" rows="4"></el-input>
  55. </el-form-item>
  56. </div>
  57. <div v-if="currentStep === 1">
  58. <el-form-item label="触发条件" prop="invitationCount">
  59. <el-input-number v-model="formModel.invitationCount" :min="1" :max="200" placeholder="请填写邀请人数"></el-input-number>
  60. <span style="margin-left: 10px; font-size: 12px; color:#999;">注:≥1,≤200(邀请新客户进群人数)</span>
  61. </el-form-item>
  62. <el-form-item label="派奖方式" prop="payoutMethod">
  63. <el-radio-group v-model="formModel.payoutMethod">
  64. <el-radio v-for="item in dicts.payoutMethod" :key="item.dictValue" :label="item.dictValue">{{ item.dictLabel }}</el-radio>
  65. </el-radio-group>
  66. </el-form-item>
  67. <el-form-item label="发放时机" prop="timingDistribution">
  68. <el-radio-group v-model="formModel.timingDistribution">
  69. <el-radio v-for="item in dicts.timingDistribution" :key="item.dictValue" :label="item.dictValue">{{ item.dictLabel }}</el-radio>
  70. </el-radio-group>
  71. </el-form-item>
  72. </div>
  73. <div v-if="currentStep === 2">
  74. <el-divider content-position="left">签到奖励</el-divider>
  75. <el-form-item label-width="0" prop="inviteTiers">
  76. <CompSign v-model="formModel.inviteTiers" @removed="handleTierChange" />
  77. </el-form-item>
  78. <el-divider content-position="left" class="divider">额外奖励</el-divider>
  79. <el-form-item label-width="0" prop="inviteExtras">
  80. <CompExtras :options="tierOptions" v-model="formModel.inviteExtras" />
  81. </el-form-item>
  82. </div>
  83. <div v-if="currentStep === 3">
  84. <el-form-item label="助力上限" prop="dailyAssistanceLimit">
  85. <el-input-number v-model="formModel.dailyAssistanceLimit" :min="1" :max="200"></el-input-number>
  86. <span> 人 / 单日</span>
  87. </el-form-item>
  88. <el-form-item label="最多助力数" prop="customAssistanceLimit">
  89. <el-input-number v-model="formModel.customAssistanceLimit" :min="1" :max="200"></el-input-number>
  90. <span> 人 / 单客户</span>
  91. </el-form-item>
  92. <el-form-item label="进群欢迎语" prop="welcomeMessage">
  93. <el-input v-model="formModel.welcomeMessage" type="textarea" autosize placeholder="请输入欢迎语"></el-input>
  94. </el-form-item>
  95. </div>
  96. </el-form>
  97. </div>
  98. </div>
  99. </div>
  100. <div class="drawer-footer">
  101. <el-button v-if="currentStep > 0" @click="handlePrev">上一步</el-button>
  102. <el-button v-if="currentStep < 3" type="primary" @click="handleNext">下一步</el-button>
  103. <el-button v-if="currentStep === 3" type="primary"
  104. :loading="btnLoading"
  105. @click="handleSubmit"
  106. >完成并保存</el-button>
  107. </div>
  108. </el-drawer>
  109. </template>
  110. <script>
  111. import {
  112. getDicts
  113. } from '@/api/system/menu';
  114. import {
  115. queryById,
  116. saveOrUpdate,
  117. getDictListByType,
  118. getWecomGroupList,
  119. getWecomEmployeeList,
  120. } from '@/api/activity/index';
  121. import moment from 'moment';
  122. import CompSign from './CompSign.vue';
  123. import CompExtras from './CompExtras.vue';
  124. import CompPreview from './CompPreview.vue';
  125. export default {
  126. name: '',
  127. components: {
  128. CompSign,
  129. CompExtras,
  130. CompPreview
  131. },
  132. data() {
  133. return {
  134. title: '活动方案配置',
  135. visible: false,
  136. btnLoading: false,
  137. currentStep: 0,
  138. isUpdate: false,
  139. formModel: {
  140. id: '',
  141. timeRange: [],
  142. newUserRule: '1',
  143. payoutMethod: '1',
  144. timingDistribution: '1',
  145. inviteTiers: [{ count: undefined, reward: undefined }],
  146. inviteExtras: [],
  147. },
  148. rules: {
  149. title: [
  150. { required: true, message: '请输入活动标题', trigger: 'blur' }
  151. ],
  152. timeRange: [
  153. { required: true, message: '请选择活动时间', trigger: 'change' }
  154. ],
  155. merchantId: [
  156. { required: true, message: '请选择商户', trigger: 'change' }
  157. ],
  158. wecomGroupId: [
  159. { required: true, message: '请选择关联企微群', trigger: 'change' }
  160. ],
  161. wecomEmployeeId: [
  162. { required: true, message: '请选择关联员工企微', trigger: 'change' }
  163. ],
  164. activityInstructions: [
  165. { required: true, message: '请输入活动说明', trigger: 'change' }
  166. ],
  167. dailyAssistanceLimit: [
  168. { required: true, message: '请输入单日助力上限', trigger: 'change' }
  169. ],
  170. customAssistanceLimit: [
  171. { required: true, message: '请输入同一客户最多助力数', trigger: 'change' }
  172. ],
  173. invitationCount: [
  174. { required: true, message: '请输入邀请人数', trigger: 'change' }
  175. ],
  176. // 签到奖励校验
  177. inviteTiers: [{
  178. required: true,
  179. // trigger: ['blur', 'change'],
  180. validator: (rule, value, callback) => {
  181. if (this.currentStep < 2) return callback();
  182. if (!value || value.length === 0) {
  183. return callback(new Error('请至少添加一个奖励阶梯'));
  184. }
  185. for (let i = 0; i < value.length; i++) {
  186. if (!value[i].count || value[i].reward === undefined) {
  187. return callback(new Error(`请完善第${i + 1}阶梯人数和奖励金额`));
  188. }
  189. }
  190. callback();
  191. }
  192. }]
  193. },
  194. dicts: {
  195. merchantId: [],
  196. wecomGroupId: [],
  197. wecomEmployeeId: [],
  198. newUserRule: [],
  199. payoutMethod: [],
  200. timingDistribution: [],
  201. }
  202. };
  203. },
  204. computed: {
  205. tierOptions() {
  206. let data = (this.formModel.inviteTiers || []).filter(item => item.count > 0).map((item, index) => ({
  207. label: `第${index + 1}阶梯 (邀请≥${item.count || 0}人)`,
  208. value: index
  209. }));
  210. return data;
  211. },
  212. pickerOptions() {
  213. return {
  214. onPick: ({ minDate }) => {
  215. const isCurrentDay = moment(minDate).isSame(moment(), 'day');
  216. if (isCurrentDay) {
  217. const picker = this.$refs.timeRange.picker;
  218. const { minTimePicker } = picker.$refs;
  219. let s = moment(`${moment().add(2, 'minutes').format('YYYY-MM-DD HH:mm:ss')}`).toDate();
  220. let e = moment(`${moment(minDate).format('YYYY-MM-DD')} 23:59:59`).toDate();
  221. minTimePicker.selectableRange = [[s, e]];
  222. }
  223. },
  224. disabledDate(time) {
  225. return time.getTime() < Date.now() - 8.64e7
  226. }
  227. }
  228. }
  229. },
  230. created() {
  231. this.initData();
  232. },
  233. methods: {
  234. initData() {
  235. // 新客户判定规则
  236. getDicts('new_customer_rule_type').then((res) => {
  237. this.dicts.newUserRule = res.data;
  238. });
  239. // 派奖方式
  240. getDicts('prize_distribution_method').then((res) => {
  241. this.dicts.payoutMethod = res.data;
  242. });
  243. // 发放时机
  244. getDicts('timing_distribution_groupFission_type').then((res) => {
  245. this.dicts.timingDistribution = res.data;
  246. });
  247. // 商户
  248. getDictListByType({ type: 1 }).then((res) => {
  249. this.dicts.merchantId = res.data;
  250. });
  251. // 企业微信群
  252. getWecomGroupList().then((res) => {
  253. this.dicts.wecomGroupId = res.data;
  254. });
  255. // 企业微信员工
  256. getWecomEmployeeList().then((res) => {
  257. this.dicts.wecomEmployeeId = res.data;
  258. });
  259. },
  260. add() {
  261. this.isUpdate = false;
  262. this.resetForm();
  263. this.visible = true;
  264. },
  265. edit(record) {
  266. this.isUpdate = true;
  267. this.resetForm();
  268. this.visible = true;
  269. // 加载详情
  270. queryById({ id: record.id }).then(res => {
  271. if (res.code === 200) {
  272. this.formModel = res.data;
  273. // 时间
  274. if (res.data.beginDate && res.data.endDate) {
  275. this.formModel.timeRange = [res.data.beginDate, res.data.endDate];
  276. }
  277. // 阶梯数组
  278. if (res.data.weGroupFissionStepList) {
  279. this.formModel.inviteTiers = res.data.weGroupFissionStepList.map((item) => ({
  280. reward: item.rewardAmount,
  281. count: item.invitationCount,
  282. }));
  283. }
  284. // 额外奖励数组
  285. if (res.data.weGroupFissionExtraList) {
  286. this.formModel.inviteExtras = res.data.weGroupFissionExtraList.map((item) => {
  287. let nd = {
  288. tierIndex: item.sortNum - 1,
  289. rewardType: item.rewardType,
  290. rewardValue: undefined
  291. };
  292. if (item.rewardType == 1) {
  293. nd.rewardValue = item.rewardAmount;
  294. } else if (item.rewardType == 2) {
  295. nd.rewardName = item.rightInfoName;
  296. nd.rewardValue = item.rightInfoId;
  297. } else if (item.rewardType == 3) {
  298. nd.rewardName = item.lotteryActivityName;
  299. nd.rewardValue = item.lotteryActivityId;
  300. }
  301. return nd;
  302. });
  303. }
  304. }
  305. });
  306. },
  307. resetForm() {
  308. this.currentStep = 0;
  309. // 初始数据
  310. const initialData = this.$options.data.call(this);
  311. this.formModel = initialData.formModel;
  312. this.$nextTick(() => {
  313. if (this.$refs.form) this.$refs.form.clearValidate();
  314. });
  315. },
  316. // 商户选择事件
  317. onChange(value) {
  318. const selectedOption = this.dicts.merchantId.find(item => item.val === value);
  319. if (selectedOption) {
  320. this.formModel.merchantName = selectedOption.name;
  321. }
  322. },
  323. handleTierChange(deletedIndex) {
  324. this.$nextTick(() => {
  325. this.$refs.form.validateField('inviteTiers');
  326. })
  327. let newExtras = this.formModel.inviteExtras.filter(item => item.tierIndex !== deletedIndex);
  328. newExtras = newExtras.map(item => {
  329. if (item.tierIndex > deletedIndex) {
  330. return { ...item, tierIndex: item.tierIndex - 1 };
  331. }
  332. return item;
  333. });
  334. this.formModel.inviteExtras = newExtras;
  335. },
  336. handleNext() {
  337. // 校验
  338. this.$refs.form.validate((valid, invalidFields) => {
  339. if (valid) {
  340. this.currentStep++;
  341. }
  342. });
  343. },
  344. handlePrev() {
  345. this.currentStep--;
  346. },
  347. handleSubmit() {
  348. this.$refs.form.validate(valid => {
  349. if (valid) {
  350. this.btnLoading = true;
  351. let { timeRange, inviteTiers, inviteExtras, ...others } = this.formModel;
  352. let params = { ...others };
  353. if (timeRange && timeRange.length === 2) {
  354. params.beginDate = timeRange[0];
  355. params.endDate = timeRange[1];
  356. }
  357. // 阶梯数组
  358. if (inviteTiers) {
  359. params.weGroupFissionStepList = inviteTiers.map((item, index) => ({
  360. sortNum: index + 1,
  361. rewardAmount: item.reward,
  362. invitationCount: item.count,
  363. }));
  364. }
  365. // 额外奖励数组
  366. if (inviteExtras) {
  367. params.weGroupFissionExtraList = inviteExtras.map((item) => {
  368. let nd = {
  369. sortNum: item.tierIndex + 1,
  370. rewardType: item.rewardType,
  371. };
  372. if (item.rewardType == 1) {
  373. nd.rewardAmount = item.rewardValue;
  374. } else if (item.rewardType == 2) {
  375. nd.rightInfoId = item.rewardValue;
  376. } else if (item.rewardType == 3) {
  377. nd.lotteryActivityId = item.rewardValue;
  378. }
  379. return nd;
  380. });
  381. }
  382. // console.log('form', params);
  383. saveOrUpdate(params).then(res => {
  384. if (res.code === 200) {
  385. this.$message.success('保存成功');
  386. this.visible = false;
  387. this.$emit('success');
  388. } else {
  389. this.$message.error(res.msg);
  390. }
  391. }).finally(() => {
  392. this.btnLoading = false;
  393. });
  394. }
  395. });
  396. }
  397. }
  398. };
  399. </script>
  400. <style lang="scss" scoped>
  401. .main-layout {
  402. padding: 0 20px;
  403. display: flex;
  404. gap: 20px;
  405. }
  406. .activity-container {
  407. flex: 1;
  408. .form-content-area {
  409. padding: 24px 24px 80px;
  410. ::v-deep input[aria-hidden="true"] {
  411. display: none !important;
  412. }
  413. .divider {
  414. display: inline-block;
  415. }
  416. }
  417. }
  418. .drawer-footer {
  419. width: calc(100% - 415px);
  420. padding: 10px 16px;
  421. text-align: right;
  422. border-top: 1px solid #e8e8e8;
  423. background: #fff;
  424. position: absolute;
  425. bottom: 0;
  426. right: 0;
  427. z-index: 99;
  428. }
  429. </style>