index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import {
  2. DataTableColumn,
  3. NButton,
  4. NCascader,
  5. NDataTable,
  6. NForm,
  7. NFormItem,
  8. NIcon,
  9. NImage,
  10. NInput,
  11. NModal,
  12. NSpace,
  13. useDialog,
  14. useMessage
  15. } from 'naive-ui';
  16. import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
  17. import styles from './index.module.less';
  18. import { useUserStore } from '/src/store/modules/users';
  19. import UploadFile from '/src/components/upload-file';
  20. import { Add } from '@vicons/ionicons5';
  21. import {
  22. api_schoolUpdate,
  23. api_sysAreaQueryAllProvince,
  24. api_teacherPage,
  25. api_tenantInfoUpdateStatus,
  26. api_userResetPassword,
  27. updateAdmin
  28. } from '../../api';
  29. import AddTeacher from '../../modal/add-teacher';
  30. import TheQrCode from '/src/components/TheQrCode';
  31. import logo from '@/common/images/logo.png';
  32. import { stringifyQuery } from '/src/router';
  33. import AddteacherModel from '../../modal/addteacherModel';
  34. import TeacherGuide from '@/custom-plugins/guide-page/teacher-guide';
  35. import TheEmpty from '/src/components/TheEmpty';
  36. import TheMessageDialog from '/src/components/TheMessageDialog';
  37. import { modalClickMask } from '/src/state';
  38. export default defineComponent({
  39. name: 'school-info',
  40. emits: ['changeTab'],
  41. setup(props, { emit }) {
  42. const user = useUserStore();
  43. const formOptions = reactive({
  44. areaList: [] as any[]
  45. });
  46. const forms = reactive({
  47. name: user.info.schoolInfos?.[0]?.name,
  48. schoolId: user.info.schoolInfos?.[0]?.id,
  49. userId: user.info.id,
  50. logo: user.info.schoolInfos?.[0]?.logo || user.info.avatar,
  51. provinceCode: user.info.schoolInfos?.[0]?.provinceCode || '', // 省份编码
  52. cityCode: user.info.schoolInfos?.[0]?.cityCode || '', // 城市编码
  53. regionCode: user.info.schoolInfos?.[0]?.regionCode || '' // 区域编码
  54. });
  55. const data = reactive({
  56. loading: false,
  57. schoolLoading: true,
  58. dataList: [] as any[],
  59. disabled: true,
  60. changeVisiable: false,
  61. messageLoading: false,
  62. resetVisiable: false,
  63. resetLoading: false,
  64. resetMessage: '',
  65. activeRow: {} as any,
  66. modal: false,
  67. qrModal: false,
  68. oldTecherform: {} as any,
  69. oldLoading: false
  70. });
  71. const showGuide = ref(false);
  72. const columns = (): DataTableColumn[] => {
  73. return [
  74. {
  75. title: '老师姓名',
  76. key: 'nickname',
  77. width: '20%',
  78. render: (row: any) => {
  79. return (
  80. <div
  81. style={{ userSelect: 'all', cursor: 'pointer' }}
  82. onClick={() => copyTo(row.nickname)}>
  83. {row.nickname}
  84. </div>
  85. );
  86. }
  87. },
  88. {
  89. title: '手机号码',
  90. key: 'phone',
  91. width: '20%',
  92. render: (row: any) => {
  93. return (
  94. <div
  95. style={{ userSelect: 'all', cursor: 'pointer' }}
  96. onClick={() => copyTo(row.phone)}>
  97. {row.phone}
  98. </div>
  99. );
  100. }
  101. },
  102. {
  103. title: '性别',
  104. key: 'questionTypeCode',
  105. width: '15%',
  106. render: (row: any) => {
  107. return <div>{row.gender ? '男' : '女'}</div>;
  108. }
  109. },
  110. {
  111. title: '状态',
  112. key: 'statusName',
  113. width: '15%',
  114. render: (row: any) => {
  115. return (
  116. <div>
  117. {row.status === 'ACTIVATION' ? (
  118. <NButton text>启用</NButton>
  119. ) : (
  120. <NButton class={styles.errorBtn} text>
  121. 冻结
  122. </NButton>
  123. )}
  124. </div>
  125. );
  126. }
  127. },
  128. {
  129. title: '操作',
  130. key: 'titleImg',
  131. width: '30%',
  132. render: (row: any) => (
  133. <NSpace>
  134. <NButton
  135. type="primary"
  136. text
  137. onClick={() => {
  138. data.resetMessage = `重置"${row.nickname}"的密码,是否继续?`;
  139. data.resetVisiable = true;
  140. data.activeRow = row;
  141. }}>
  142. 重置密码
  143. </NButton>
  144. {row.status === 'ACTIVATION' ? (
  145. <>
  146. <NButton
  147. disabled={row.jobType === 'ADMIN'}
  148. type="primary"
  149. text
  150. onClick={() => handleChange(row)}>
  151. 冻结
  152. </NButton>
  153. {row.jobType === 'TEACHER' && (
  154. <NButton
  155. type="primary"
  156. text
  157. onClick={() => {
  158. data.changeVisiable = true;
  159. data.activeRow = row;
  160. }}>
  161. 转交管理
  162. </NButton>
  163. )}
  164. </>
  165. ) : (
  166. <NButton
  167. class={styles.errorBtn}
  168. text
  169. onClick={() => handleChange(row)}>
  170. 解冻
  171. </NButton>
  172. )}
  173. </NSpace>
  174. )
  175. }
  176. ];
  177. };
  178. const getAreaList = async () => {
  179. const res = await api_sysAreaQueryAllProvince();
  180. if (res?.code === 200) {
  181. formOptions.areaList = res.data;
  182. }
  183. };
  184. const getList = async () => {
  185. data.loading = true;
  186. const res = await api_teacherPage({
  187. schoolId: user.info.schoolInfos?.[0]?.id,
  188. // jobType: 'TEACHER',
  189. // jobType: 'ADMIN',
  190. page: 1,
  191. rows: 1000
  192. });
  193. data.loading = false;
  194. if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
  195. data.dataList = res.data.rows;
  196. }
  197. setTimeout(() => {
  198. showGuide.value = true;
  199. }, 500);
  200. };
  201. const onChangeManage = async () => {
  202. data.messageLoading = true;
  203. try {
  204. await updateAdmin({
  205. school: forms.schoolId,
  206. newAdminId: data.activeRow.id,
  207. oldAdminId: forms.userId
  208. });
  209. message.success('转交成功');
  210. emit('changeTab', 'person');
  211. await user.getInfo();
  212. } catch {
  213. //
  214. }
  215. data.messageLoading = false;
  216. };
  217. onMounted(() => {
  218. getAreaList();
  219. getList();
  220. });
  221. const dialog = useDialog();
  222. const message = useMessage();
  223. const handleChange = (row: any) => {
  224. const statuStr = row.status === 'LOCKED' ? '解冻' : '冻结';
  225. dialog.warning({
  226. title: '温馨提示',
  227. content: `是否${statuStr}"${row.nickname}"?`,
  228. positiveText: '确定',
  229. negativeText: '取消',
  230. maskClosable: false,
  231. onPositiveClick: async () => {
  232. await api_tenantInfoUpdateStatus({
  233. ids: [row.id],
  234. status: row.status === 'LOCKED' ? 'ACTIVATION' : 'LOCKED'
  235. });
  236. getList();
  237. message.success(statuStr + '成功');
  238. }
  239. });
  240. };
  241. // 重置密码
  242. const onResetPassword = async () => {
  243. data.resetLoading = true;
  244. try {
  245. await api_userResetPassword({
  246. userId: data.activeRow.id,
  247. clientType: 'TEACHER'
  248. });
  249. message.success('重置成功');
  250. data.resetVisiable = false;
  251. } catch {
  252. //
  253. }
  254. data.resetLoading = false;
  255. };
  256. const formRef = ref();
  257. const changeSchoolInfo = () => {
  258. formRef.value?.validate(async (err: any) => {
  259. if (err) {
  260. return;
  261. }
  262. data.schoolLoading = false;
  263. await api_schoolUpdate({ ...user.info.schoolInfos?.[0], ...forms });
  264. data.schoolLoading = true;
  265. message.success('修改成功');
  266. await user.getInfo();
  267. data.disabled = true;
  268. });
  269. };
  270. const registerUrl = () => {
  271. const queryStr = `tenantId=${user.info.schoolInfos?.[0]?.tenantId}&schoolId=${user.info.schoolInfos?.[0]?.id}&schoolName=${user.info.schoolInfos?.[0]?.name}`;
  272. const url =
  273. `${location.origin}/classroom-app/#/teaher-register?` + queryStr;
  274. console.log(url);
  275. return url;
  276. };
  277. const copyTo = (text: string) => {
  278. const input = document.createElement('input');
  279. input.value = text;
  280. document.body.appendChild(input);
  281. input.select();
  282. input.setSelectionRange(0, input.value.length);
  283. document.execCommand('Copy');
  284. document.body.removeChild(input);
  285. message.success('复制成功');
  286. };
  287. return () => (
  288. <div class={styles.schoolInfo}>
  289. <NForm
  290. ref={formRef}
  291. class={styles.formWrap}
  292. model={forms}
  293. style={{ padding: '30px 0' }}
  294. disabled={data.disabled}>
  295. <NSpace size={[30, 20]}>
  296. <div class={styles.logo}>
  297. <NImage
  298. previewDisabled={false}
  299. src={forms.logo}
  300. objectFit="contain"
  301. />
  302. <div
  303. style={{ display: data.disabled ? 'none' : '' }}
  304. class={styles.changeHead}>
  305. 修改头像
  306. {data.schoolLoading && (
  307. <UploadFile
  308. class={[styles.uploadFile]}
  309. cropper
  310. onUpdate:fileList={val => {
  311. forms.logo = val;
  312. }}
  313. />
  314. )}
  315. </div>
  316. </div>
  317. <NFormItem
  318. label="学校名称"
  319. path="name"
  320. showRequireMark={false}
  321. rule={[
  322. { required: true, message: '请填写学校名称', trigger: 'blur' }
  323. ]}>
  324. <NInput
  325. bordered={!data.disabled}
  326. maxlength={20}
  327. v-model:value={forms.name}
  328. />
  329. </NFormItem>
  330. <NFormItem label="城区">
  331. {!data.oldLoading && (
  332. <NCascader
  333. placeholder="请选择城区"
  334. bordered={!data.disabled}
  335. options={formOptions.areaList}
  336. labelField="name"
  337. valueField="code"
  338. childrenField="areas"
  339. checkStrategy="child"
  340. expandTrigger="hover"
  341. defaultValue={
  342. user.info.schoolInfos?.[0]?.regionCode ||
  343. user.info.schoolInfos?.[0]?.cityCode ||
  344. user.info.schoolInfos?.[0]?.provinceCode
  345. }
  346. onUpdate:value={(val: any, option: any, pathValues: any) => {
  347. forms.provinceCode = pathValues[0]?.code;
  348. forms.cityCode = pathValues[1]?.code;
  349. forms.regionCode = pathValues[2]?.code;
  350. }}
  351. />
  352. )}
  353. </NFormItem>
  354. <NFormItem>
  355. {data.disabled ? (
  356. <NSpace class={styles.btnList} align="center" justify="end">
  357. <NButton
  358. class={styles.btn}
  359. color="#f24433"
  360. onClick={() => {
  361. data.oldTecherform = Object.assign({}, forms);
  362. data.disabled = false;
  363. }}>
  364. 修改信息
  365. </NButton>
  366. </NSpace>
  367. ) : (
  368. <NSpace class={styles.btnList} align="center" justify="end">
  369. <NButton
  370. class={styles.btn}
  371. onClick={() => {
  372. Object.assign(forms, data.oldTecherform);
  373. data.disabled = true;
  374. data.oldLoading = true;
  375. nextTick(() => {
  376. data.oldLoading = false;
  377. });
  378. }}>
  379. 取消
  380. </NButton>
  381. <NButton
  382. class={styles.btn}
  383. loading={!data.schoolLoading}
  384. type="primary"
  385. onClick={() => changeSchoolInfo()}>
  386. 完成
  387. </NButton>
  388. </NSpace>
  389. )}
  390. </NFormItem>
  391. </NSpace>
  392. </NForm>
  393. <NSpace style={{ padding: '0 0 32px' }}>
  394. <NButton
  395. focusable={false}
  396. {...{ id: 'teacher-0' }}
  397. type="primary"
  398. renderIcon={() => <NIcon component={<Add />} />}
  399. onClick={() => (data.modal = true)}>
  400. 添加老师
  401. </NButton>
  402. <NButton
  403. focusable={false}
  404. {...{ id: 'teacher-1' }}
  405. type="primary"
  406. onClick={() => (data.qrModal = true)}>
  407. 老师注册二维码
  408. </NButton>
  409. </NSpace>
  410. <NDataTable
  411. v-slots={{
  412. empty: () => <TheEmpty></TheEmpty>
  413. }}
  414. loading={data.loading}
  415. columns={columns()}
  416. data={data.dataList}></NDataTable>
  417. <NModal
  418. maskClosable={modalClickMask}
  419. class={styles.addTeacher}
  420. v-model:show={data.modal}
  421. title="添加老师"
  422. preset="dialog"
  423. showIcon={false}>
  424. <AddTeacher
  425. areaList={formOptions.areaList}
  426. onClose={() => {
  427. data.modal = false;
  428. getList();
  429. }}
  430. />
  431. </NModal>
  432. {data.qrModal ? (
  433. <div v-model:show={data.qrModal} class="n-modal-mask">
  434. <AddteacherModel
  435. onClose={() => {
  436. data.qrModal = false;
  437. }}></AddteacherModel>
  438. </div>
  439. ) : null}
  440. {showGuide.value ? <TeacherGuide></TeacherGuide> : null}
  441. <NModal
  442. maskClosable={modalClickMask}
  443. v-model:show={data.changeVisiable}
  444. preset="card"
  445. class={['modalTitle', styles.removeVisiable1]}
  446. title={'转交管理员'}>
  447. <TheMessageDialog
  448. content={`<p style="text-align: left;">转交管理员后,您当前账号将无法查看和更改学校信息,请确认是否转交给<span style="color: #198CFE">【${data.activeRow.nickname}】</span></p>`}
  449. cancelButtonText="取消"
  450. confirmButtonText="确认"
  451. loading={data.messageLoading}
  452. onClose={() => (data.changeVisiable = false)}
  453. onConfirm={onChangeManage}
  454. />
  455. </NModal>
  456. <NModal
  457. maskClosable={modalClickMask}
  458. v-model:show={data.resetVisiable}
  459. preset="card"
  460. class={['modalTitle', styles.removeVisiable1]}
  461. title={'重置密码'}>
  462. <TheMessageDialog
  463. content={data.resetMessage}
  464. cancelButtonText="取消"
  465. confirmButtonText="确认"
  466. loading={data.resetLoading}
  467. onClose={() => (data.resetVisiable = false)}
  468. onConfirm={onResetPassword}
  469. />
  470. </NModal>
  471. </div>
  472. );
  473. }
  474. });