teamUser.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <template>
  2. <div class="full_page">
  3. <div class="content">
  4. <div class="top adfac">
  5. <div class="t_l adfac">
  6. <img src="@/assets/images/agent/arrow_left.png" @click="handleBack">
  7. <div class="spans">
  8. <span>公司管理</span>
  9. <span class="last">&nbsp;/&nbsp;成员管理</span>
  10. </div>
  11. </div>
  12. </div>
  13. <div class="c_bottom adf">
  14. <div class="cb_l">
  15. <el-select v-model="programid" placeholder="请选择项目" @change="handleChange">
  16. <el-option v-for="item in useAgentStore().companyList" :key="item.id" :label="item.enterpriseName" :value="item.id"></el-option>
  17. </el-select>
  18. <div class="cbl_list">
  19. <div class="cbl_item" :class="{'active':tidx===index}" v-for="(item, index) in teamList" :key="index" @click="handleTeam(item,index)">
  20. {{ item.teamName }}
  21. </div>
  22. </div>
  23. </div>
  24. <div class="cb_r">
  25. <div class="cbr_top adfacjb">
  26. <div class="ct_l">团队成员</div>
  27. <div class="ct_r adfac">
  28. <div class="tr_btn" @click="handleDownloadExcel" v-hasPermi="['core:project:downloadExcel']" v-if="programid&&teamid">模板下载</div>
  29. <el-upload v-if="programid&&teamid"
  30. :action="uploadUrl"
  31. :data="{enterpriseId: programid, teamId: teamid}"
  32. :headers="uploadHeaders"
  33. :before-upload="handleBeforeUpload"
  34. :on-success="handleSuccess"
  35. >
  36. <div class="tr_btn" v-hasPermi="['sys:user:export']">导入成员</div>
  37. </el-upload>
  38. <div class="tr_btn tb2" @click="handleAddUser" v-hasPermi="['sys:user:add']">添加成员</div>
  39. <div class="tr_btn tb2" @click="handleSendEmail">发送邮件</div>
  40. </div>
  41. </div>
  42. <el-table ref="multipleTable" @selection-change="handleSelectionChange":data="userList" border cell-class-name="vertical-top-cell" v-loading="loading" empty-text="暂无人员" max-height="578px" style="margin-top: 16px;">
  43. <el-table-column
  44. type="selection"
  45. width="55">
  46. </el-table-column>
  47. <el-table-column label="序号" width="50">
  48. <template #default="scope">
  49. {{ scope.$index + 1 }}
  50. </template>
  51. </el-table-column>
  52. <el-table-column label="姓名" prop="realName"></el-table-column>
  53. <el-table-column label="性别" prop="gender">
  54. <template #default="{ row }">{{ genderCfg[row.gender]||'未知' }}</template>
  55. </el-table-column>
  56. <el-table-column label="类型" prop="category">
  57. <template #default="{ row }">{{ UserCategory.find(u=>u.value===row.category)?.label ||'未知' }}</template>
  58. </el-table-column>
  59. <el-table-column label="人物简介" prop="introduction" width="400" show-overflow-tooltip></el-table-column>
  60. <el-table-column label="人物故事" prop="userStory" width="400" show-overflow-tooltip></el-table-column>
  61. <el-table-column label="操作" width="180">
  62. <template #default="scope">
  63. <el-button link type="text" size="mini" @click="handleEdit(scope.row)" v-hasPermi="['sys:user:update']">编辑</el-button>
  64. <el-button link type="text" size="mini" @click="handleDelete(scope.row)" v-hasPermi="['sys:user:delete']">删除</el-button>
  65. <el-button link type="text" size="mini" @click="handleSend(scope.row)" v-hasPermi="['sys:user:delete']">发送邮件</el-button>
  66. </template>
  67. </el-table-column>
  68. </el-table>
  69. <el-row style="display: flex;justify-content: center;">
  70. <el-pagination
  71. @size-change="handleSizeChange"
  72. @current-change="handleCurrentChange"
  73. :current-page="queryParams.page"
  74. :page-sizes="[5, 10, 20, 50]"
  75. :page-size="10"
  76. layout="total, sizes, prev, pager, next, jumper"
  77. :total="total"
  78. v-show="total > 0">
  79. </el-pagination>
  80. </el-row>
  81. </div>
  82. </div>
  83. </div>
  84. <el-drawer :title="userTitle" :visible.sync="userShow" append-to-body size="60%" @close="userShow=false">
  85. <el-form ref="userRef" :model="userForm" :rules="userRules" label-width="100px" style="width: 90%;margin: 0 auto;">
  86. <el-row>
  87. <el-col :span="12">
  88. <el-form-item label="姓名" prop="realName">
  89. <el-input v-model="userForm.realName" placeholder="请输入姓名" />
  90. </el-form-item>
  91. </el-col>
  92. <el-col :span="12">
  93. <el-form-item label="性别" prop="gender">
  94. <el-select v-model="userForm.gender" placeholder="请选择性别" style="width: 100%;">
  95. <el-option label="男" :value="0"></el-option>
  96. <el-option label="女" :value="1"></el-option>
  97. <el-option label="保密" :value="2"></el-option>
  98. </el-select>
  99. </el-form-item>
  100. </el-col>
  101. </el-row>
  102. <el-row>
  103. <el-col :span="12">
  104. <el-form-item label="类型" prop="category">
  105. <el-select v-model="userForm.category" placeholder="请选择类型" style="width: 100%;">
  106. <el-option v-for="item in UserCategory" :label="item.label" :value="item.value"></el-option>
  107. </el-select>
  108. </el-form-item>
  109. </el-col>
  110. <el-col :span="12">
  111. <el-form-item label="手机号码" prop="mobile">
  112. <el-input type="number" v-model="userForm.mobile" placeholder="请输入手机号码" />
  113. </el-form-item>
  114. </el-col>
  115. </el-row>
  116. <el-row>
  117. <el-col :span="12">
  118. <el-form-item label="所属部门" prop="dept">
  119. <el-input v-model="userForm.dept" placeholder="请输入所属部门" />
  120. </el-form-item>
  121. </el-col>
  122. <el-col :span="12">
  123. <el-form-item label="教育程度" prop="education">
  124. <el-input v-model="userForm.education" placeholder="请输入教育程度" />
  125. </el-form-item>
  126. </el-col>
  127. </el-row>
  128. <el-row>
  129. <el-col :span="12">
  130. <el-form-item label="分工" prop="divisionOfLabour">
  131. <el-input v-model="userForm.divisionOfLabour" placeholder="请输入分工" />
  132. </el-form-item>
  133. </el-col>
  134. <el-col :span="12">
  135. <el-form-item label="职位" prop="post">
  136. <el-input v-model="userForm.post" placeholder="请输入职位" />
  137. </el-form-item>
  138. </el-col>
  139. </el-row>
  140. <el-row>
  141. <el-col :span="12">
  142. <el-form-item label="级别" prop="level">
  143. <el-input v-model="userForm.level" placeholder="请输入级别" />
  144. </el-form-item>
  145. </el-col>
  146. <el-col :span="12">
  147. <el-form-item label="邮箱" prop="email">
  148. <el-input v-model="userForm.email" placeholder="请输入邮箱" />
  149. </el-form-item>
  150. </el-col>
  151. </el-row>
  152. <el-row>
  153. <el-col :span="24">
  154. <el-form-item label="人物简介" prop="introduction">
  155. <el-input type="textarea" :rows="2" v-model="userForm.introduction" placeholder="请输入人物简介" />
  156. </el-form-item>
  157. </el-col>
  158. </el-row>
  159. <el-row>
  160. <el-col :span="24">
  161. <el-form-item label="人物故事" prop="userStory">
  162. <el-input type="textarea" :rows="2" v-model="userForm.userStory" placeholder="请输入人物故事" />
  163. </el-form-item>
  164. </el-col>
  165. </el-row>
  166. </el-form>
  167. <div class="demo-drawer__footer" style="display: flex;justify-content: end;">
  168. <el-button :loading="buttonLoading2" type="primary" @click="submitForm2" v-hasPermi="['sys:user:save']">保 存</el-button>
  169. <el-button @click="cancel2" style="margin-right: 5%;">取 消</el-button>
  170. </div>
  171. </el-drawer>
  172. <el-dialog width="30%" :visible.sync="emailShow" title="选择邮件模板" @close="emailCancel">
  173. <el-form ref="emailRef" :model="emailForm" :rules="emailRules" label-width="100px" style="width: 90%;margin: 0 auto;">
  174. <el-form-item label="邮件模板" prop="tempId">
  175. <el-select v-model="emailForm.tempId" placeholder="请选择邮件模板" style="width: 100%;">
  176. <el-option v-for="item in useAgentStore().emailModelList" :label="item.name" :value="item.id"></el-option>
  177. </el-select>
  178. </el-form-item>
  179. </el-form>
  180. <div class="demo-drawer__footer" style="display: flex;justify-content: end;">
  181. <el-button :loading="emailButtonLoading" type="primary" @click="emailSubmitForm">发 送</el-button>
  182. <el-button @click="emailCancel" style="margin-right: 5%;">取 消</el-button>
  183. </div>
  184. </el-dialog>
  185. </div>
  186. </template>
  187. <script setup name="">
  188. import Cookies from "js-cookie";
  189. import { ref, getCurrentInstance, onMounted } from 'vue'
  190. const { proxy } = getCurrentInstance();
  191. import {useAgentStore} from "@/store_v3/modules/agent";
  192. useAgentStore().getCompanyData();
  193. useAgentStore().getEmailModelData();
  194. import {
  195. getCoachList,
  196. updateCoach,
  197. getCoachInfo,
  198. deleteCoach,
  199. addCoach,
  200. getTeamListById,
  201. sendEmailToUser
  202. } from '@/api/agent/index.js'
  203. const uploadUrl = `${window.SITE_CONFIG["apiURL"]}/sys/user/import`
  204. const uploadHeaders = {token:Cookies.get("token")};
  205. const { companyIndustry, staffSize, UserCategory} = proxy.useDict("companyIndustry", "staffSize", "UserCategory");
  206. const programid = ref('')
  207. const teamid = ref('')
  208. const teamList = ref([]);
  209. const tidx = ref('')
  210. const userList = ref([]);
  211. const total = ref(0);
  212. const loading = ref(false);
  213. const genderCfg = {
  214. '0': '男',
  215. '1': '女',
  216. '2': '保密',
  217. }
  218. const userTitle = ref('添加成员信息');
  219. const userShow = ref(false);
  220. const buttonLoading2 = ref(false);
  221. const userRef = ref(null);
  222. const queryParams = ref({
  223. page:1,
  224. limit:10,
  225. teamId:'',
  226. realName:'',
  227. })
  228. const userForm = ref({
  229. id:'',
  230. enterpriseId:'',
  231. teamId:'',
  232. realName:'',
  233. username:'',
  234. gender:'',
  235. birthdate:'',
  236. mobile:'',
  237. dept:'',
  238. education:'',
  239. divisionOfLabour:'',
  240. post:'',
  241. level:'',
  242. userType:2,
  243. category:'',
  244. introduction:'',
  245. userStory:'',
  246. email:'',
  247. });
  248. const userRules = ref({
  249. realName: [
  250. { required: true, message: '请输入姓名', trigger: 'blur' }
  251. ],
  252. gender: [
  253. { required: true, message: '请选择性别', trigger: 'change' }
  254. ],
  255. birthdate: [
  256. { required: true, message: '请选择出生日期', trigger: 'change' }
  257. ],
  258. // mobile: [
  259. // { required: true, message: '请输入手机号码', trigger: 'blur' },
  260. // { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
  261. // ],
  262. dept: [
  263. { required: true, message: '请输入所属部门', trigger: 'blur' }
  264. ],
  265. // education: [
  266. // { required: true, message: '请输入教育程度', trigger: 'blur' }
  267. // ],
  268. // divisionOfLabour: [
  269. // { required: true, message: '请输入分工', trigger: 'blur' }
  270. // ],
  271. post: [
  272. { required: true, message: '请输入职位', trigger: 'blur' }
  273. ],
  274. // level:[
  275. // { required: true, message: '请输入级别', trigger: 'blur' }
  276. // ],
  277. category:[
  278. { required: true, message: '请选择成员类型', trigger: 'change' }
  279. ],
  280. // introduction:[
  281. // { required: true, message: '请输入成员简介', trigger: 'blur' }
  282. // ],
  283. // userStory:[
  284. // { required: true, message: '请输入成员故事', trigger: 'blur' }
  285. // ],
  286. email:[
  287. { required: true, message: '请输入邮箱', trigger: 'blur' },
  288. { pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱', trigger: 'blur' }
  289. ]
  290. });
  291. const multipleSelection = ref([])
  292. const emailShow = ref(false);
  293. const emailRef = ref(null);
  294. const emailButtonLoading = ref(false);
  295. const emailForm = ref({
  296. tempId:null,
  297. userIdList:[]
  298. });
  299. const emailRules = ref({
  300. tempId: [
  301. { required: true, message: '请选择邮件模板', trigger: 'change' }
  302. ]
  303. });
  304. const currentUserId = ref(null);
  305. const emailSubmitForm = async () => {
  306. proxy.$refs.emailRef.validate((valid) => {
  307. if (valid) {
  308. emailButtonLoading.value = true;
  309. emailForm.value.userIdList = multipleSelection.value.length>0?multipleSelection.value.map(item=>item.id):[currentUserId.value];
  310. sendEmailToUser(emailForm.value).then(res=>{
  311. if(res.code!==0) return proxy.$message.error(res.msg);
  312. proxy?.$modal.msgSuccess('发送成功!')
  313. }).finally(()=>{emailButtonLoading.value=false;emailShow.value = false;})
  314. } else {
  315. return false;
  316. }
  317. });
  318. };
  319. const emailCancel = () => {
  320. emailShow.value = false;
  321. emailButtonLoading.value = false;
  322. currentUserId.value = null;
  323. emailForm.value = {
  324. tempId:null,
  325. userIdList:[]
  326. };
  327. };
  328. const handleSelectionChange = (val) => {
  329. multipleSelection.value = val;
  330. }
  331. const handleBack = () => {
  332. proxy.$router.back()
  333. }
  334. const handleBeforeUpload = (e,node,data)=>{
  335. let type = e.name.split('.')[e.name.split('.').length-1];
  336. let isExcel = e.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  337. if(type.toLowerCase() !== 'xlsx' && type.toLowerCase() !== 'xls' && !isExcel){
  338. proxy?.$modal.msgError('请上传xlsx或xls格式的Excel文件!');
  339. return false;
  340. }
  341. setTimeout(()=>{
  342. getUserList();
  343. },1000)
  344. }
  345. const handleSuccess = (e,node,data)=>{
  346. if(e.code===0){
  347. proxy?.$modal.msgSuccess('导入成功!'+e.msg);
  348. }else {
  349. proxy?.$modal.msgError('导入失败!'+e.msg);
  350. }
  351. }
  352. const handleChange = (val) => {
  353. programid.value = val;
  354. getTeamListByCompanyId();
  355. }
  356. const getTeamListByCompanyId = ()=> {
  357. getTeamListById({enterpriseId: programid.value}).then(res=>{
  358. if(res.code!==0) proxy.$message.error(res.msg);
  359. teamList.value = res.data;
  360. if(teamid.value){
  361. let idx = teamList.value.findIndex(item=>{
  362. return item.id === teamid.value;
  363. });
  364. if(idx>-1){
  365. tidx.value = idx;
  366. }
  367. }
  368. })
  369. }
  370. const handleTeam = (item, index) => {
  371. tidx.value = index;
  372. teamid.value = item.id;
  373. getUserList();
  374. }
  375. const getUserList = async () => {
  376. queryParams.value.teamId = teamid.value;
  377. let query = {...queryParams.value};
  378. loading.value = true;
  379. const res = await getCoachList(query);
  380. userList.value = res.data.list;
  381. total.value = res.data.total;
  382. loading.value = false;
  383. }
  384. const handleSizeChange = (e)=>{
  385. queryParams.value.limit = e;
  386. getUserList();
  387. }
  388. const handleCurrentChange = (e)=>{
  389. queryParams.value.page = e;
  390. getUserList();
  391. }
  392. const handleDownloadExcel = () => {
  393. window.location.href = `${window.SITE_CONFIG["apiURL"]}/sys/user/download?token=${Cookies.get("token")}`;
  394. }
  395. const handleAddUser = () => {
  396. if(!teamid.value) return proxy.$message.error('请先选择团队');
  397. reset();
  398. userTitle.value = "新增成员详情";
  399. userShow.value = true;
  400. }
  401. const handleSendEmail = async () => {
  402. if(multipleSelection.value.length === 0) return proxy.$message.error('请选择要发送邮件的成员!');
  403. emailShow.value = true;
  404. // await proxy.$modal.confirm('确定要发送邮件给选中的成员吗?', '提示').finally(()=>{})
  405. }
  406. const handleSend = async row => {
  407. emailShow.value = true;
  408. currentUserId.value = row.id;
  409. // await proxy.$modal.confirm('确定要发送邮件给成员【'+row.realName+'】吗?', '提示').finally(()=>{})
  410. }
  411. const reset = () => {
  412. userForm.value = {
  413. id:'',
  414. enterpriseId:'',
  415. teamId:'',
  416. realName:'',
  417. username:'',
  418. gender:'',
  419. birthdate:'',
  420. mobile:'',
  421. dept:'',
  422. education:'',
  423. divisionOfLabour:'',
  424. post:'',
  425. level:'',
  426. userType:2,
  427. category:'',
  428. introduction:'',
  429. userStory:''
  430. };
  431. proxy.resetForm("userRef");
  432. }
  433. const submitForm2 = () => {
  434. proxy.$refs.userRef.validate((valid) => {
  435. if (valid) {
  436. buttonLoading2.value = true;
  437. // userForm.value.username = userForm.value.mobile;
  438. userForm.value.enterpriseId = programid.value;
  439. userForm.value.teamId = teamid.value;
  440. if(userForm.value.id){
  441. updateCoach(userForm.value).then((res)=>{
  442. if(res.code!==0) return proxy.$message.error(res.msg);
  443. buttonLoading2.value = false;
  444. userShow.value = false;
  445. getUserList();
  446. })
  447. }else{
  448. addCoach(userForm.value).then((res)=>{
  449. if(res.code!==0) return proxy.$message.error(res.msg);
  450. buttonLoading2.value = false;
  451. userShow.value = false;
  452. getUserList();
  453. })
  454. }
  455. } else {
  456. return false;
  457. }
  458. });
  459. }
  460. const cancel2 = () => {
  461. userShow.value = false;
  462. reset();
  463. }
  464. const handleEdit = (row) => {
  465. reset();
  466. getCoachInfo(row.id).then(response => {
  467. userForm.value = {...userForm.value,...response.data};
  468. userTitle.value = "编辑成员详情";
  469. userShow.value = true;
  470. });
  471. }
  472. async function handleDelete(row) {
  473. await proxy?.$modal.confirm('确认删除成员【' + row.realName + '】吗?').finally(() => loading.value = false);
  474. let res = await deleteCoach([row.id]);
  475. if (res.code === 0) proxy?.$modal.msgSuccess("删除成功");
  476. else return proxy?.$modal.msgError(res.msg);
  477. await getUserList();
  478. }
  479. onMounted(()=>{
  480. programid.value = proxy.$route?.query?.enterpriseId;
  481. teamid.value = proxy.$route?.query?.teamId;
  482. getTeamListByCompanyId();
  483. getUserList();
  484. })
  485. </script>
  486. <style scoped lang="scss">
  487. .full_page{
  488. background: #FAFAFA;
  489. .content{
  490. width: calc(100% - 24px);
  491. height: calc(100vh - 30px);
  492. margin: 12px;
  493. background: #FFFFFF;
  494. border-radius: 6px 6px 0px 0px;
  495. border: 1px solid #F3F4F6;
  496. position: relative;
  497. display: flex;
  498. flex-direction: column;
  499. .top{
  500. width: 100%;
  501. height: 64px;
  502. background: #FFFFFF;
  503. border-bottom: 1px solid #F3F4F6;
  504. .t_l{
  505. width: 310px;
  506. img{
  507. width: 36px;
  508. height: 36px;
  509. margin-left: 16px;
  510. cursor: pointer;
  511. }
  512. .spans{
  513. display: flex;
  514. align-items: center;
  515. margin-left: 23px;
  516. span{
  517. font-family: PingFangSC, PingFang SC;
  518. font-weight: 400;
  519. font-size: 14px;
  520. color: #6B7280;
  521. line-height: 14px;
  522. &.last{
  523. color: #111111;
  524. }
  525. }
  526. }
  527. }
  528. }
  529. .c_bottom{
  530. width: 100%;
  531. flex: 1;
  532. display: flex;
  533. .cb_l{
  534. width: 280px;
  535. height: 100%;
  536. border-right: 1px solid #E5E7EB;
  537. padding: 24px 16px;
  538. box-sizing: border-box;
  539. .cbl_list{
  540. margin-top: 24px;
  541. width: 100%;
  542. flex: 1;
  543. overflow-y: auto;
  544. .cbl_item{
  545. width: 100%;
  546. padding: 24px 16px;
  547. box-sizing: border-box;
  548. box-shadow: inset 0px -1px 0px 0px #EFEFEF;
  549. font-family: PingFangSC, PingFang SC;
  550. font-weight: 400;
  551. font-size: 14px;
  552. color: #252525;
  553. line-height: 14px;
  554. overflow: hidden;
  555. text-overflow: ellipsis;
  556. white-space: nowrap;
  557. cursor: pointer;
  558. &.active,&:hover{
  559. background: rgba(118,30,106,0.06);
  560. font-weight: bold;
  561. color: #761E6A;
  562. }
  563. }
  564. }
  565. }
  566. .cb_r{
  567. width: calc(100% - 280px);
  568. padding: 30px 16px 24px;
  569. flex: 1;
  570. box-sizing: border-box;
  571. .ct_l{
  572. font-family: PingFang-SC, PingFang-SC;
  573. font-weight: bold;
  574. font-size: 16px;
  575. color: #252525;
  576. line-height: 16px;
  577. }
  578. .ct_r{
  579. .tr_btn{
  580. width: 92px;
  581. height: 36px;
  582. border-radius: 6px;
  583. border: 1px solid #C1C7D2;
  584. margin-left: 20px;
  585. cursor: pointer;
  586. font-family: PingFangSC, PingFang SC;
  587. font-weight: 400;
  588. font-size: 14px;
  589. color: #111111;
  590. display: flex;
  591. align-items: center;
  592. justify-content: center;
  593. &.tb2{
  594. background: #761E6A;
  595. color: #FFFFFF;
  596. }
  597. }
  598. }
  599. }
  600. }
  601. }
  602. }
  603. </style>