Browse Source

老师端设置

liushengqiang 1 year ago
parent
commit
118ebb74bc

+ 1 - 0
package.json

@@ -41,6 +41,7 @@
     "umi-request": "^1.4.0",
     "vudio.js": "^1.0.3",
     "vue": "^3.3.4",
+    "vue-qr": "^4.0.9",
     "vue-router": "^4.1.6",
     "vue3-lottie": "^2.7.0",
     "vuedraggable": "^4.1.0",

File diff suppressed because it is too large
+ 1271 - 104
pnpm-lock.yaml


+ 215 - 0
src/components/TheQrCode/index.tsx

@@ -0,0 +1,215 @@
+import { defineComponent } from 'vue';
+import { AwesomeQR } from 'vue-qr/src/lib/awesome-qr';
+function toBoolean(val: any): boolean {
+  if (val === '') return val;
+  return val === 'true' || val == '1';
+}
+function readAsArrayBuffer(url: any) {
+  return new Promise(resolve => {
+    const xhr = new XMLHttpRequest();
+    xhr.responseType = 'blob'; //设定返回数据类型为Blob
+    xhr.onload = function () {
+      const reader = new FileReader();
+      reader.onloadend = function () {
+        resolve(reader.result);
+      };
+      reader.readAsArrayBuffer(xhr.response); //xhr.response就是一个Blob,用FileReader读取
+    };
+    xhr.open('GET', url);
+    xhr.send();
+  });
+}
+
+export default defineComponent({
+  name: 'TheQrCode',
+  props: {
+    text: {
+      type: String,
+      required: true
+    },
+    qid: {
+      type: String
+    },
+    correctLevel: {
+      type: Number,
+      default: 0
+    },
+    size: {
+      type: Number,
+      default: 220
+    },
+    margin: {
+      type: Number,
+      default: 20
+    },
+    colorDark: {
+      type: String,
+      default: '#000000'
+    },
+    colorLight: {
+      type: String,
+      default: '#FFFFFF'
+    },
+    bgSrc: {
+      type: String,
+      default: undefined
+    },
+    background: {
+      type: String,
+      default: 'rgba(0,0,0,0)'
+    },
+    backgroundDimming: {
+      type: String,
+      default: 'rgba(0,0,0,0)'
+    },
+    logoSrc: {
+      type: String,
+      default: undefined
+    },
+    logoBackgroundColor: {
+      type: String,
+      default: 'rgba(255,255,255,1)'
+    },
+    gifBgSrc: {
+      type: String,
+      default: undefined
+    },
+    logoScale: {
+      type: Number,
+      default: 0.2
+    },
+    logoMargin: {
+      type: Number,
+      default: 0
+    },
+    logoCornerRadius: {
+      type: Number,
+      default: 8
+    },
+    whiteMargin: {
+      type: [Boolean, String],
+      default: true
+    },
+    dotScale: {
+      type: Number,
+      default: 1
+    },
+    autoColor: {
+      type: [Boolean, String],
+      default: true
+    },
+    binarize: {
+      type: [Boolean, String],
+      default: false
+    },
+    binarizeThreshold: {
+      type: Number,
+      default: 128
+    },
+    callback: {
+      type: Function,
+      default: function () {
+        return undefined;
+      }
+    },
+    bindElement: {
+      type: Boolean,
+      default: true
+    },
+    backgroundColor: {
+      type: String,
+      default: '#FFFFFF'
+    },
+    components: {
+      default: function () {
+        return {
+          data: {
+            scale: 1
+          },
+          timing: {
+            scale: 1,
+            protectors: false
+          },
+          alignment: {
+            scale: 1,
+            protectors: false
+          },
+          cornerAlignment: {
+            scale: 1,
+            protectors: true
+          }
+        };
+      }
+    }
+  },
+  data() {
+    return {
+      imgUrl: '' as any
+    };
+  },
+  watch: {
+    $props: {
+      deep: true,
+      handler() {
+        this.main();
+      }
+    }
+  },
+  mounted() {
+    this.main();
+  },
+  methods: {
+    async main() {
+      // const that = this;
+      if (this.gifBgSrc) {
+        const gifImg = await readAsArrayBuffer(this.gifBgSrc);
+        const logoImg = this.logoSrc;
+
+        this.render(undefined, logoImg, gifImg);
+        return;
+      }
+      const bgImg = this.bgSrc;
+      const logoImg = this.logoSrc;
+      this.render(bgImg, logoImg);
+    },
+    async render(img: any, logoImg: any, gifBgSrc?: any) {
+      console.log(img, logoImg, gifBgSrc);
+      new AwesomeQR({
+        gifBackground: gifBgSrc,
+        text: this.text,
+        size: this.size,
+        // margin: this.margin,
+        // colorDark: this.colorDark,
+        // colorLight: this.colorLight,
+        // backgroundColor: this.backgroundColor,
+        // backgroundImage: img,
+        // // backgroundDimming: this.backgroundDimming,
+        // // // logoImage: logoImg + '?' + new Date().getTime(),
+        // logoScale: this.logoScale,
+        // logoBackgroundColor: this.logoBackgroundColor,
+        // correctLevel: this.correctLevel,
+        // logoMargin: this.logoMargin,
+        // logoCornerRadius: this.logoCornerRadius,
+        // whiteMargin: toBoolean(this.whiteMargin),
+        // dotScale: this.dotScale,
+        // autoColor: toBoolean(this.autoColor),
+        // components: this.components
+      })
+        .draw()
+        .then((dataUri: any) => {
+          console.log('🚀 ~ dataUri:', dataUri);
+          this.imgUrl = dataUri;
+          this.callback && this.callback(dataUri, this.qid);
+        });
+    }
+  },
+  render() {
+    return (
+      <>
+        {this.bindElement && this.imgUrl && (
+          <img style="display: inline-block" src={this.imgUrl} />
+        )}
+      </>
+    );
+  }
+});

+ 43 - 0
src/views/setting/api.ts

@@ -0,0 +1,43 @@
+import request from '@/utils/request';
+
+/**
+ * 老师列表
+ */
+export const api_teacherPage = (params: any) => {
+  return request.post('/edu-app/teacher/page', {
+    data: params
+  });
+};
+
+/**
+ * 老师新增
+ */
+export const api_teacherAdd = (params: object) => {
+  return request.post('/edu-app/teacher/add', {
+    data: params
+  });
+};
+/**
+ * 老师停用/启用
+ */
+export const api_tenantInfoUpdateStatus = (params: object) => {
+  return request.post('/edu-app/teacher/updateStatus', {
+    data: params
+  });
+};
+/**
+ * 老师重置密码
+ */
+export const api_userResetPassword = (params: object) => {
+  return request.post('/edu-app/user/resetPassword', {
+    data: params,
+    requestType: 'form'
+  });
+};
+
+/** 分页查询机构 */
+export const api_tenantInfoPage = (params: object) => {
+  return request.post('/edu-app/tenantInfo/page', {
+    data: params
+  });
+};

+ 37 - 5
src/views/setting/components/personInfo.tsx

@@ -11,7 +11,8 @@ import {
   NSelect,
   NSpace,
   SelectOption,
-  useMessage
+  useMessage,
+  NModal
 } from 'naive-ui';
 import headerD from '@/views/home/images/headerD.png';
 import defultHeade from '@/components/layout/images/teacherIcon.png';
@@ -20,6 +21,8 @@ import maleIcon from '../images/maleIcon.png';
 import { state } from '/src/state';
 import { useUserStore } from '/src/store/modules/users';
 import { api_teacherUpdate } from '/src/api/user';
+import UploadFile from '/src/components/upload-file';
+import ForgotPassword from '../modal/forgotPassword';
 export default defineComponent({
   name: 'setting-personInfo',
   setup(props, { emit, attrs }) {
@@ -38,10 +41,12 @@ export default defineComponent({
       gender: userStore.info.gender,
       schoolId: userStore.info.schoolInfos?.[0]?.id,
       tenantId: userStore.info.schoolInfos?.[0]?.tenantId,
-      id: userStore.info.id
+      id: userStore.info.id,
+      avatar: userStore.info.avatar
     });
     const data = reactive({
-      disabled: true
+      disabled: true,
+      openChangePwd: false
     });
 
     const handleSave = () => {
@@ -67,7 +72,19 @@ export default defineComponent({
             <NImage
               previewDisabled
               class={styles.defultHeade}
-              src={defultHeade}></NImage>
+              src={teacherForm.avatar || defultHeade}></NImage>
+            <div
+              style={{ display: data.disabled ? 'none' : '' }}
+              class={[styles.defultHeade, styles.changeHead]}>
+              修改头像
+              <UploadFile
+                class={[styles.uploadFile]}
+                cropper
+                onUpdate:fileList={val => {
+                  teacherForm.avatar = val;
+                }}
+              />
+            </div>
           </div>
           <div class={styles.headerInfo}>
             <p class={styles.headerTitle}>
@@ -133,7 +150,10 @@ export default defineComponent({
         </div>
         {data.disabled ? (
           <NSpace class={styles.btnList} align="center" justify="end">
-            <NButton class={styles.btn} color="#198cfe">
+            <NButton
+              class={styles.btn}
+              color="#198cfe"
+              onClick={() => (data.openChangePwd = true)}>
               修改密码
             </NButton>
             <NButton
@@ -156,6 +176,18 @@ export default defineComponent({
             </NButton>
           </NSpace>
         )}
+        <NModal
+          class={styles.changePwdModal}
+          v-model:show={data.openChangePwd}
+          preset="dialog"
+          showIcon={false}
+          title="修改密码">
+          <ForgotPassword
+            onClose={() => {
+              data.openChangePwd = false;
+            }}
+          />
+        </NModal>
       </div>
     );
   }

+ 153 - 0
src/views/setting/components/schoolInfo/index.module.less

@@ -0,0 +1,153 @@
+.logo {
+    position: relative;
+    width: 100Px;
+    height: 100Px;
+
+    .changeHead {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        background-color: rgba(0, 0, 0, .7);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 16px;
+        color: #fff;
+        font-weight: 600;
+        transition: opacity .3s;
+        border-radius: 50%;
+        cursor: pointer;
+        opacity: 0;
+
+        &:hover {
+            opacity: 1;
+        }
+    }
+
+    .uploadFile {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        opacity: 0;
+    }
+}
+
+.input {
+    border-radius: 8px;
+    min-height: 43px;
+    min-width: 240px;
+}
+
+.schoolInfo {
+    :global {
+        .n-form-item .n-form-item-label {
+            color: #777;
+        }
+
+        .n-button {
+            border-radius: 8px;
+        }
+
+        .n-data-table .n-data-table-th {
+
+            background: #F7F7F8;
+            color: rgba(113, 113, 114, 1) !important;
+            border: none;
+            min-height: 54Px;
+            font-size: 15Px;
+        }
+
+        .n-data-table.n-data-table--bordered .n-data-table-wrapper {
+            border: none;
+        }
+
+        .n-data-table-tr .n-data-table-td .n-button__content,
+        .n-data-table .n-data-table-td {
+            font-weight: bold;
+            font-size: 15Px;
+        }
+    }
+
+    .errorBtn {
+        :global {
+            .n-button__content {
+                color: #FF4D4F;
+            }
+        }
+    }
+}
+
+.addTeacher {
+    padding: 0;
+    border-radius: 16Px;
+    overflow: hidden;
+    min-width: 456Px;
+
+    :global {
+        .n-dialog__close{
+            transform: translate(0, 3Px);
+        }
+        .n-dialog__title {
+            min-height: 70Px;
+            justify-content: center;
+            background: #F5F6FA;
+        }
+
+        .n-form {
+            padding: 20Px 0;
+        }
+
+        .n-input {
+            min-height: 53Px;
+            border-radius: 8Px;
+
+            .n-input__input-el {
+                height: 100%;
+            }
+        }
+
+        .n-form-item-blank {
+            padding-right: 30Px;
+            min-height: 53Px;
+        }
+
+        .genderBtn {
+            min-width: 84Px;
+            min-height: 37Px;
+            border-radius: 8Px;
+        }
+
+        .n-form-item-label {
+            color: #777;
+            padding: 0;
+            font-size: 18Px;
+        }
+
+        .n-form-item-label__text {
+            min-height: 53Px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+
+        .nalert {
+            padding: 0 30Px;
+            .n-alert{
+                background: #FFE8E8;
+                text-align: center;
+            }
+            .n-alert-body .n-alert-body__content{
+                color: #EA4132;
+            }
+        }
+        .actionBtn{
+            width: 156Px;
+            height: 47Px;
+            font-size: 18Px;
+        }
+    }
+}

+ 250 - 0
src/views/setting/components/schoolInfo/index.tsx

@@ -0,0 +1,250 @@
+import {
+  DataTableColumn,
+  NButton,
+  NDataTable,
+  NForm,
+  NFormItem,
+  NIcon,
+  NImage,
+  NInput,
+  NModal,
+  NSpace,
+  useDialog,
+  useMessage
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import { useUserStore } from '/src/store/modules/users';
+import UploadFile from '/src/components/upload-file';
+import { Add } from '@vicons/ionicons5';
+import {
+  api_teacherPage,
+  api_tenantInfoUpdateStatus,
+  api_userResetPassword
+} from '../../api';
+import AddTeacher from '../../modal/add-teacher';
+import TheQrCode from '/src/components/TheQrCode';
+
+export default defineComponent({
+  name: 'school-info',
+  setup() {
+    const user = useUserStore();
+
+    const forms = reactive({
+      schoolName: user.info.schoolInfos?.[0]?.name,
+      avatar: user.info.schoolInfos?.[0]?.avatar || user.info.avatar
+    });
+    const data = reactive({
+      loading: false,
+      dataList: [] as any[],
+
+      modal: false,
+      qrModal: false
+    });
+
+    const columns = (): DataTableColumn[] => {
+      return [
+        {
+          title: '老师姓名',
+          key: 'nickname'
+        },
+        {
+          title: '手机号码',
+          key: 'phone'
+        },
+        {
+          title: '性别',
+          key: 'questionTypeCode',
+          render: (row: any) => {
+            return <div>{row.gender ? '男' : '女'}</div>;
+          }
+        },
+        {
+          title: '状态',
+          key: 'statusName',
+          render: (row: any) => {
+            return (
+              <div>
+                {row.status === 'ACTIVATION' ? (
+                  <NButton text>{row.statusName}</NButton>
+                ) : (
+                  <NButton class={styles.errorBtn} text>
+                    {row.statusName}
+                  </NButton>
+                )}
+              </div>
+            );
+          }
+        },
+        {
+          title: '操作',
+          key: 'titleImg',
+          render: (row: any) => (
+            <NSpace>
+              <NButton
+                type="primary"
+                quaternary
+                size="small"
+                onClick={() => onResetPassword(row)}>
+                重置密码
+              </NButton>
+
+              {row.status === 'ACTIVATION' ? (
+                <NButton
+                  type="primary"
+                  quaternary
+                  size="small"
+                  onClick={() => handleChange(row)}>
+                  冻结
+                </NButton>
+              ) : (
+                <NButton
+                  class={styles.errorBtn}
+                  quaternary
+                  size="small"
+                  onClick={() => handleChange(row)}>
+                  解冻
+                </NButton>
+              )}
+            </NSpace>
+          )
+        }
+      ];
+    };
+
+    const getList = async () => {
+      data.loading = true;
+      const res = await api_teacherPage({
+        schoolId: user.info.schoolInfos?.[0]?.id,
+        // jobType: 'TEACHER',
+        // jobType: 'ADMIN',
+        page: 1,
+        rows: 1000
+      });
+      data.loading = false;
+      if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
+        data.dataList = res.data.rows;
+      }
+    };
+    onMounted(() => {
+      getList();
+    });
+
+    const dialog = useDialog();
+    const message = useMessage();
+    const handleChange = (row: any) => {
+      const statuStr = row.status === 'LOCKED' ? '解冻' : '冻结';
+      dialog.warning({
+        title: '温馨提示',
+        content: `是否${statuStr}"${row.nickname}"?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          await api_tenantInfoUpdateStatus({
+            ids: [row.id],
+            status: row.status === 'LOCKED' ? 'ACTIVATION' : 'LOCKED'
+          });
+          getList();
+          message.success(statuStr + '成功');
+        }
+      });
+    };
+    // 重置密码
+    const onResetPassword = (row: any): void => {
+      dialog.warning({
+        title: '警告',
+        content: `重置"${row.nickname}"的密码,是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          await api_userResetPassword({
+            userId: row.id,
+            password: 'ktyq' + row.phone.substr(7),
+            clientType: 'TEACHER'
+          });
+          message.success('重置成功');
+        }
+      });
+    };
+    return () => (
+      <div class={styles.schoolInfo}>
+        <NSpace wrapItem={false} align="center">
+          <div class={styles.logo}>
+            <NImage
+              previewDisabled
+              width={100}
+              height={100}
+              src={forms.avatar}
+            />
+            <div class={styles.changeHead}>
+              修改头像
+              <UploadFile
+                class={[styles.uploadFile]}
+                cropper
+                onUpdate:fileList={val => {
+                  forms.avatar = val;
+                }}
+              />
+            </div>
+          </div>
+          <NForm model={forms} style={{ paddingTop: '30px' }}>
+            <NFormItem
+              label="学校名称"
+              path="schoolName"
+              showRequireMark={false}
+              rule={[
+                { required: true, message: '请填写学习名称', trigger: 'blur' }
+              ]}>
+              <NInput
+                class={styles.input}
+                maxlength={20}
+                v-model:value={forms.schoolName}
+              />
+            </NFormItem>
+          </NForm>
+        </NSpace>
+
+        <NSpace style={{ padding: '32px 0' }}>
+          <NButton
+            type="primary"
+            renderIcon={() => <NIcon component={<Add />} />}
+            onClick={() => (data.modal = true)}>
+            添加老师
+          </NButton>
+          <NButton type="primary" onClick={() => (data.qrModal = true)}>
+            老师注册二维码
+          </NButton>
+        </NSpace>
+
+        <NDataTable
+          loading={data.loading}
+          columns={columns()}
+          data={data.dataList}></NDataTable>
+
+        <NModal
+          class={styles.addTeacher}
+          v-model:show={data.modal}
+          title="添加老师"
+          preset="dialog"
+          showIcon={false}>
+          <AddTeacher
+            onClose={() => {
+              data.modal = false;
+              getList();
+            }}
+          />
+        </NModal>
+
+        <NModal
+          v-model:show={data.qrModal}
+          title="二维码"
+          preset="dialog"
+          showIcon={false}>
+          <div style={{ textAlign: 'center' }}>
+            <TheQrCode text="https://www.baidu.com" size={300} />
+          </div>
+        </NModal>
+      </div>
+    );
+  }
+});

BIN
src/views/setting/images/closeEye.png


BIN
src/views/setting/images/openEye.png


+ 56 - 2
src/views/setting/index.module.less

@@ -67,6 +67,27 @@
         top: 13px;
         left: 61px;
       }
+
+      .changeHead {
+        background-color: rgba(0, 0, 0, .7);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 16px;
+        color: #fff;
+        font-weight: 600;
+        transition: opacity .3s;
+        cursor: pointer;
+      }
+
+      .uploadFile {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        opacity: 0;
+      }
     }
 
     .headerInfo {
@@ -144,14 +165,47 @@
 }
 
 :global {
-  .option.n-base-select-option{
+  .option.n-base-select-option {
     justify-content: center;
   }
+
   .option.n-base-select-option.n-base-select-option--pending::before {
     background-color: #198cfe !important;
   }
-  .option.n-base-select-option.n-base-select-option--pending .n-base-select-option__content{
+
+  .option.n-base-select-option.n-base-select-option--pending .n-base-select-option__content {
     color: #fff !important;
     text-align: center;
   }
+}
+
+.changePwdModal {
+  border-radius: 16px;
+  .wrap{
+    padding: 12px 0;
+    :global{
+      .n-input{
+        border-radius: 8px;
+      }
+      .n-input .n-input__input-el{
+        height: 53px;
+      }
+      .n-button.n-button--disabled{
+        background: #aaa;
+      }
+    }
+  }
+  .sendMsg{
+    height: 53px;
+    min-width: 108px;
+  }
+  .pwdIcon {
+    width: 24px;
+    height: 24px;
+    cursor: pointer;
+  }
+  .submitBtm{
+    width: 45%;
+    height: 47px;
+  }
 }

+ 4 - 1
src/views/setting/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref } from 'vue';
 import styles from './index.module.less';
 import { NTabs, NTabPane } from 'naive-ui';
 import PersonInfo from './components/personInfo';
+import SchoolInfo from './components/schoolInfo/index';
 export default defineComponent({
   name: 'base-setting',
   setup(props, { emit, attrs }) {
@@ -18,7 +19,9 @@ export default defineComponent({
           <NTabPane name="person" tab="个人信息">
             <PersonInfo></PersonInfo>
           </NTabPane>
-          <NTabPane name="school" tab="学校设置"></NTabPane>
+          <NTabPane name="school" tab="学校设置">
+            <SchoolInfo />
+          </NTabPane>
         </NTabs>
       </div>
     );

+ 169 - 0
src/views/setting/modal/add-teacher/index.tsx

@@ -0,0 +1,169 @@
+import {
+  NForm,
+  NFormItem,
+  NInput,
+  NSpace,
+  NButton,
+  useMessage,
+  NAlert
+} from 'naive-ui';
+import { defineComponent, reactive, ref } from 'vue';
+import { api_teacherAdd } from '../../api';
+import { useUserStore } from '/src/store/modules/users';
+export default defineComponent({
+  name: 'teacher-operation',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const user = useUserStore();
+    const forms = reactive({
+      tenantId: user.info.schoolInfos?.[0]?.tenantId,
+      phone: null,
+      schoolId: user.info.schoolInfos?.[0]?.id,
+      nickname: null,
+      gender: 0
+    });
+    const btnLoading = ref(false);
+    const formsRef = ref();
+    const message = useMessage();
+    // 提交记录
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return;
+        try {
+          btnLoading.value = true;
+          await api_teacherAdd({ ...forms });
+          message.success('添加成功');
+        } catch (e) {
+          console.log(e);
+        }
+        setTimeout(() => {
+          btnLoading.value = false;
+          emit('close');
+        }, 100);
+      });
+    };
+
+    // const formatParentAreaId = (id: any, list: any, ids = [] as any) => {
+    //   for (const item of list) {
+    //     if (item.code === id) {
+    //       return [...ids, id];
+    //     }
+    //     if (item.areas && item.areas.length > 0) {
+    //       const cIds: any = formatParentAreaId(id, item.areas, [
+    //         ...ids,
+    //         item.code
+    //       ]);
+    //       if (cIds.includes(id)) {
+    //         return cIds;
+    //       }
+    //     }
+    //   }
+    //   return ids;
+    // };
+
+    return () => (
+      <div>
+        <NForm
+          model={forms}
+          ref={formsRef}
+          showRequireMark={false}
+          label-placement="left"
+          label-width="126">
+          <NFormItem
+            label="老师姓名"
+            path="nickname"
+            rule={[
+              {
+                required: true,
+                message: '请输入老师姓名'
+              }
+            ]}>
+            <NInput
+              v-model:value={forms.nickname}
+              placeholder="请输入老师姓名"
+              clearable
+              maxlength={14}
+            />
+          </NFormItem>
+          <NFormItem
+            label="老师性别"
+            path="gender"
+            rule={[
+              {
+                required: true,
+                message: '请选择老师性别'
+              }
+            ]}>
+            <NSpace>
+              <n-button
+                class="genderBtn"
+                type={forms.gender ? 'info' : undefined}
+                tertiary={forms.gender ? false : true}
+                onClick={() => {
+                  forms.gender = 1;
+                }}>
+                男
+              </n-button>
+              <n-button
+                class="genderBtn"
+                type={!forms.gender ? 'info' : undefined}
+                tertiary={!forms.gender ? false : true}
+                onClick={() => {
+                  forms.gender = 0;
+                }}>
+                女
+              </n-button>
+            </NSpace>
+          </NFormItem>
+          <NFormItem
+            label="手机号"
+            path="phone"
+            rule={[
+              {
+                required: true,
+                message: '手机号码',
+                trigger: 'blur'
+              },
+              {
+                pattern:
+                  /^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\d{8}$/,
+                message: '请输入正确的手机号',
+                trigger: 'blur'
+              }
+            ]}>
+            <NInput
+              maxlength={11} 
+              v-model:value={forms.phone}
+              placeholder="请输入手机号"
+              clearable
+            />
+          </NFormItem>
+
+          <div class="nalert">
+            <NAlert type="error" showIcon={false} bordered={false}>
+              默认密码为ktyq+手机号后四位
+            </NAlert>
+          </div>
+        </NForm>
+
+        <NSpace style={{ padding: '20px 0 32px 0' }} justify="center">
+          <NButton
+            class="actionBtn"
+            round
+            type="default"
+            onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            class="actionBtn"
+            round
+            type="primary"
+            onClick={() => onSubmit()}
+            loading={btnLoading.value}>
+            确定
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 220 - 0
src/views/setting/modal/forgotPassword.tsx

@@ -0,0 +1,220 @@
+import { defineComponent, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import openEye from '../images/openEye.png';
+import closeEye from '../images/closeEye.png';
+import {
+  useMessage,
+  NForm,
+  NFormItem,
+  NInput,
+  NButton,
+  NInputGroup,
+  NSpace
+} from 'naive-ui';
+import { useRoute, useRouter } from 'vue-router';
+import { PageEnum } from '/src/enums/pageEnum';
+import { storage } from '@/utils/storage';
+import { useUserStore } from '/src/store/modules/users';
+import { sendSms, updatePassword } from '../../login/api';
+interface FormState {
+  mobile: string;
+  password: string;
+  grant_type: string;
+  loginType: string;
+  client_id: string;
+  client_secret: string;
+}
+
+export default defineComponent({
+  name: 'forgotPassword',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const router = useRouter();
+    const formRef = ref();
+    const message = useMessage();
+    const loading = ref(false);
+    const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
+    const showPwd = ref(false);
+    const userStore = useUserStore();
+    const formInline = reactive({
+      mobile: '',
+      password: '',
+      code: '',
+      isCaptcha: true
+    });
+    const isDisabledCode = ref(false);
+    const starTimer = ref(60);
+    const codeName = '发送短信';
+
+    const handleSubmit = async () => {
+      formRef.value.validate(async (errors: any) => {
+        if (!errors) {
+          message.loading('修改中...');
+          loading.value = true;
+          try {
+            await updatePassword({
+              ...formInline,
+              clientType: 'TEACHER'
+            });
+            message.success('修改成功');
+            loading.value = false;
+            emit('close');
+            setTimeout(() => {
+              userStore.logout();
+              history.go(0);
+            }, 500);
+            return false;
+          } catch (e: any) {
+            loading.value = false;
+            message.error(e.msg);
+            return false;
+          }
+        }
+      });
+      return false;
+    };
+    const sendMessage = () => {
+      formRef.value?.validate(
+        (errors: any) => {
+          if (errors) {
+            return;
+          }
+          checkTimeOut();
+          sendSms({
+            clientId: 'cooleshow-teacher',
+            mobile: formInline.mobile,
+            type: 'PASSWORD'
+          });
+        },
+        (rule: any) => {
+          return rule.key === 'a';
+        }
+      );
+    };
+
+    const checkTimeOut = () => {
+      if (isDisabledCode.value) {
+        return;
+      }
+      isDisabledCode.value = true;
+      const tiemr = setInterval(() => {
+        starTimer.value--;
+        console.log(starTimer.value);
+        if (starTimer.value <= 0) {
+          isDisabledCode.value = false;
+          clearInterval(tiemr);
+        }
+      }, 1000);
+    };
+    return () => (
+      <>
+        <div class={styles.wrap}>
+          <NForm
+            ref={formRef}
+            label-placement="left"
+            size="large"
+            model={formInline}>
+            <NFormItem
+              path="mobile"
+              rule={[
+                {
+                  key: 'a',
+                  required: true,
+                  message: '请输入手机号',
+                  trigger: 'blur'
+                },
+                {
+                  key: 'a',
+                  pattern: /^1[3456789]\d{9}$/,
+                  message: '手机号格式不正确',
+                  trigger: 'blur'
+                }
+              ]}>
+              <NInput
+                maxlength={11}
+                v-model:value={formInline.mobile}
+                placeholder="请输入手机号"></NInput>
+            </NFormItem>
+            <NFormItem
+              path="code"
+              rule={[
+                { required: true, message: '请输入验证码', trigger: 'blur' }
+              ]}>
+              <NInputGroup>
+                <NInput
+                  v-model:value={formInline.code}
+                  type="text"
+                  maxlength={6}
+                  placeholder="请输入验证码"
+                  inputProps={{ autocomplete: 'off' }}
+                  class={styles.sendInput}></NInput>
+                <NButton
+                  type="primary"
+                  class={styles.sendMsg}
+                  disabled={isDisabledCode.value}
+                  bordered={false}
+                  onClick={() => sendMessage()}>
+                  {isDisabledCode.value ? starTimer.value : codeName}
+                </NButton>
+              </NInputGroup>
+            </NFormItem>
+            <NFormItem
+              path="password"
+              rule={[
+                {
+                  required: true,
+                  message: '请输入密码',
+                  trigger: 'blur'
+                },
+                {
+                  pattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/,
+                  message: '密码为6-20位数字和字母组合',
+                  trigger: 'blur'
+                }
+              ]}>
+              <NInput
+                v-model:value={formInline.password}
+                type="password"
+                showPasswordOn="click"
+                placeholder="请输入密码"
+                inputProps={{ autocomplete: 'off' }}
+                class={[showPwd.value ? '' : styles['no-pwd']]}>
+                {{
+                  'password-visible-icon': () => (
+                    <img src={openEye} class={styles.pwdIcon} />
+                  ),
+                  'password-invisible-icon': () => (
+                    <img src={closeEye} class={styles.pwdIcon} />
+                  )
+                }}
+              </NInput>
+            </NFormItem>
+          </NForm>
+        </div>
+        <NSpace
+          justify="space-around"
+          style={{ width: '100%' }}
+          wrap={false}
+          wrapItem={false}>
+          <NButton
+            class={[styles.submitBtm, styles.submitForgoBtm]}
+            onClick={() => emit('close')}
+            size="large"
+            round
+            disabled={loading.value}>
+            取消
+          </NButton>
+          <NButton
+            class={[styles.submitBtm, styles.submitForgoBtm]}
+            type="primary"
+            onClick={handleSubmit}
+            size="large"
+            round
+            disabled={loading.value}>
+            确认修改
+          </NButton>
+        </NSpace>
+      </>
+    );
+  }
+});

Some files were not shown because too many files changed in this diff