Sfoglia il codice sorgente

初始化auth模块

lianghongjie 1 anno fa
commit
e364782624

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+/dist
+/lib
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 20 - 0
package.json

@@ -0,0 +1,20 @@
+{
+  "name": "queenjs-modules-shared",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "http://124.70.149.18:10880/lianghj/queenjs-modules-shared.git"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "ant-design-vue": "^3.2.20",
+    "queenjs": "^1.0.0-beta.78",
+    "vue": "^3.3.4"
+  }
+}

+ 37 - 0
packages/auth/components/Forget/index.tsx

@@ -0,0 +1,37 @@
+import { css } from "@linaria/core";
+import { queenApi } from "queenjs";
+import { defineComponent } from "vue";
+import LoginLayout from "../components/LoginLayout";
+import LoginTabs from "../components/LoginTabs";
+import ResetForm from "../components/ResetForm";
+
+export default defineComponent(() => {
+  return () => (
+    <LoginLayout>
+      <div class={styleRoot}>
+        <LoginTabs options={[{ label: "忘记密码", value: "" }]} />
+        <ResetForm />
+        <a class="backLoginLink" onClick={() => queenApi.router.back()}>
+          返回登录
+        </a>
+      </div>
+    </LoginLayout>
+  );
+});
+
+const styleRoot = css`
+  width: 490px;
+  padding: 44px;
+  background-color: @inf-component-bg;
+
+  .backLoginLink {
+    display: flex;
+    margin: 0 auto;
+    width: 120px;
+    align-items: center;
+    color: @inf-primary-color;
+    svg {
+      font-size: 30px;
+    }
+  }
+`;

+ 11 - 0
packages/auth/components/Login/index.tsx

@@ -0,0 +1,11 @@
+import { defineComponent } from "vue";
+import LoginForm from "../components/LoginForm";
+import LoginLayout from "../components/LoginLayout";
+
+export default defineComponent(() => {
+  return () => (
+    <LoginLayout>
+      <LoginForm />
+    </LoginLayout>
+  );
+});

+ 11 - 0
packages/auth/components/Register/index.tsx

@@ -0,0 +1,11 @@
+import { defineComponent } from "vue";
+import LoginLayout from "../components/LoginLayout";
+import RegisterForm from "../components/RegisterForm";
+
+export default defineComponent(() => {
+  return () => (
+    <LoginLayout>
+      <RegisterForm />
+    </LoginLayout>
+  );
+});

+ 59 - 0
packages/auth/components/components/LoginBox.tsx

@@ -0,0 +1,59 @@
+import { css } from "@linaria/core";
+import { defineComponent } from "vue";
+import { useAuth } from "../..";
+
+export default defineComponent({
+  setup(props, { slots }) {
+    const auth = useAuth();
+    const bannerStyle = auth.helper.createBgStyle(
+      auth.config.banner?.background
+    );
+    return () => (
+      <div class={containerRoot}>
+        {auth.config.banner && (
+          <section class="login-banner" style={bannerStyle}>
+            <div class="banner-content">
+              <pre class="content-title">{auth.config.banner.title}</pre>
+              <pre class="content-subTit">{auth.config.banner.subTitle}</pre>
+            </div>
+          </section>
+        )}
+        {slots.default?.()}
+      </div>
+    );
+  },
+});
+
+const containerRoot = css`
+  display: flex;
+  border-radius: 8px;
+  background: #fff;
+  overflow: hidden;
+
+  .login-banner {
+    width: 400px;
+    color: @inf-text-color;
+
+    .banner-content {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0.3);
+    }
+
+    pre {
+      margin: 24px;
+      font-family: Arial, Helvetica, sans-serif;
+    }
+
+    .content-title {
+      font-size: 24px;
+      color: #fff;
+    }
+    .content-subTit {
+      font-size: 16px;
+      color: #ccc;
+    }
+  }
+`;

+ 153 - 0
packages/auth/components/components/LoginForm.tsx

@@ -0,0 +1,153 @@
+import { css } from "@linaria/core";
+import { Button, Form, Input } from "ant-design-vue";
+import { defineComponent, reactive } from "vue";
+import { useAuth } from "../..";
+import LoginTabs from "./LoginTabs";
+
+const loginTabs = {
+  default: [{ label: "用户登录", value: "default" }],
+  company: [
+    {
+      label: "个人登录",
+      value: "default",
+    },
+    {
+      label: "企业登录",
+      value: "company",
+    },
+  ],
+};
+
+const layout = {
+  wrapperCol: { span: 24 },
+};
+const tailLayout = {
+  wrapperCol: { span: 24 },
+};
+
+export default defineComponent({
+  setup() {
+    const auth = useAuth();
+
+    const state = reactive({
+      loading: false,
+      loginType: "default",
+    });
+
+    const formState = reactive({
+      phone: "",
+      password: "",
+    });
+
+    const validatePass = async (rule: any, value: string) => {
+      if (!value) {
+        return Promise.reject("请输入正确的账号");
+      } else {
+        return Promise.resolve();
+      }
+    };
+
+    const rules = reactive({
+      phone: [
+        {
+          required: true,
+          validator: validatePass,
+          trigger: "change",
+        },
+      ],
+      password: [
+        { required: true, message: "密码不能为空", trigger: "change" },
+        {
+          min: 1,
+          max: 18,
+          message: "密码长度为6~18位字符",
+          trigger: "change",
+        },
+      ],
+    });
+
+    const { validate, validateInfos } = Form.useForm(formState, rules);
+
+    async function submit() {
+      try {
+        state.loading = true;
+        const values = await validate();
+        await auth.actions.pwdLogin(values);
+      } finally {
+        state.loading = false;
+      }
+    }
+
+    return () => (
+      <div class={styleRoot}>
+        <LoginTabs
+          options={
+            auth.config.needCompanyLogin ? loginTabs.company : loginTabs.default
+          }
+          onChange={({ value }) => (state.loginType = value)}
+        />
+        <Form {...layout} name="basic">
+          <Form.Item name="phone" {...validateInfos.phone}>
+            <Input
+              placeholder="请输入手机号码"
+              allowClear
+              v-model={[formState.phone, "value"]}
+              maxlength={30}
+            />
+          </Form.Item>
+          <Form.Item name="password" {...validateInfos.password}>
+            <Input.Password
+              placeholder="请输入密码"
+              allowClear
+              v-model={[formState.password, "value"]}
+              maxlength={12}
+            />
+          </Form.Item>
+          <Form.Item {...tailLayout}>
+            <Button
+              htmlType="submit"
+              block
+              type="primary"
+              size="large"
+              shape="round"
+              loading={state.loading}
+              onClick={submit}
+            >
+              立即登录
+            </Button>
+          </Form.Item>
+        </Form>
+        <div class="login-more">
+          {auth.config.needRegister ? (
+            <router-link to="/register">注册账号</router-link>
+          ) : (
+            <div></div>
+          )}
+          {auth.config.needResetPwd ? (
+            <router-link to="/forget">忘记密码</router-link>
+          ) : (
+            <div></div>
+          )}
+        </div>
+      </div>
+    );
+  },
+});
+
+const styleRoot = css`
+  width: 490px;
+  padding: 44px;
+  background-color: @inf-component-bg;
+
+  .login-more {
+    display: flex;
+    justify-content: space-between;
+  }
+
+  .ant-input-affix-wrapper {
+    background-color: @inf-input-bg;
+    input {
+      background-color: @inf-input-bg;
+    }
+  }
+`;

+ 27 - 0
packages/auth/components/components/LoginLayout.tsx

@@ -0,0 +1,27 @@
+import { css } from "@linaria/core";
+import { defineComponent } from "vue";
+import { useAuth } from "../..";
+import LoginBox from "./LoginBox";
+
+export default defineComponent({
+  setup(props, { slots }) {
+    const auth = useAuth();
+    const layoutStyle = auth.helper.createBgStyle(
+      auth.config.layout?.background
+    );
+
+    return () => (
+      <div class={pageRoot} style={layoutStyle}>
+        <LoginBox>{slots.default?.()}</LoginBox>
+      </div>
+    );
+  },
+});
+
+const pageRoot = css`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100vh;
+  background-color: @inf-layout-bg;
+`;

+ 57 - 0
packages/auth/components/components/LoginTabs.tsx

@@ -0,0 +1,57 @@
+import { css } from "@linaria/core";
+import { defineComponent, reactive } from "vue";
+import { array } from "vue-types";
+
+export default defineComponent({
+  props: {
+    options: array<{ label: string; value: any }>(),
+  },
+  emits: ["change"],
+  setup(props, { emit }) {
+    const state = reactive({
+      tabIndex: 0,
+    });
+
+    function onChange(item: any, i: number) {
+      state.tabIndex = i;
+      emit("change", item);
+    }
+
+    return () => (
+      <div class={styleRoot}>
+        {props.options?.map((item, i) => {
+          return (
+            <div
+              class={["tabItem", state.tabIndex === i && "active"]}
+              key={i}
+              onClick={() => onChange(item, i)}
+            >
+              {item.label}
+            </div>
+          );
+        })}
+      </div>
+    );
+  },
+});
+
+const styleRoot = css`
+  display: flex;
+  justify-content: center;
+  margin-bottom: 40px;
+  .tabItem {
+    padding: 0 20px;
+    font-size: 22px;
+    font-weight: bold;
+    color: @inf-text-passive-color;
+    cursor: pointer;
+
+    &.active {
+      color: @inf-text-active-color;
+    }
+
+    & + .tabItem {
+      border-left: 1px solid @inf-border-color;
+    }
+  }
+`;

+ 136 - 0
packages/auth/components/components/RegisterForm.tsx

@@ -0,0 +1,136 @@
+import { css } from "@linaria/core";
+import { Button, Form, Input } from "ant-design-vue";
+import { queenApi } from "queenjs";
+import { defineComponent, reactive } from "vue";
+import { useAuth } from "../..";
+import { CaptchaInput } from "./captcha";
+import LoginTabs from "./LoginTabs";
+
+const layout = {
+  wrapperCol: { span: 24 },
+};
+const tailLayout = {
+  wrapperCol: { span: 24 },
+};
+
+export default defineComponent({
+  setup() {
+    const auth = useAuth();
+
+    const state = reactive({
+      loading: false,
+    });
+
+    const formState = reactive({
+      phone: "",
+      code: "",
+      password: "",
+      rePwd: "",
+    });
+
+    const rules = reactive({
+      phone: [{ required: true, message: "手机号不能为空", trigger: "change" }],
+      code: [{ required: true, message: "验证码不能为空", trigger: "change" }],
+      password: [
+        { required: true, message: "密码不能为空", trigger: "change" },
+      ],
+      rePwd: [{ required: true, message: "请确认密码", trigger: "change" }],
+    });
+
+    const { validate, validateInfos } = Form.useForm(formState, rules);
+
+    async function submit() {
+      try {
+        state.loading = true;
+        const { rePwd, ...formData } = await validate();
+        if (rePwd !== formData.password) {
+          queenApi.messageError("输入密码不一致!");
+          return;
+        }
+        await auth.actions.personRegister(formData);
+      } finally {
+        state.loading = false;
+      }
+    }
+
+    function getCodeByClick() {
+      return auth.actions.getSmsCode({
+        phone: formState.phone,
+        use: "register",
+      });
+    }
+
+    return () => (
+      <div class={styleRoot}>
+        <LoginTabs options={[{ label: "用户注册", value: "" }]} />
+        <Form {...layout} name="basic">
+          <Form.Item name="phone" {...validateInfos.phone}>
+            <Input
+              placeholder="请输入手机号码"
+              v-model={[formState.phone, "value"]}
+              maxlength={30}
+            />
+          </Form.Item>
+          <Form.Item name="code" {...validateInfos.code}>
+            <CaptchaInput
+              disabled={!/^1[3456789]\d{9}$/.test(formState.phone)}
+              value={formState.code}
+              onChange={(v) => (formState.code = v)}
+              onGetCode={getCodeByClick}
+            />
+          </Form.Item>
+          <Form.Item name="password" {...validateInfos.password}>
+            <Input
+              onFocus={(e: any) => (e.target.type = "password")}
+              placeholder="请设置密码"
+              v-model={[formState.password, "value"]}
+              maxlength={12}
+            />
+          </Form.Item>
+          <Form.Item name="rePwd" {...validateInfos.rePwd}>
+            <Input
+              onFocus={(e: any) => (e.target.type = "password")}
+              placeholder="再次确认密码"
+              v-model={[formState.rePwd, "value"]}
+              maxlength={12}
+            />
+          </Form.Item>
+          <Form.Item {...tailLayout}>
+            <Button
+              htmlType="submit"
+              block
+              type="primary"
+              size="large"
+              shape="round"
+              loading={state.loading}
+              onClick={submit}
+            >
+              立即注册
+            </Button>
+          </Form.Item>
+        </Form>
+        <router-link to="/" class="backLoginLink">
+          {/* <IconBack /> */}
+          返回登录
+        </router-link>
+      </div>
+    );
+  },
+});
+
+const styleRoot = css`
+  width: 490px;
+  padding: 44px;
+  background-color: @inf-component-bg;
+
+  .backLoginLink {
+    display: flex;
+    margin: 0 auto;
+    width: 120px;
+    align-items: center;
+    color: @inf-primary-color;
+    svg {
+      font-size: 30px;
+    }
+  }
+`;

+ 113 - 0
packages/auth/components/components/ResetForm.tsx

@@ -0,0 +1,113 @@
+import { Button, Form, Input } from "ant-design-vue";
+import { queenApi } from "queenjs";
+import { defineComponent, reactive } from "vue";
+import { useAuth } from "../..";
+import { CaptchaInput } from "./captcha";
+
+const layout = {
+  wrapperCol: { span: 24 },
+};
+const tailLayout = {
+  wrapperCol: { span: 24 },
+};
+
+export default defineComponent({
+  emits: ["success"],
+  setup(props, { emit }) {
+    const auth = useAuth();
+
+    const state = reactive({
+      loading: false,
+    });
+
+    const formState = reactive({
+      phone: "",
+      code: "",
+      password: "",
+      rePwd: "",
+    });
+
+    const rules = reactive({
+      phone: [{ required: true, message: "手机号不能为空", trigger: "change" }],
+      code: [{ required: true, message: "验证码不能为空", trigger: "change" }],
+      password: [
+        { required: true, message: "密码不能为空", trigger: "change" },
+      ],
+      rePwd: [{ required: true, message: "请确认密码", trigger: "change" }],
+    });
+
+    const { validate, validateInfos } = Form.useForm(formState, rules);
+
+    const submit = async () => {
+      try {
+        state.loading = true;
+        const { rePwd, ...formData } = await validate();
+        if (rePwd !== formData.password) {
+          queenApi.messageError("输入密码不一致!");
+          return;
+        }
+        await auth.actions.resetPassword(formData);
+        emit("success");
+      } finally {
+        state.loading = false;
+      }
+    };
+
+    function getCodeByClick() {
+      return auth.actions.getSmsCode({
+        phone: formState.phone,
+        use: "resetPasswd",
+      });
+    }
+
+    return () => (
+      <Form {...layout} name="basic">
+        <Form.Item name="phone" {...validateInfos.phone}>
+          <Input
+            placeholder="请输入手机号码"
+            type=""
+            v-model={[formState.phone, "value"]}
+            maxlength={30}
+          />
+        </Form.Item>
+        <Form.Item name="code" {...validateInfos.code}>
+          <CaptchaInput
+            disabled={!/^1[3456789]\d{9}$/.test(formState.phone)}
+            value={formState.code}
+            onChange={(v) => (formState.code = v)}
+            onGetCode={getCodeByClick}
+          />
+        </Form.Item>
+        <Form.Item name="password" {...validateInfos.password}>
+          <Input
+            type="password"
+            placeholder="请设置密码"
+            v-model={[formState.password, "value"]}
+            maxlength={12}
+          />
+        </Form.Item>
+        <Form.Item name="rePwd" {...validateInfos.rePwd}>
+          <Input
+            type="password"
+            placeholder="再次确认密码"
+            v-model={[formState.rePwd, "value"]}
+            maxlength={12}
+          />
+        </Form.Item>
+        <Form.Item {...tailLayout}>
+          <Button
+            htmlType="submit"
+            block
+            type="primary"
+            size="large"
+            shape="round"
+            loading={state.loading}
+            onClick={submit}
+          >
+            修改密码
+          </Button>
+        </Form.Item>
+      </Form>
+    );
+  },
+});

+ 72 - 0
packages/auth/components/components/captcha.tsx

@@ -0,0 +1,72 @@
+import { defineComponent, onUnmounted, ref } from "vue";
+import { Input, Button } from "ant-design-vue";
+import { bool, func, string } from "vue-types";
+import { css } from "@linaria/core";
+
+export const CaptchaInput = defineComponent({
+  props: {
+    value: string(),
+    disabled: bool(),
+    bordered: bool().def(true),
+    onGetCode: func(),
+    placeholder: string().def("请输入验证码"),
+    text: string().def("发送验证码"),
+    CoundownText: string().def("秒后重发"),
+  },
+  emits: ["change"],
+  setup(props, { emit }) {
+    const time = ref(0);
+    let timer: any = null;
+
+    onUnmounted(() => {
+      clearInterval(timer);
+    });
+
+    async function getCode() {
+      const err = await props.onGetCode?.();
+      if (!err) {
+        time.value = 60;
+        timer = setInterval(() => {
+          time.value--;
+          if (time.value === 0) {
+            clearInterval(timer);
+          }
+        }, 1000);
+      }
+    }
+
+    return () => {
+      const { CoundownText, placeholder, text } = props;
+
+      const sending = time.value !== 0;
+      const btnTxt = sending ? `${time.value}${CoundownText}` : text;
+      return (
+        <div class={rootStyle}>
+          <Input
+            bordered={props.bordered}
+            type="phone"
+            value={props.value}
+            placeholder={placeholder}
+            maxlength={6}
+            onChange={(e) => emit("change", e.target.value)}
+          />
+          <Button
+            type="text"
+            disabled={props.disabled || sending}
+            onClick={getCode}
+          >
+            {btnTxt}
+          </Button>
+        </div>
+      );
+    };
+  },
+});
+
+const rootStyle = css`
+  display: flex;
+  align-items: center;
+  input {
+    flex: 1;
+  }
+`;

+ 44 - 0
packages/auth/index.ts

@@ -0,0 +1,44 @@
+import { Http, ModuleRoot } from "queenjs";
+import { actions } from "./module/actions";
+import { User_Config } from "./module/config";
+import { Dict_Auth_Storage } from "./module/dicts/storage";
+import { authHelper } from "./module/helper";
+import { https } from "./module/http";
+import { stores } from "./module/stores";
+export class AuthModule extends ModuleRoot {
+  config = this.setConfig(User_Config);
+
+  store = this.createStore(stores);
+  https = this.createHttps([https]);
+  actions = this.createActions(actions);
+  helper = this.createHelper([authHelper]);
+
+  onReady() {
+    if (!Http.config.interceptors?.checkAuth) {
+      Http.defaultConfig({
+        interceptors: {
+          checkAuth: Http.interceptor({
+            response: (res: any) => {
+              this.actions.checkLoginExpire(res.data);
+              return res;
+            },
+          }),
+        },
+      });
+    }
+  }
+}
+
+const setToken = Http.interceptor({
+  request(req) {
+    if (!req.headers) req.headers = {};
+    req.headers["authorization"] = `Bearer ${Dict_Auth_Storage.get("token")}`;
+    return req;
+  },
+});
+
+Http.defaultConfig({
+  interceptors: { setToken },
+});
+
+export const { useAuth, setAuth, initAuth } = AuthModule.hook("Auth");

+ 4 - 0
packages/auth/module/actions/index.ts

@@ -0,0 +1,4 @@
+import { loginActions } from "./login";
+import { userActions } from "./user";
+
+export const actions = [loginActions, userActions];

+ 87 - 0
packages/auth/module/actions/login.ts

@@ -0,0 +1,87 @@
+import { queenApi } from "queenjs";
+import { AuthModule } from "../..";
+import { User_Config } from "../config";
+
+export const loginActions = AuthModule.action({
+  async pwdLogin(data: any) {
+    const ret = await this.https.personPwdLogin({
+      ...data,
+      key: this.config.key,
+    });
+    this.actions.getUserInfo();
+    this.actions.saveToken(ret.result.token);
+    this.actions.jump("loginSucc");
+  },
+
+  async wechatLogin(code: string) {
+    const res = await this.https.personWechatLogin({ code });
+    this.actions.getUserInfo();
+    this.actions.saveToken(res.result.token);
+    this.actions.jump("loginSucc");
+  },
+
+  async checkLoginExpire(result: any) {
+    if (result.errorNo === 401) {
+      this.store.clearUserInfo();
+      if (!this.helper.isLoginPage()) {
+        this.actions.jump("tokenExpire");
+      }
+    }
+  },
+
+  logout() {
+    this.store.clearUserInfo();
+    this.actions.jump("logout");
+  },
+
+  async getSmsCode(data: {
+    email?: string;
+    phone?: string;
+    use: "register" | "resetPasswd" | "login";
+  }) {
+    await this.https.getSmsCode(data);
+  },
+
+  async personRegister(data: any) {
+    const ret = await this.https.personRegister({
+      ...data,
+      regInfo: this.config.regInfo,
+    });
+    this.actions.getUserInfo();
+    this.actions.saveToken(ret.result.token);
+    this.actions.jump("loginSucc");
+  },
+
+  async emailRegister(data: any) {
+    const ret = await this.https.emailRegist({
+      ...data,
+      regInfo: this.config.regInfo,
+    });
+    this.actions.getUserInfo();
+    this.actions.saveToken(ret.result.token);
+    this.actions.jump("loginSucc");
+  },
+
+  async resetPassword(data: {
+    email?: string;
+    phone?: string;
+    password: string;
+    code: string;
+  }) {
+    await this.https.resetPwd(data);
+    queenApi.messageSuccess("修改成功");
+    if (this.helper.isLoginPage()) {
+      setTimeout(() => {
+        queenApi.router.back();
+      }, 1000);
+    }
+  },
+
+  saveToken(token: string) {
+    this.store.setToken(token);
+  },
+
+  jump(jumpType: keyof typeof User_Config["jumpOptions"]) {
+    this.config.jumpOptions[jumpType].action.call(this);
+  },
+});

+ 28 - 0
packages/auth/module/actions/user.ts

@@ -0,0 +1,28 @@
+import { queenApi } from "queenjs";
+import { AuthModule } from "../..";
+
+export const userActions = AuthModule.action("keepActive", {
+  async initUser() {
+    await queenApi.router.isReady();
+    if (this.store.token) {
+      await this.actions.getUserInfo();
+      // 判断是登录页, 则跳转页面
+      if (this.helper.isLoginPage()) {
+        this.actions.jump("loginSucc");
+      }
+    }
+    // 不是登录页没有token, 判断是否需要登录
+    else if (this.helper.isCurrPathNeedAuth()) {
+      this.actions.jump("needLogin");
+    }
+  },
+  async getUserInfo() {
+    try {
+      const { result } = await this.https.getProfile();
+      this.store.setUserInfo(result.user);
+    } catch (error: any) {
+      this.actions.checkLoginExpire(error.result);
+      throw error;
+    }
+  },
+});

+ 77 - 0
packages/auth/module/config.ts

@@ -0,0 +1,77 @@
+import { AuthModule } from "..";
+
+type BgStyle = {
+  image: string;
+  repeat?: string;
+  position?: string;
+  size?: string;
+};
+
+const jumpActions = {
+  success: createOpts({
+    action() {
+      const redirectUrl = this.helper.getRedirectPath();
+      this.helper.linkTo(redirectUrl || this.config.loginJumpPath, {
+        replace: !!redirectUrl,
+      });
+    },
+  }),
+  needLogin: createOpts({
+    action() {
+      this.helper.linkTo(this.config.loginPath, {
+        replace: true,
+        redirect: true,
+      });
+    },
+  }),
+  logout: createOpts({
+    action() {
+      this.helper.linkTo(this.config.loginPath);
+    },
+  }),
+};
+
+export const User_Config = {
+  singlePage: false,
+  key: "", //queentreesku3d
+  companyKey: "",
+  regInfo: {
+    key: "", //usercenter
+  },
+
+  loginPath: "/login",
+  loginJumpPath: "/",
+  logoutJumpPath: "/",
+
+  needCompanyLogin: false,
+  needRegister: false,
+  needResetPwd: false,
+
+  perms: null as {
+    baseURL: string;
+  } | null,
+
+  banner: null as {
+    mode?: string;
+    background: BgStyle;
+    title?: string;
+    subTitle?: string;
+  } | null,
+
+  layout: null as {
+    background?: BgStyle;
+  } | null,
+
+  jumpOptions: {
+    loginSucc: jumpActions.success,
+    needLogin: jumpActions.needLogin,
+    tokenExpire: jumpActions.needLogin,
+    logout: jumpActions.logout,
+  },
+};
+
+function createOpts<T extends { action: (this: AuthModule) => void }>(
+  opts: T
+): T {
+  return opts;
+}

+ 9 - 0
packages/auth/module/dicts/storage.ts

@@ -0,0 +1,9 @@
+import { createDictStorage } from "queenjs";
+
+export const Dict_Auth_Storage = createDictStorage(
+  {
+    userInfo: {},
+    token: "",
+  },
+  "localStorage"
+);

+ 61 - 0
packages/auth/module/helper.ts

@@ -0,0 +1,61 @@
+import { queenApi } from "queenjs";
+import { AuthModule } from "..";
+
+export const authHelper = AuthModule.helper({
+  isLoginPage() {
+    if (this.config.singlePage) {
+      const [loginPath] = this.config.loginPath.split("#");
+      return location.href.indexOf(loginPath) > 0;
+    } else {
+      return queenApi.router.currentRoute.value.name === "login";
+    }
+  },
+  isCurrPathNeedAuth() {
+    return queenApi.router.currentRoute.value.meta.needAuth;
+  },
+  getCurrPath() {
+    return queenApi.router.currentRoute.value.fullPath;
+  },
+  getRedirectPath() {
+    if (this.config.singlePage) {
+      const params = new URLSearchParams(location.search);
+      return decodeURIComponent(params.get("redirect") || "");
+    } else {
+      return decodeURIComponent(
+        (queenApi.router.currentRoute.value.query.redirect as string) || ""
+      );
+    }
+  },
+  createBgStyle(bgConfig: any = {}) {
+    const bgStyle: any = {};
+    for (const name in bgConfig) {
+      const value = bgConfig[name];
+      bgStyle["background-" + name] =
+        name === "image" ? `url(${value})` : value;
+    }
+    return bgStyle;
+  },
+  linkTo(path: string, options?: { redirect?: boolean; replace?: boolean }) {
+    if (this.config.singlePage) {
+      if (options?.redirect)
+        path = path + "?redirect=" + encodeURIComponent(location.href);
+      if (options?.replace) {
+        location.replace(path);
+      } else {
+        location.href = path;
+      }
+    } else {
+      const route = {
+        path,
+        query: {
+          redirect: options?.redirect ? this.helper.getCurrPath() : undefined,
+        },
+      };
+      if (options?.replace) {
+        queenApi.router.replace(route);
+      } else {
+        queenApi.router.push(route);
+      }
+    }
+  },
+});

+ 82 - 0
packages/auth/module/http.ts

@@ -0,0 +1,82 @@
+import { AuthModule } from "..";
+
+export const https = AuthModule.http({
+  // 获取验证码
+  getSmsCode(data: {
+    email?: string;
+    phone?: string;
+    use: "login" | "register" | "resetPasswd" | "bindAccount";
+  }) {
+    return this.request(data.phone ? "/sms" : "/senEmail", {
+      method: "POST",
+      data,
+    });
+  },
+
+  // 个人账号注册
+  personRegister(data: { phone: string; password: string }) {
+    return this.request("/register", {
+      method: "POST",
+      data,
+    });
+  },
+  // 邮箱注册
+  emailRegist(data: { email: string; password: string }) {
+    return this.request("/user/register/email", {
+      data,
+      method: "POST",
+    });
+  },
+  // 个人账号密码登录
+  personPwdLogin(data: { phone: string; password: string; key: string }) {
+    return this.request("/login/password", {
+      method: "POST",
+      data,
+    });
+  },
+  // 个人账号短信登录
+  personSmsLogin(data: { phone: string; code: string; key: string }) {
+    return this.request("/login/sms", {
+      method: "POST",
+      data,
+    });
+  },
+
+  /** 微信登录 */
+  personWechatLogin(params: { [key: string]: any }) {
+    return this.request("/wechat/login", { method: "POST", data: params });
+  },
+
+  // 获取用户信息
+  getProfile() {
+    return this.request("/profile", {
+      method: "GET",
+      silence: false,
+    });
+  },
+  // 更新用户信息
+  updateProfile(data: any) {
+    return this.request("/profile", {
+      method: "POST",
+      data,
+    });
+  },
+
+  // 重置登录密码 、 忘记密码
+  resetPwd(data: {
+    email?: string;
+    phone?: string;
+    password: string;
+    code: string;
+  }) {
+    return this.request("/resetpwd", {
+      method: "POST",
+      data,
+    });
+  },
+
+  // 绑定手机号码、邮箱
+  bindAccount(data: { phone?: string; email?: string; code: string }) {
+    return this.request("/bind/account ", { method: "post", data });
+  },
+});

+ 3 - 0
packages/auth/module/stores/index.ts

@@ -0,0 +1,3 @@
+import { userStore } from "./user";
+
+export const stores = [userStore];

+ 37 - 0
packages/auth/module/stores/user.ts

@@ -0,0 +1,37 @@
+import { AuthModule } from "../..";
+import { User } from "../../typings";
+import { Dict_Auth_Storage } from "../dicts/storage";
+
+const initialState = {
+  alipay: "",
+  avatar: "",
+  city: "",
+  company: "",
+  companyKey: "",
+  desc: "",
+  email: "",
+  name: "",
+  phone: "",
+  wechat: "",
+  userType: 2,
+  _id: "",
+};
+
+export const userStore = AuthModule.store({
+  state: () => ({
+    token: Dict_Auth_Storage.get("token"),
+    userInfo: initialState,
+  }),
+  actions: {
+    setToken(token: string) {
+      Dict_Auth_Storage.set("token", (this.store.token = token));
+    },
+    setUserInfo(userInfo: User) {
+      Dict_Auth_Storage.set("userInfo", (this.store.userInfo = userInfo));
+    },
+    clearUserInfo() {
+      this.store.setUserInfo({ ...initialState });
+      this.store.setToken("");
+    },
+  },
+});

+ 11 - 0
packages/auth/package.json

@@ -0,0 +1,11 @@
+{
+  "name": "@queenjs-modules/auth",
+  "version": "0.0.20",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}

+ 14 - 0
packages/auth/typings.ts

@@ -0,0 +1,14 @@
+export interface User {
+  alipay: string;
+  avatar: string;
+  city: string;
+  company: string;
+  companyKey: string;
+  desc: string;
+  email: string;
+  name: string;
+  phone: string;
+  wechat: string;
+  userType: number;
+  _id: string;
+}

+ 21 - 0
tsconfig.json

@@ -0,0 +1,21 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "module": "esnext",
+    "strict": true,
+    "jsx": "preserve",
+    "importHelpers": true,
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "sourceMap": true,
+    "baseUrl": ".",
+    "types": ["webpack-env"],
+    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
+  },
+  "include": ["packages/**/*.ts", "packages/**/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 399 - 0
yarn.lock

@@ -0,0 +1,399 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ant-design/colors@^6.0.0":
+  version "6.0.0"
+  resolved "http://124.70.149.18:4873/@ant-design%2fcolors/-/colors-6.0.0.tgz#9b9366257cffcc47db42b9d0203bb592c13c0298"
+  integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==
+  dependencies:
+    "@ctrl/tinycolor" "^3.4.0"
+
+"@ant-design/icons-svg@^4.2.1":
+  version "4.2.1"
+  resolved "http://124.70.149.18:4873/@ant-design%2ficons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
+  integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==
+
+"@ant-design/icons-vue@^6.1.0":
+  version "6.1.0"
+  resolved "http://124.70.149.18:4873/@ant-design%2ficons-vue/-/icons-vue-6.1.0.tgz#f9324fdc0eb4cea943cf626d2bf3db9a4ff4c074"
+  integrity sha512-EX6bYm56V+ZrKN7+3MT/ubDkvJ5rK/O2t380WFRflDcVFgsvl3NLH7Wxeau6R8DbrO5jWR6DSTC3B6gYFp77AA==
+  dependencies:
+    "@ant-design/colors" "^6.0.0"
+    "@ant-design/icons-svg" "^4.2.1"
+
+"@babel/parser@^7.20.15", "@babel/parser@^7.21.3":
+  version "7.22.7"
+  resolved "http://124.70.149.18:4873/@babel%2fparser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae"
+  integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==
+
+"@babel/runtime@^7.10.5":
+  version "7.22.6"
+  resolved "http://124.70.149.18:4873/@babel%2fruntime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
+  integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
+  dependencies:
+    regenerator-runtime "^0.13.11"
+
+"@ctrl/tinycolor@^3.4.0":
+  version "3.6.0"
+  resolved "http://124.70.149.18:4873/@ctrl%2ftinycolor/-/tinycolor-3.6.0.tgz#53fa5fe9c34faee89469e48f91d51a3766108bc8"
+  integrity sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==
+
+"@jridgewell/sourcemap-codec@^1.4.15":
+  version "1.4.15"
+  resolved "http://124.70.149.18:4873/@jridgewell%2fsourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@simonwep/pickr@~1.8.0":
+  version "1.8.2"
+  resolved "http://124.70.149.18:4873/@simonwep%2fpickr/-/pickr-1.8.2.tgz#96dc86675940d7cad63d69c22083dd1cbb9797cb"
+  integrity sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==
+  dependencies:
+    core-js "^3.15.1"
+    nanopop "^2.1.0"
+
+"@vue/compiler-core@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fcompiler-core/-/compiler-core-3.3.4.tgz#7fbf591c1c19e1acd28ffd284526e98b4f581128"
+  integrity sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==
+  dependencies:
+    "@babel/parser" "^7.21.3"
+    "@vue/shared" "3.3.4"
+    estree-walker "^2.0.2"
+    source-map-js "^1.0.2"
+
+"@vue/compiler-dom@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fcompiler-dom/-/compiler-dom-3.3.4.tgz#f56e09b5f4d7dc350f981784de9713d823341151"
+  integrity sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==
+  dependencies:
+    "@vue/compiler-core" "3.3.4"
+    "@vue/shared" "3.3.4"
+
+"@vue/compiler-sfc@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fcompiler-sfc/-/compiler-sfc-3.3.4.tgz#b19d942c71938893535b46226d602720593001df"
+  integrity sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==
+  dependencies:
+    "@babel/parser" "^7.20.15"
+    "@vue/compiler-core" "3.3.4"
+    "@vue/compiler-dom" "3.3.4"
+    "@vue/compiler-ssr" "3.3.4"
+    "@vue/reactivity-transform" "3.3.4"
+    "@vue/shared" "3.3.4"
+    estree-walker "^2.0.2"
+    magic-string "^0.30.0"
+    postcss "^8.1.10"
+    source-map-js "^1.0.2"
+
+"@vue/compiler-ssr@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fcompiler-ssr/-/compiler-ssr-3.3.4.tgz#9d1379abffa4f2b0cd844174ceec4a9721138777"
+  integrity sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==
+  dependencies:
+    "@vue/compiler-dom" "3.3.4"
+    "@vue/shared" "3.3.4"
+
+"@vue/reactivity-transform@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2freactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929"
+  integrity sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==
+  dependencies:
+    "@babel/parser" "^7.20.15"
+    "@vue/compiler-core" "3.3.4"
+    "@vue/shared" "3.3.4"
+    estree-walker "^2.0.2"
+    magic-string "^0.30.0"
+
+"@vue/reactivity@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2freactivity/-/reactivity-3.3.4.tgz#a27a29c6cd17faba5a0e99fbb86ee951653e2253"
+  integrity sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==
+  dependencies:
+    "@vue/shared" "3.3.4"
+
+"@vue/runtime-core@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fruntime-core/-/runtime-core-3.3.4.tgz#4bb33872bbb583721b340f3088888394195967d1"
+  integrity sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==
+  dependencies:
+    "@vue/reactivity" "3.3.4"
+    "@vue/shared" "3.3.4"
+
+"@vue/runtime-dom@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fruntime-dom/-/runtime-dom-3.3.4.tgz#992f2579d0ed6ce961f47bbe9bfe4b6791251566"
+  integrity sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==
+  dependencies:
+    "@vue/runtime-core" "3.3.4"
+    "@vue/shared" "3.3.4"
+    csstype "^3.1.1"
+
+"@vue/server-renderer@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fserver-renderer/-/server-renderer-3.3.4.tgz#ea46594b795d1536f29bc592dd0f6655f7ea4c4c"
+  integrity sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==
+  dependencies:
+    "@vue/compiler-ssr" "3.3.4"
+    "@vue/shared" "3.3.4"
+
+"@vue/shared@3.3.4":
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/@vue%2fshared/-/shared-3.3.4.tgz#06e83c5027f464eef861c329be81454bc8b70780"
+  integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
+
+ant-design-vue@^3.2.20:
+  version "3.2.20"
+  resolved "http://124.70.149.18:4873/ant-design-vue/-/ant-design-vue-3.2.20.tgz#552f5f08e2acbcc10bd4089145d4cc2b15d4cfec"
+  integrity sha512-YWpMfGaGoRastIXEYfCoJiaRiDHk4chqtYhlKQM5GqPt6NfvrM1Vg2e60yHtjxlZjed91wCMm0rAmyUr7Hwzdg==
+  dependencies:
+    "@ant-design/colors" "^6.0.0"
+    "@ant-design/icons-vue" "^6.1.0"
+    "@babel/runtime" "^7.10.5"
+    "@ctrl/tinycolor" "^3.4.0"
+    "@simonwep/pickr" "~1.8.0"
+    array-tree-filter "^2.1.0"
+    async-validator "^4.0.0"
+    dayjs "^1.10.5"
+    dom-align "^1.12.1"
+    dom-scroll-into-view "^2.0.0"
+    lodash "^4.17.21"
+    lodash-es "^4.17.15"
+    resize-observer-polyfill "^1.5.1"
+    scroll-into-view-if-needed "^2.2.25"
+    shallow-equal "^1.0.0"
+    vue-types "^3.0.0"
+    warning "^4.0.0"
+
+array-tree-filter@^2.1.0:
+  version "2.1.0"
+  resolved "http://124.70.149.18:4873/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190"
+  integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==
+
+async-validator@^4.0.0:
+  version "4.2.5"
+  resolved "http://124.70.149.18:4873/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339"
+  integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "http://124.70.149.18:4873/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+
+axios@^0.27.2:
+  version "0.27.2"
+  resolved "http://124.70.149.18:4873/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
+  integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
+  dependencies:
+    follow-redirects "^1.14.9"
+    form-data "^4.0.0"
+
+combined-stream@^1.0.8:
+  version "1.0.8"
+  resolved "http://124.70.149.18:4873/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+compute-scroll-into-view@^1.0.20:
+  version "1.0.20"
+  resolved "http://124.70.149.18:4873/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43"
+  integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==
+
+core-js@^3.15.1:
+  version "3.31.1"
+  resolved "http://124.70.149.18:4873/core-js/-/core-js-3.31.1.tgz#f2b0eea9be9da0def2c5fece71064a7e5d687653"
+  integrity sha512-2sKLtfq1eFST7l7v62zaqXacPc7uG8ZAya8ogijLhTtaKNcpzpB4TMoTw2Si+8GYKRwFPMMtUT0263QFWFfqyQ==
+
+csstype@^3.1.1:
+  version "3.1.2"
+  resolved "http://124.70.149.18:4873/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
+  integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
+
+dayjs@^1.10.5:
+  version "1.11.9"
+  resolved "http://124.70.149.18:4873/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
+  integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "http://124.70.149.18:4873/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+
+dom-align@^1.12.1:
+  version "1.12.4"
+  resolved "http://124.70.149.18:4873/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511"
+  integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==
+
+dom-scroll-into-view@^2.0.0:
+  version "2.0.1"
+  resolved "http://124.70.149.18:4873/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz#0decc8522801fd8d3f1c6ba355a74d382c5f989b"
+  integrity sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==
+
+estree-walker@^2.0.2:
+  version "2.0.2"
+  resolved "http://124.70.149.18:4873/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+  integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+eventemitter3@^4.0.7:
+  version "4.0.7"
+  resolved "http://124.70.149.18:4873/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+follow-redirects@^1.14.9:
+  version "1.15.2"
+  resolved "http://124.70.149.18:4873/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+  integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
+form-data@^4.0.0:
+  version "4.0.0"
+  resolved "http://124.70.149.18:4873/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+is-plain-object@3.0.1:
+  version "3.0.1"
+  resolved "http://124.70.149.18:4873/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b"
+  integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==
+
+"js-tokens@^3.0.0 || ^4.0.0":
+  version "4.0.0"
+  resolved "http://124.70.149.18:4873/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+lodash-es@^4.17.15:
+  version "4.17.21"
+  resolved "http://124.70.149.18:4873/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+  integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+lodash@^4.17.21:
+  version "4.17.21"
+  resolved "http://124.70.149.18:4873/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+loose-envify@^1.0.0:
+  version "1.4.0"
+  resolved "http://124.70.149.18:4873/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+  dependencies:
+    js-tokens "^3.0.0 || ^4.0.0"
+
+magic-string@^0.30.0:
+  version "0.30.1"
+  resolved "http://124.70.149.18:4873/magic-string/-/magic-string-0.30.1.tgz#ce5cd4b0a81a5d032bd69aab4522299b2166284d"
+  integrity sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==
+  dependencies:
+    "@jridgewell/sourcemap-codec" "^1.4.15"
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "http://124.70.149.18:4873/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+  version "2.1.35"
+  resolved "http://124.70.149.18:4873/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+moduse@^0.0.8:
+  version "0.0.8"
+  resolved "http://124.70.149.18:4873/moduse/-/moduse-0.0.8.tgz#dbee76d5ec605673ce1c1880b8e79693823135f1"
+  integrity sha512-nzhqhr8eAPcEtl++lI02cMTiCuYpOQmd8Npy9JLQw7pJNd1WU4hCU0rBGEnX0a0985/OP2Jmlpo9fFHm42aFZQ==
+  dependencies:
+    lodash "^4.17.21"
+
+nanoid@^3.3.6:
+  version "3.3.6"
+  resolved "http://124.70.149.18:4873/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+  integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
+nanopop@^2.1.0:
+  version "2.3.0"
+  resolved "http://124.70.149.18:4873/nanopop/-/nanopop-2.3.0.tgz#a5f672fba27d45d6ecbd0b59789c040072915123"
+  integrity sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw==
+
+picocolors@^1.0.0:
+  version "1.0.0"
+  resolved "http://124.70.149.18:4873/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+postcss@^8.1.10:
+  version "8.4.25"
+  resolved "http://124.70.149.18:4873/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f"
+  integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==
+  dependencies:
+    nanoid "^3.3.6"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
+queenjs@^1.0.0-beta.78:
+  version "1.0.0-beta.78"
+  resolved "http://124.70.149.18:4873/queenjs/-/queenjs-1.0.0-beta.78.tgz#1d19d0d0b67d04ff407c50cf08e47a8e1f9dce58"
+  integrity sha512-70uvtWg38trjNrrccwYyFtDkAN3HjOJgLH7l1oACd0CUk2e2iPRJ0EoQcc6Z90QxuewNK1XKPLV+at+GpS6a2g==
+  dependencies:
+    axios "^0.27.2"
+    eventemitter3 "^4.0.7"
+    moduse "^0.0.8"
+    vue-moduse "^0.0.11"
+
+regenerator-runtime@^0.13.11:
+  version "0.13.11"
+  resolved "http://124.70.149.18:4873/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+  integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
+resize-observer-polyfill@^1.5.1:
+  version "1.5.1"
+  resolved "http://124.70.149.18:4873/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
+  integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+
+scroll-into-view-if-needed@^2.2.25:
+  version "2.2.31"
+  resolved "http://124.70.149.18:4873/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587"
+  integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==
+  dependencies:
+    compute-scroll-into-view "^1.0.20"
+
+shallow-equal@^1.0.0:
+  version "1.2.1"
+  resolved "http://124.70.149.18:4873/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
+  integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
+
+source-map-js@^1.0.2:
+  version "1.0.2"
+  resolved "http://124.70.149.18:4873/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+vue-moduse@^0.0.11:
+  version "0.0.11"
+  resolved "http://124.70.149.18:4873/vue-moduse/-/vue-moduse-0.0.11.tgz#192657c985cc44b3233dbc99e82b289c710a1163"
+  integrity sha512-pVEd47rg7p8bm9Q4PP5WaLxdqjysrZJcfPs2okkYe5VcVlDR0iN3QcscEgLMBVW+pc1wVf3GkQp+vMdABb9lrw==
+
+vue-types@^3.0.0:
+  version "3.0.2"
+  resolved "http://124.70.149.18:4873/vue-types/-/vue-types-3.0.2.tgz#ec16e05d412c038262fc1efa4ceb9647e7fb601d"
+  integrity sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==
+  dependencies:
+    is-plain-object "3.0.1"
+
+vue@^3.3.4:
+  version "3.3.4"
+  resolved "http://124.70.149.18:4873/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"
+  integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==
+  dependencies:
+    "@vue/compiler-dom" "3.3.4"
+    "@vue/compiler-sfc" "3.3.4"
+    "@vue/runtime-dom" "3.3.4"
+    "@vue/server-renderer" "3.3.4"
+    "@vue/shared" "3.3.4"
+
+warning@^4.0.0:
+  version "4.0.3"
+  resolved "http://124.70.149.18:4873/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+  integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+  dependencies:
+    loose-envify "^1.0.0"