index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <view class="certification-main">
  3. <Step :step="step" />
  4. <view v-if="data.auditStatus === '30'" class="certification-error">
  5. <view class="appStatus-error-icon"></view>
  6. <view>{{data.auditRemark}}</view>
  7. </view>
  8. <view class="certification-scroll">
  9. <view>
  10. <view class="certification-content">
  11. <!-- 上传资质 -->
  12. <view class="flex_c_l certification-title"><text class="text-red" v-if="viewStatus">*</text>{{
  13. viewStatus ? '请选择' : '' }}发布服务类目:</view>
  14. <!-- 服务类目 -->
  15. <Category />
  16. <view class="flex_c_l certification-title"><text class="text-red" v-if="viewStatus">*</text>{{
  17. viewStatus ? '请填写' : '' }}服务信息:</view>
  18. <CategoryInfo />
  19. <CategoryImags />
  20. <!-- 规格信息 -->
  21. <Specifications :viewStatus="viewStatus" />
  22. </view>
  23. </view>
  24. </view>
  25. <view class="certification-footer flex_c_c">
  26. <view v-if="[0].includes(step)">
  27. <view class="btn-w certification-btn flex_c_c" @click="goNext">提交</view>
  28. </view>
  29. <view v-if="[1].includes(step)" class="flex_c_c flex-gap">
  30. <view class="certification-btn-bols flex_c_c btn-p2" @click="onDelete">删除</view>
  31. <view class="certification-btn flex_c_c btn-p" @click="onEdit">修改</view>
  32. </view>
  33. <view v-if="[2].includes(step)" class="flex_c_c flex-gap-30 step-more-main">
  34. <view class="more-btns" v-if="moreBtn">
  35. <view class="more-btn" @click="onDelete">删除</view>
  36. <view class="more-btn" @click="onAdd">新增</view>
  37. </view>
  38. <view class="more-box" @click="moreBtn = !moreBtn">
  39. <view :class="[moreBtn ? 'icon-down' : 'icon-up']"></view>
  40. 更多
  41. </view>
  42. <view class="certification-btn-bols flex_c_c btn-p2" @click="onServeStatus"
  43. v-if="data.auditStatus === '20'">
  44. {{ data.serviceStatus === '10' ? '下线' : '上线' }}
  45. </view>
  46. <view class="certification-btn flex_c_c btn-p" @click="onEdit" v-if="data.auditStatus === '20'">修改
  47. </view>
  48. <view class="certification-btn-bols flex_c_c btn-p3" @click="onEdit" v-if="data.auditStatus === '30'">修改
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. </template>
  54. <script setup>
  55. import { computed, ref, reactive, watch } from 'vue';
  56. import { provide, inject } from 'vue';
  57. import { onLoad } from '@dcloudio/uni-app';
  58. import store from '@/store';
  59. import { applyVolunteerService, reapplyVolunteerService, volunteerServiceDetails, volunteerServiceDelete, volunteerServiceUp, volunteerServiceDown } from '@/api/volunteer';
  60. import Step from './Step.vue';
  61. import Category from './Category.vue';
  62. import CategoryInfo from './CategoryInfo.vue';
  63. import CategoryImags from './CategoryImags.vue';
  64. import Specifications from './Specifications.vue';
  65. const step = ref(0);
  66. const moreBtn = ref(false);
  67. const volunteerServiceId = ref(null);//服务id
  68. // true: 显示1、2步骤 false: 2、3步骤(查看)
  69. const viewStatus = computed(() => {
  70. return ![1, 2].includes(step.value)
  71. })
  72. provide('viewStatus', viewStatus);
  73. const data = reactive({
  74. //服务
  75. project: {
  76. // id: '13',
  77. // businessTierName: '居家陪伴',
  78. },
  79. activeProjects: [
  80. {
  81. id: '13',
  82. businessTierName: '居家陪伴',
  83. },
  84. {
  85. id: '14',
  86. businessTierName: '出行陪伴',
  87. }
  88. ],
  89. auditStatus: '1', //审批状态 1待审批2已通过3已驳回
  90. projectImages: [],//图片 注:第一张用于封面展示
  91. businessManagementId: '',//服务类型id
  92. name: '',//服务名称
  93. coverUrl: '', //服务封面图片地址
  94. url: '',//服务展示图片地址,多张照片使用逗号隔开
  95. serviceDescribe: '',//服务描述
  96. volunteerServiceSpecList: [ //服务规格列表
  97. {
  98. specDescribe: null,//规格说明
  99. price: null,//服务价格(元)
  100. unit: null,//购买单位,例如:次、小时、天等
  101. minPurchaseQuantity: null,//最少购买数,默认为1
  102. duration: null,//服务时长(分钟),非时效服务可为空
  103. serviceType: null,//服务规格类型: 10周期服务 20单次服务 30非时效服务
  104. isDefault: null,//是否默认规格 0:否 1:是
  105. isDefault: 1,//是否默认规格 0:否 1:是
  106. }
  107. ]
  108. });
  109. provide('formData', data);
  110. watch(data, (newVal, oldVal) => {
  111. uni.$u.debounce(storageData, 500)
  112. })
  113. const storageData = () => {
  114. //新增的时候做数据存储
  115. !volunteerServiceId.value && store.dispatch('handlerReleaseFormData', data)
  116. }
  117. //清除数据
  118. const resetFormData = () => {
  119. data.projectImages = [];
  120. data.businessManagementId = '';
  121. data.name = '';
  122. data.coverUrl = '';
  123. data.url = '';
  124. data.serviceDescribe = '';
  125. data.project = {};
  126. data.auditStatus = null;
  127. data.volunteerServiceSpecList = [
  128. {
  129. specDescribe: null,
  130. price: null,
  131. unit: null,
  132. minPurchaseQuantity: null,
  133. duration: null,
  134. serviceType: null,
  135. isDefault: null
  136. }
  137. ];
  138. }
  139. // 服务规格必填校验
  140. const specListVerify = () => {
  141. return new Promise((resolve, reject) => {
  142. const { volunteerServiceSpecList } = data;
  143. if (!volunteerServiceSpecList || volunteerServiceSpecList.length === 0) {
  144. reject('请至少添加一个服务规格');
  145. return;
  146. }
  147. for (let i = 0; i < volunteerServiceSpecList.length; i++) {
  148. const item = volunteerServiceSpecList[i];
  149. if (!item.duration) {
  150. reject('服务时常不能为空');
  151. return;
  152. }
  153. if (!item.specDescribe) {
  154. reject('规格说明不能为空');
  155. return;
  156. }
  157. if (!item.price || isNaN(item.price) || Number(item.price) <= 0) {
  158. reject('请输入正确的服务价格');
  159. return;
  160. }
  161. if (!item.unit) {
  162. reject('购买单位不能为空');
  163. return;
  164. }
  165. if (!item.minPurchaseQuantity || Number(item.minPurchaseQuantity) < 1) {
  166. reject('最少购买数量必须大于等于1');
  167. return;
  168. }
  169. if (!item.serviceType) {
  170. reject('请选择服务类型');
  171. return;
  172. }
  173. }
  174. resolve(true);
  175. });
  176. };
  177. //服务必选验证
  178. const projectVerify = async () => {
  179. return new Promise(async (resolve, reject) => {
  180. if (!data.project.id) reject('请选择服务类目');
  181. if (data.name.trim().length === 0) reject('请填写服务名称');
  182. if (data.serviceDescribe.trim().length === 0) reject('请填写服务描述');
  183. if (data.projectImages.length === 0) reject('请上传服务相关图片');
  184. //服务规格描述校验
  185. try {
  186. await specListVerify(); // 校验服务规格列表
  187. } catch (error) {
  188. reject(error);
  189. return;
  190. }
  191. resolve(true)
  192. })
  193. }
  194. const goNext = async () => {
  195. try {
  196. if (step.value === 0) {
  197. const verify = await projectVerify();
  198. verify && onSubmit()
  199. }
  200. step.value = step.value + 1;
  201. } catch (error) {
  202. uni.showToast({
  203. title: error,
  204. icon: 'none'
  205. })
  206. console.log("TCL: goNext -> error", error)
  207. }
  208. }
  209. //服务申请
  210. const onSubmit = async () => {
  211. console.log('提交', data);
  212. return new Promise(async (resolve, reject) => {
  213. const {
  214. name,//服务名称
  215. serviceDescribe,//服务描述
  216. volunteerServiceSpecList,
  217. projectImages,//图片
  218. project,//选择的服务
  219. } = data;
  220. const submit_data = {
  221. volunteerId: store.getters.userId,//志愿者id
  222. businessManagementId: project.id,//服务类型id
  223. name,//服务名称
  224. coverUrl: projectImages[0], //服务封面图片地址
  225. url: projectImages.slice(1).join(','),//服务展示图片地址,多张照片使用逗号隔开
  226. serviceDescribe,//服务描述
  227. volunteerServiceSpecList,
  228. }
  229. console.log('submit', submit_data);
  230. let res = null;
  231. //修改
  232. if (volunteerServiceId.value) {
  233. submit_data.volunteerServiceId = volunteerServiceId.value;
  234. res = await reapplyVolunteerService(submit_data);
  235. } else {
  236. res = await applyVolunteerService(submit_data);
  237. }
  238. console.log("TCL: onSubmit -> res", res)
  239. if (res.code === 200) {
  240. store.dispatch('clearReleaseFormData')
  241. uni.showToast({
  242. title: volunteerServiceId.value ? '提交成功' : '申请成功',
  243. icon: 'success'
  244. })
  245. resolve();
  246. }
  247. reject(res.msg);
  248. })
  249. }
  250. const getServeDetails = async () => {
  251. try {
  252. const res = await volunteerServiceDetails(volunteerServiceId.value);
  253. if (res.code === 200) {
  254. const imgs = [res.data.coverUrl, ...res.data.url.split(',')]
  255. const res_data = {
  256. ...res.data,
  257. projectImages: imgs,
  258. project: {
  259. id: res.data.businessManagementId,
  260. businessTierName: res.data.businessTierName,
  261. },
  262. }
  263. if (res.data.auditStatus === '10') { //未审核
  264. step.value = 1;
  265. }
  266. if (['20', '30', '40'].includes(res.data.auditStatus)) { //已通过、未通过
  267. step.value = 2;
  268. }
  269. Object.assign(data, res_data);
  270. }
  271. console.log("TCL: getServeDetails -> res", res)
  272. } catch (error) {
  273. console.log("TCL: getServeDetails -> error", error)
  274. }
  275. }
  276. const onDelete = async () => {
  277. try {
  278. uni.showModal({
  279. title: '提示',
  280. content: '确定要删除该服务吗?',
  281. success: async (res) => {
  282. if (res.confirm) {
  283. // 调用删除接口
  284. try {
  285. await volunteerServiceDelete(volunteerServiceId.value);
  286. uni.showToast({ title: '删除成功' });
  287. setTimeout(() => {
  288. uni.navigateBack({
  289. delta: 1
  290. });
  291. }, 300)
  292. } catch (err) {
  293. uni.showToast({ title: '删除失败', icon: 'none' });
  294. }
  295. }
  296. }
  297. });
  298. } catch (error) {
  299. console.log("TCL: onDelete -> error", error)
  300. }
  301. }
  302. const onAdd = async () => {
  303. try {
  304. resetFormData();
  305. step.value = 0;
  306. } catch (error) {
  307. console.log("TCL: onAdd -> error", error)
  308. }
  309. }
  310. const onEdit = async () => {
  311. try {
  312. data.auditStatus = null;
  313. step.value = 0;
  314. } catch (error) {
  315. console.log("TCL: onEdit -> onEdit", error)
  316. }
  317. }
  318. const onDown = async () => {
  319. try {
  320. uni.showModal({
  321. title: '提示',
  322. content: '确定要下线该服务吗?',
  323. success: async (res) => {
  324. if (res.confirm) {
  325. // 调用删除接口
  326. try {
  327. const res = await volunteerServiceDown(volunteerServiceId.value);
  328. if (res.code === 200) {
  329. uni.showToast({
  330. title: '下线成功',
  331. icon: 'success'
  332. })
  333. getServeDetails();
  334. }
  335. } catch (err) {
  336. uni.showToast({ title: '下线失败', icon: 'none' });
  337. }
  338. }
  339. }
  340. });
  341. } catch (error) {
  342. console.log("TCL: onDown -> onEdit", error)
  343. }
  344. }
  345. const onUp = async () => {
  346. try {
  347. const res = await volunteerServiceUp(volunteerServiceId.value);
  348. if (res.code === 200) {
  349. uni.showToast({
  350. title: '服务上线成功',
  351. icon: 'success'
  352. })
  353. getServeDetails();
  354. }
  355. } catch (error) {
  356. console.log("TCL: onDown -> onEdit", error)
  357. }
  358. }
  359. const onServeStatus = async () => {
  360. try {
  361. //下线
  362. if (data.serviceStatus === '10') {
  363. await onDown();
  364. } else {
  365. await onUp();
  366. }
  367. getServeDetails();
  368. } catch (error) {
  369. console.log("TCL: onDown -> error", error)
  370. }
  371. }
  372. //选取服务返回
  373. const activeProjectBack = (options) => {
  374. if (options.serves) {
  375. const serves = JSON.parse(decodeURIComponent(options.serves));
  376. Object.assign(data, { project: serves });
  377. }
  378. }
  379. // 初始化数据
  380. const initData = () => {
  381. console.log('初始化数据', store.state.business);
  382. try {
  383. const initD = store.state.business.releaseFormData;
  384. if (initD) {
  385. Object.assign(data, initD);
  386. }
  387. } catch (error) {
  388. console.log("TCL: initData -> error", error)
  389. }
  390. }
  391. onLoad((options) => {
  392. console.log("TCL: options", options);
  393. if (options.volunteerServiceId) {
  394. volunteerServiceId.value = options.volunteerServiceId;
  395. getServeDetails();
  396. return;
  397. }
  398. initData();
  399. })
  400. defineExpose({ activeProjectBack });
  401. </script>
  402. <style lang="scss" scoped>
  403. @import "./index.scss";
  404. </style>