details.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="jsInfos" ref="form" label-width="120px">
  4. <el-card class="box-card">
  5. <div slot="header" class="clearfix">
  6. <span class="section-title">基本信息</span>
  7. </div>
  8. <el-row :gutter="20">
  9. <el-col :span="8">
  10. <el-form-item label="姓名">
  11. <el-input v-model="jsInfos.cName" placeholder="请输入姓名"></el-input>
  12. </el-form-item>
  13. </el-col>
  14. <el-col :span="8">
  15. <el-form-item label="昵称">
  16. <el-input v-model="jsInfos.cNickName"></el-input>
  17. </el-form-item>
  18. </el-col>
  19. <el-col :span="8">
  20. <el-form-item label="电话">
  21. <el-input v-model="jsInfos.cPhone" placeholder="请输入电话"></el-input>
  22. </el-form-item>
  23. </el-col>
  24. </el-row>
  25. <el-row :gutter="20">
  26. <el-col :span="8">
  27. <el-form-item label="城市">
  28. <el-select
  29. v-model="jsInfos.deptId"
  30. placeholder="请选择城市"
  31. @change="handleSelectionChange"
  32. style="width: 100%"
  33. >
  34. <el-option
  35. v-for="item in options"
  36. :key="item.deptId"
  37. :label="item.deptName"
  38. :value="item.deptId"
  39. >
  40. </el-option>
  41. </el-select>
  42. </el-form-item>
  43. </el-col>
  44. <el-col :span="8">
  45. <el-form-item label="性别">
  46. <el-radio-group v-model="jsInfos.nSex">
  47. <el-radio :label="0">女</el-radio>
  48. <el-radio :label="1">男</el-radio>
  49. </el-radio-group>
  50. </el-form-item>
  51. </el-col>
  52. <el-col :span="8">
  53. <el-form-item label="是否推荐">
  54. <el-radio-group v-model="jsInfos.nB1">
  55. <el-radio :label="0">否</el-radio>
  56. <el-radio :label="1">是</el-radio>
  57. </el-radio-group>
  58. </el-form-item>
  59. </el-col>
  60. </el-row>
  61. <el-row>
  62. <el-col :span="24">
  63. <el-form-item label="地址">
  64. <el-input v-model="jsInfos.name" disabled placeholder="请在地图选择"></el-input>
  65. </el-form-item>
  66. </el-col>
  67. <el-col :span="24">
  68. <el-form-item label="定位选择">
  69. <Map
  70. :dataObj="addr"
  71. :newAddress="newAddress"
  72. @uploadAddress="uploadAddressData"
  73. />
  74. </el-form-item>
  75. </el-col>
  76. </el-row>
  77. <el-row :gutter="20">
  78. <el-col :span="24">
  79. <el-form-item label="个人简介">
  80. <el-input
  81. type="textarea"
  82. :rows="3"
  83. placeholder="请输入个人简介"
  84. v-model="jsInfos.cJianjie"
  85. >
  86. </el-input>
  87. </el-form-item>
  88. </el-col>
  89. </el-row>
  90. <el-row :gutter="20">
  91. <el-col :span="12">
  92. <el-form-item label="头像">
  93. <ImageUpload v-model="jsInfos.cPortrait" :limit="1" />
  94. </el-form-item>
  95. </el-col>
  96. <el-col :span="12">
  97. <el-form-item label="生活照">
  98. <ImageUpload v-model="jsInfos.cImgList" :limit="9" tipText="建议上传生活近照,最多9张" />
  99. </el-form-item>
  100. </el-col>
  101. <el-col :span="24">
  102. <el-form-item label="视频展示">
  103. <el-upload
  104. class="video-uploader"
  105. action="#"
  106. accept="video/*"
  107. :limit="1"
  108. list-type="picture-card"
  109. :http-request="handleVideoUpload"
  110. :before-upload="beforeVideoUpload"
  111. :show-file-list="false"
  112. >
  113. <video
  114. class="video-preview"
  115. v-if="jsInfos.cVideo"
  116. :src="getVideoUrl(jsInfos.cVideo)"
  117. controls
  118. ></video>
  119. <i v-else class="el-icon-plus"></i>
  120. <div class="el-upload__tip" slot="tip">支持MP4、AVI等格式,大小不超过100MB</div>
  121. </el-upload>
  122. </el-form-item>
  123. </el-col>
  124. </el-row>
  125. </el-card>
  126. <el-card class="box-card" style="margin-top: 20px;">
  127. <div slot="header" class="clearfix">
  128. <span class="section-title">资质与详细信息</span>
  129. </div>
  130. <el-row :gutter="20">
  131. <el-col :span="8">
  132. <el-form-item label="评分">
  133. <el-input v-model="jsInfos.nStar" type="number"></el-input>
  134. </el-form-item>
  135. </el-col>
  136. <el-col :span="8">
  137. <el-form-item label="点赞数">
  138. <el-input v-model="jsInfos.nZan" type="number"></el-input>
  139. </el-form-item>
  140. </el-col>
  141. <el-col :span="8">
  142. <el-form-item label="佣金比例">
  143. <el-input v-model="jsInfos.nBili">
  144. <template slot="append">%</template>
  145. </el-input>
  146. </el-form-item>
  147. </el-col>
  148. <el-col :span="8">
  149. <el-form-item label="已服务数量">
  150. <el-input v-model="jsInfos.nNum" type="number"></el-input>
  151. </el-form-item>
  152. </el-col>
  153. <el-col :span="8">
  154. <el-form-item label="虑拟订单数" prop="virtualOrderNumber">
  155. <el-input type="number" v-model="jsInfos.virtualOrderNumber" placeholder="请输入虑拟订单数" />
  156. </el-form-item>
  157. </el-col>
  158. <el-col :span="8">
  159. <el-form-item label="虚拟技师数量" prop="virtualTechNumber">
  160. <el-input type="number" v-model="jsInfos.virtualTechNumber" placeholder="请输入虚拟技师数量" />
  161. </el-form-item>
  162. </el-col>
  163. <el-col :span="8">
  164. <el-form-item label="服务项目">
  165. <el-select
  166. v-model="jsProjectList"
  167. multiple
  168. filterable
  169. allow-create
  170. collapse-tags
  171. value-key="cId"
  172. placeholder="请选择项目"
  173. style="width: 100%"
  174. >
  175. <el-option
  176. v-for="item in projectList"
  177. :key="item.cId"
  178. :label="item.cTitle"
  179. :value="item"
  180. ></el-option>
  181. </el-select>
  182. </el-form-item>
  183. </el-col>
  184. </el-row>
  185. <el-row :gutter="20">
  186. <el-col :span="24">
  187. <el-form-item label="身份证照片">
  188. <ImageUpload v-model="jsInfos.cSfzImg" :limit="2" />
  189. </el-form-item>
  190. </el-col>
  191. <el-col :span="24">
  192. <el-form-item label="健康证">
  193. <ImageUpload v-model="jsInfos.healthCertificate" :limit="1" />
  194. </el-form-item>
  195. </el-col>
  196. <el-col :span="24">
  197. <el-form-item label="资格证">
  198. <ImageUpload v-model="jsInfos.certification" :limit="1" />
  199. </el-form-item>
  200. </el-col>
  201. <el-col :span="24">
  202. <el-form-item label="营业执照">
  203. <ImageUpload v-model="jsInfos.businessLicense" :limit="1" />
  204. </el-form-item>
  205. </el-col>
  206. <el-col :span="24">
  207. <el-form-item label="无犯罪证明">
  208. <ImageUpload v-model="jsInfos.noCrime" :limit="1" />
  209. </el-form-item>
  210. </el-col>
  211. </el-row>
  212. </el-card>
  213. <el-card class="box-card" style="margin-top: 20px;">
  214. <div slot="header" class="clearfix">
  215. <span class="section-title">承诺相关信息</span>
  216. </div>
  217. <el-row :gutter="20">
  218. <el-col :span="24">
  219. <el-form-item label="承诺书">
  220. <FileUpload
  221. :fileSize="10"
  222. :fileType="['pdf', 'jpg', 'jpeg', 'png']"
  223. v-model="jsInfos.commitment"
  224. />
  225. </el-form-item>
  226. </el-col>
  227. <el-col :span="24">
  228. <el-form-item label="承诺录音">
  229. <FileUpload
  230. :fileSize="10"
  231. :fileType="['mp3', 'wav', 'wma']"
  232. v-model="jsInfos.recording"
  233. />
  234. </el-form-item>
  235. </el-col>
  236. <el-col :span="24">
  237. <el-form-item label="承诺录像">
  238. <FileUpload
  239. :fileSize="50"
  240. :fileType="['mp4', 'mov', 'mkv', 'wmv']"
  241. v-model="jsInfos.videoRecording"
  242. />
  243. </el-form-item>
  244. </el-col>
  245. </el-row>
  246. </el-card>
  247. <div class="footer-actions">
  248. <el-button type="info" icon="el-icon-chat-dot-round" @click="addComplaintShow">添加评论</el-button>
  249. <el-button
  250. :type="jsInfos.nStatus2 == -1 ? 'success' : 'warning'"
  251. @click="work"
  252. >
  253. {{ jsInfos.nStatus2 == -1 ? '上 岗' : '下 岗' }}
  254. </el-button>
  255. <template v-if="jsInfos.nTong == 0">
  256. <el-button type="success" icon="el-icon-check" @click="auditJs(jsInfos.id)">审 核</el-button>
  257. <el-button type="danger" icon="el-icon-close" @click="returnJs(jsInfos.id)">退 回</el-button>
  258. </template>
  259. <el-button
  260. :type="jsInfos.nTong == 3 ? 'success' : 'danger'"
  261. @click="blacklist"
  262. >
  263. {{ jsInfos.nTong == 3 ? '恢 复' : '拉 黑' }}
  264. </el-button>
  265. <el-button type="primary" icon="el-icon-edit" @click="submitForm">保存修改</el-button>
  266. <el-button @click="$router.go(-1)">返 回</el-button>
  267. </div>
  268. </el-form>
  269. <el-dialog title="添加评论" :visible.sync="dialogFormVisible" width="500px">
  270. <el-form :model="complainForm" label-width="80px">
  271. <el-form-item label="评分">
  272. <el-rate v-model="complainForm.nStar" style="margin-top: 10px;"></el-rate>
  273. </el-form-item>
  274. <el-form-item label="内容">
  275. <el-input type="textarea" v-model="complainForm.cText" placeholder="请输入评论内容"></el-input>
  276. </el-form-item>
  277. <el-form-item label="时间">
  278. <el-date-picker
  279. v-model="complainForm.dTime"
  280. type="datetime"
  281. placeholder="选择日期时间"
  282. :picker-options="pickerOptions"
  283. style="width: 100%"
  284. >
  285. </el-date-picker>
  286. </el-form-item>
  287. </el-form>
  288. <div slot="footer" class="dialog-footer">
  289. <el-button @click="dialogFormVisible = false">取 消</el-button>
  290. <el-button type="primary" @click="submitComplain">确 定</el-button>
  291. </div>
  292. </el-dialog>
  293. </div>
  294. </template>
  295. <script>
  296. import {
  297. getJs,
  298. updateJs,
  299. auditJs,
  300. repulseJs,
  301. addComplaint,
  302. getProject,
  303. changeWork,
  304. block,
  305. getDept,
  306. } from "@/api/system/js";
  307. import { uploadvideo } from "@/api/public";
  308. import Map from "@/components/map/map.vue";
  309. export default {
  310. name: "JsDetails",
  311. components: {
  312. Map,
  313. },
  314. data() {
  315. return {
  316. // 禁用未来日期
  317. pickerOptions: {
  318. disabledDate(time) {
  319. return time.getTime() > Date.now();
  320. },
  321. },
  322. options: [], // 城市/部门列表
  323. dialogFormVisible: false,
  324. // 主信息对象
  325. jsInfos: {
  326. cImgList: [], // 确保初始化为数组或若依组件需要的格式
  327. cSfzImg: [],
  328. nStatus2: 0,
  329. nTong: 0,
  330. },
  331. addr: { lat: "", lng: "" }, // 地图回显坐标
  332. newAddress: "太原", // 地图默认城市
  333. projectList: [], // 所有项目
  334. jsProjectList: [], // 当前选中的项目对象
  335. complainForm: {
  336. cNickName: "匿名评论",
  337. nStar: 5,
  338. cText: "",
  339. dTime: new Date(),
  340. },
  341. };
  342. },
  343. mounted() {
  344. const id = this.$route.query.id;
  345. if (id) {
  346. this.complainForm.cJsid = id;
  347. this.getJsInfo(id);
  348. }
  349. this.getProjictList();
  350. this.getDept();
  351. },
  352. methods: {
  353. /** 辅助函数:处理视频URL回显(如果是相对路径则加上base_api) */
  354. getVideoUrl(url) {
  355. if (!url) return '';
  356. if (url.indexOf("http") === 0) return url;
  357. return process.env.VUE_APP_BASE_API + url;
  358. },
  359. // 视频上传前的校验
  360. beforeVideoUpload(file) {
  361. const isVideo = file.type.includes("video/");
  362. const isLtSize = file.size / 1024 / 1024 < 100;
  363. if (!isVideo) {
  364. this.$message.error("只能上传视频文件!");
  365. }
  366. if (!isLtSize) {
  367. this.$message.error("视频大小不能超过100MB!");
  368. }
  369. return isVideo && isLtSize;
  370. },
  371. // 视频自定义上传
  372. handleVideoUpload(options) {
  373. const { file } = options;
  374. const formData = new FormData();
  375. formData.append("video", file);
  376. uploadvideo(formData).then((response) => {
  377. // 保存相对路径,显示时处理
  378. this.$set(this.jsInfos, 'cVideo', response.fileName);
  379. this.$message.success("视频上传成功");
  380. });
  381. },
  382. // 地图选点回调
  383. uploadAddressData(payload) {
  384. this.jsInfos.latitude = payload.latitude;
  385. this.jsInfos.longitude = payload.longitude;
  386. this.jsInfos.name = payload.addressName;
  387. },
  388. // 城市选择回调
  389. handleSelectionChange(deptId) {
  390. // 找到对应的 deptName
  391. const obj = this.options.find(item => item.deptId === deptId);
  392. if(obj) {
  393. this.jsInfos.city = obj.deptName;
  394. }
  395. },
  396. getDept() {
  397. getDept().then((res) => {
  398. if (res.code == 200) {
  399. this.options = res.data;
  400. }
  401. });
  402. },
  403. getProjictList() {
  404. getProject({}).then((res) => {
  405. if (res.code == 200) {
  406. this.projectList = res.data;
  407. }
  408. });
  409. },
  410. // 获取技师详情
  411. getJsInfo(id) {
  412. getJs({ id: id }).then((response) => {
  413. const data = response.data;
  414. // 1. 处理地图信息
  415. this.addr = {
  416. lat: data.latitude,
  417. lng: data.longitude
  418. };
  419. // 2. 处理项目回显
  420. this.jsProjectList = [];
  421. if (data.projects && data.projects.length > 0) {
  422. data.projects.forEach((item) => {
  423. this.jsProjectList.push({ cTitle: item.cTitle, cId: item.cId });
  424. });
  425. }
  426. // 3. 赋值给主对象
  427. // 注意:ImageUpload组件通常需要逗号分隔的字符串或数组(取决于版本),若依默认通常存相对路径
  428. // 这里假设后端返回的是相对路径,如果不是,ImageUpload会自动处理显示,但v-model最好绑定相对路径
  429. this.jsInfos = data;
  430. // 确保数组类型字段初始化,防止组件报错
  431. if (!this.jsInfos.cImgList) this.jsInfos.cImgList = [];
  432. if (!this.jsInfos.cSfzImg) this.jsInfos.cSfzImg = [];
  433. });
  434. },
  435. // 提交评论
  436. submitComplain() {
  437. if (!this.complainForm.nStar) return this.$message.warning("请输入评分");
  438. if (!this.complainForm.cText) return this.$message.warning("请输入评论内容");
  439. if (!this.complainForm.dTime) return this.$message.warning("请选择评论时间");
  440. addComplaint(this.complainForm).then((res) => {
  441. if (res.code == 200) {
  442. this.$message.success("提交成功");
  443. this.dialogFormVisible = false;
  444. } else {
  445. this.$message.error(res.msg);
  446. }
  447. });
  448. },
  449. addComplaintShow() {
  450. this.complainForm.name = this.jsInfos.cName;
  451. this.complainForm.cId = this.jsInfos.cId;
  452. this.dialogFormVisible = true;
  453. },
  454. // 上岗/下岗切换
  455. work() {
  456. let data = {
  457. id: this.jsInfos.id,
  458. nStatus2: this.jsInfos.nStatus2 == 0 ? "-1" : "0"
  459. };
  460. changeWork(data).then((res) => {
  461. if (res.code == 200) {
  462. this.$message.success(data.nStatus2 == 0 ? "已上岗" : "已下岗");
  463. this.getJsInfo(this.jsInfos.id); // 刷新数据
  464. } else {
  465. this.$message.error(res.msg);
  466. }
  467. });
  468. },
  469. // 拉黑/恢复
  470. blacklist() {
  471. let data = {
  472. id: this.jsInfos.id,
  473. nTong: this.jsInfos.nTong == 3 ? "0" : "3"
  474. };
  475. block(data).then((res) => {
  476. if (res.code == 200) {
  477. this.$message.success(data.nTong == 3 ? "拉黑成功" : "取消拉黑成功");
  478. this.getJsInfo(this.jsInfos.id);
  479. } else {
  480. this.$message.error(res.msg);
  481. }
  482. });
  483. },
  484. // 提交修改
  485. submitForm() {
  486. // 处理项目ID集合
  487. const projectIds = this.jsProjectList.map(item => item.cId).join(",");
  488. this.jsInfos.cBhList = projectIds;
  489. // ImageUpload 组件已经通过 v-model 更新了 jsInfos 中的图片字段
  490. // 注意:如果 ImageUpload 返回的是数组,而后端需要逗号分隔字符串,这里需要转换
  491. // 假设 cImgList 和 cSfzImg 是数组,其他单图是字符串
  492. let submitData = { ...this.jsInfos };
  493. // 如果后端要求图片是字符串而不是数组,进行转换(视若依版本而定,通用处理如下)
  494. if(Array.isArray(submitData.cImgList)) {
  495. submitData.cImgList = submitData.cImgList.join(',');
  496. }
  497. if(Array.isArray(submitData.cSfzImg)) {
  498. submitData.cSfzImg = submitData.cSfzImg.join(',');
  499. }
  500. updateJs(submitData).then((res) => {
  501. if (res.code == 200) {
  502. this.$message.success("修改成功");
  503. this.$router.go(-1);
  504. } else {
  505. this.$message.error(res.msg);
  506. }
  507. });
  508. },
  509. // 审核通过
  510. auditJs(id) {
  511. if (this.jsInfos.nTong != 0) return this.$message.warning("该技师已操作");
  512. auditJs({ id }).then(() => {
  513. this.$message.success("审核通过");
  514. this.getJsInfo(id);
  515. });
  516. },
  517. // 退回
  518. returnJs(id) {
  519. if (this.jsInfos.nTong != 0) return this.$message.warning("该技师已操作");
  520. this.$prompt("请输入退回原因", "提示", {
  521. confirmButtonText: "确定",
  522. cancelButtonText: "取消",
  523. }).then(({ value }) => {
  524. repulseJs({ id, reasonRefusal: value }).then(() => {
  525. this.$message.success("已退回");
  526. this.getJsInfo(id);
  527. });
  528. }).catch(() => {});
  529. },
  530. },
  531. };
  532. </script>
  533. <style lang="scss" scoped>
  534. .app-container {
  535. padding: 20px;
  536. background-color: #f0f2f5;
  537. min-height: 100vh;
  538. }
  539. .box-card {
  540. margin-bottom: 20px;
  541. }
  542. .section-title {
  543. font-size: 16px;
  544. font-weight: bold;
  545. color: #303133;
  546. padding-left: 10px;
  547. border-left: 4px solid #409EFF;
  548. }
  549. .video-uploader {
  550. .video-preview {
  551. width: 240px;
  552. height: 100%;
  553. object-fit: contain;
  554. }
  555. }
  556. .footer-actions {
  557. padding: 10px 20px;
  558. text-align: right;
  559. background: #fff;
  560. box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
  561. position: fixed;
  562. left: 0;
  563. right: 0;
  564. bottom: 0;
  565. z-index: 10;
  566. }
  567. </style>