| | |
| | | // 根据角色动态生成路由 |
| | | import { defineFakeRoute } from "vite-plugin-fake-server/client"; |
| | | // // 根据角色动态生成路由 |
| | | // import { defineFakeRoute } from "vite-plugin-fake-server/client"; |
| | | |
| | | export default defineFakeRoute([ |
| | | { |
| | | url: "/login", |
| | | method: "post", |
| | | response: ({ body }) => { |
| | | if (body.username === "admin") { |
| | | return { |
| | | success: true, |
| | | data: { |
| | | avatar: "https://avatars.githubusercontent.com/u/44761321", |
| | | username: "admin", |
| | | nickname: "小铭", |
| | | // 一个用户可能有多个角色 |
| | | roles: ["admin"], |
| | | // 按钮级别权限 |
| | | permissions: ["*:*:*"], |
| | | accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", |
| | | refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", |
| | | expires: "2030/10/30 00:00:00" |
| | | } |
| | | }; |
| | | } else { |
| | | return { |
| | | success: true, |
| | | data: { |
| | | avatar: "https://avatars.githubusercontent.com/u/52823142", |
| | | username: "common", |
| | | nickname: "小林", |
| | | roles: ["common"], |
| | | permissions: ["permission:btn:add", "permission:btn:edit"], |
| | | accessToken: "eyJhbGciOiJIUzUxMiJ9.common", |
| | | refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", |
| | | expires: "2030/10/30 00:00:00" |
| | | } |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | ]); |
| | | // export default defineFakeRoute([ |
| | | // { |
| | | // url: "/login", |
| | | // method: "post", |
| | | // response: ({ body }) => { |
| | | // if (body.username === "admin") { |
| | | // return { |
| | | // success: true, |
| | | // data: { |
| | | // avatar: "https://avatars.githubusercontent.com/u/44761321", |
| | | // username: "admin", |
| | | // nickname: "小铭", |
| | | // // 一个用户可能有多个角色 |
| | | // roles: ["admin"], |
| | | // // 按钮级别权限 |
| | | // permissions: ["*:*:*"], |
| | | // accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", |
| | | // refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", |
| | | // expires: "2030/10/30 00:00:00" |
| | | // } |
| | | // }; |
| | | // } else { |
| | | // return { |
| | | // success: true, |
| | | // data: { |
| | | // avatar: "https://avatars.githubusercontent.com/u/52823142", |
| | | // username: "common", |
| | | // nickname: "小林", |
| | | // roles: ["common"], |
| | | // permissions: ["permission:btn:add", "permission:btn:edit"], |
| | | // accessToken: "eyJhbGciOiJIUzUxMiJ9.common", |
| | | // refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", |
| | | // expires: "2030/10/30 00:00:00" |
| | | // } |
| | | // }; |
| | | // } |
| | | // } |
| | | // } |
| | | // ]); |
New file |
| | |
| | | /** |
| | | * (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参) |
| | | * |
| | | * 注册api接口集合 |
| | | * @method login 登录 |
| | | */ |
| | | |
| | | import { http } from "@/utils/http"; |
| | | import { baseUrlApi } from "../util"; |
| | | type Result = { |
| | | success: boolean; |
| | | data: Array<any>; |
| | | }; |
| | | |
| | | export const login = (data?: object) => { |
| | | return http.request("post", baseUrlApi("/api/auth/loginPhone"), { data }); |
| | | }; |
New file |
| | |
| | | /** |
| | | * (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参) |
| | | * |
| | | * 注册api接口集合 |
| | | * @method register 注册 |
| | | * @method captcha 获取验证码 |
| | | */ |
| | | |
| | | import { http } from "@/utils/http"; |
| | | import { baseUrlApi } from "../util"; |
| | | type Result = { |
| | | success: boolean; |
| | | data: Array<any>; |
| | | }; |
| | | |
| | | export const register = (data?: object) => { |
| | | return http.request( |
| | | "post", |
| | | baseUrlApi("/api/customer/customerRegistration"), |
| | | { data } |
| | | ); |
| | | }; |
| | | |
| | | export const captcha = () => { |
| | | return http.request<Result>("get", baseUrlApi("/api/zCSMS/captcha")); |
| | | }; |
| | | |
| | | //获取角色 |
| | | export const exRole = () => { |
| | | return http.request<Result>("get", baseUrlApi("/api/customer/exRole")); |
| | | }; |
| | | |
| | | // 获取手机验证码 |
| | | export const phoneNumberCode = (params?: object) => { |
| | | return http.request( |
| | | "post", |
| | | baseUrlApi( |
| | | `/api/zCSMS/sendSMS/${params.phone}/${params.code}/${params.codeId}` |
| | | ) |
| | | ); |
| | | }; |
| | |
| | | import { http } from "@/utils/http"; |
| | | import { baseUrlApi } from "./util"; |
| | | |
| | | export type UserResult = { |
| | | success: boolean; |
| | |
| | | |
| | | /** 登录 */ |
| | | export const getLogin = (data?: object) => { |
| | | return http.request<UserResult>("post", "/login", { data }); |
| | | return http.request("post", baseUrlApi("/api/auth/loginPhone"), { data }); |
| | | // return http.request<UserResult>("post", "/login", { data }); |
| | | }; |
| | | |
| | | /** 刷新`token` */ |
| | | export const refreshTokenApi = (data?: object) => { |
| | | return http.request<RefreshTokenResult>("post", "/refresh-token", { data }); |
| | | return http.request<RefreshTokenResult>("post", "/refresh-token1", { data }); |
| | | }; |
New file |
| | |
| | | export const baseUrlApi = (url: string) => |
| | | process.env.NODE_ENV === "development" |
| | | ? `/api${url}` |
| | | : `http://192.168.0.36:5005${url}`; |
| | |
| | | } |
| | | |
| | | /** 路由白名单 */ |
| | | // const whiteList = ["/login"]; |
| | | const whiteList = []; |
| | | |
| | | const { VITE_HIDE_HOME } = import.meta.env; |
| | |
| | | // redirect: "/welcome", |
| | | meta: { |
| | | icon: "ep/home-filled", |
| | | title: "首页", |
| | | title: "主页", |
| | | rank: 0 |
| | | }, |
| | | children: [ |
| | |
| | | name: "Welcome", |
| | | component: () => import("@/views/welcome/index.vue"), |
| | | meta: { |
| | | title: "首页", |
| | | title: "主页", |
| | | showLink: VITE_HIDE_HOME === "true" ? false : true |
| | | } |
| | | } |
| | |
| | | path: "/", |
| | | name: "Main", |
| | | redirect: "/home", |
| | | meta: {} |
| | | meta: { |
| | | title: "首页", |
| | | showLink: false, |
| | | rank: 101 |
| | | } |
| | | }, |
| | | { |
| | | path: "/home", |
| | |
| | | { |
| | | path: "/login", |
| | | name: "Login", |
| | | component: () => import("@/views/home/index.vue"), |
| | | component: () => import("@/views/login/index.vue"), |
| | | meta: { |
| | | title: "登录", |
| | | showLink: false, |
| | |
| | | meta: { |
| | | title: "注册", |
| | | showLink: false, |
| | | rank: 101 |
| | | rank: 104 |
| | | } |
| | | }, |
| | | { |
| | | path: "/registersucess", |
| | | name: "RegisterSucess", |
| | | component: () => import("@/views/register/registersucess.vue"), |
| | | meta: { |
| | | title: "注册成功", |
| | | showLink: false, |
| | | rank: 103 |
| | | } |
| | | }, |
| | | { |
| | |
| | | const { name, path, parentId, meta } = routeInfo; |
| | | return isAllEmpty(parentId) |
| | | ? isAllEmpty(meta?.rank) || |
| | | (meta?.rank === 0 && name !== "Home" && path !== "/") |
| | | (meta?.rank === 0 && name !== "Welcome" && path !== "/welcome") |
| | | ? true |
| | | : false |
| | | : false; |
| | |
| | | |
| | | /** 处理动态路由(后端返回的路由) */ |
| | | function handleAsyncRoutes(routeList) { |
| | | if (routeList.length === 0) { |
| | | if (routeList?.length === 0) { |
| | | usePermissionStoreHook().handleWholeMenus(routeList); |
| | | } else { |
| | | formatFlatteningRoutes(addAsyncRoutes(routeList)).map( |
| | |
| | | if (!router.hasRoute(v?.name)) router.addRoute(v); |
| | | const flattenRouters: any = router |
| | | .getRoutes() |
| | | .find(n => n.path === "/"); |
| | | .find(n => n.path === "/welcome"); |
| | | // 保持router.options.routes[0].children与path为"/"的children一致,防止数据不一致导致异常 |
| | | flattenRouters.children = router.options.routes[0].children; |
| | | router.addRoute(flattenRouters); |
| | |
| | | } else { |
| | | return new Promise(resolve => { |
| | | getAsyncRoutes().then(({ data }) => { |
| | | handleAsyncRoutes(cloneDeep(data)); |
| | | storageLocal().setItem(key, data); |
| | | // handleAsyncRoutes(cloneDeep(data)); |
| | | // storageLocal().setItem(key, data); |
| | | resolve(router); |
| | | }); |
| | | }); |
| | |
| | | } else { |
| | | return new Promise(resolve => { |
| | | getAsyncRoutes().then(({ data }) => { |
| | | handleAsyncRoutes(cloneDeep(data)); |
| | | // handleAsyncRoutes(cloneDeep(data)); |
| | | resolve(router); |
| | | }); |
| | | }); |
| | |
| | | * @returns 返回处理后的一维路由 |
| | | */ |
| | | function formatFlatteningRoutes(routesList: RouteRecordRaw[]) { |
| | | if (routesList.length === 0) return routesList; |
| | | if (routesList?.length === 0) return routesList; |
| | | let hierarchyList = buildHierarchyTree(routesList); |
| | | for (let i = 0; i < hierarchyList.length; i++) { |
| | | if (hierarchyList[i].children) { |
| | |
| | | * @returns 返回将一维数组重新处理成规定路由的格式 |
| | | */ |
| | | function formatTwoStageRoutes(routesList: RouteRecordRaw[]) { |
| | | if (routesList.length === 0) return routesList; |
| | | if (routesList?.length === 0) return routesList; |
| | | const newRoutesList: RouteRecordRaw[] = []; |
| | | routesList.forEach((v: RouteRecordRaw) => { |
| | | if (v.path === "/") { |
| | | if (v.path === "/welcome") { |
| | | newRoutesList.push({ |
| | | component: v.component, |
| | | name: v.name, |
| | |
| | | usePermissionStoreHook().wholeMenus[0]?.children[0] |
| | | ); |
| | | tag && useMultiTagsStoreHook().handleTags("push", topMenu); |
| | | console.log(topMenu, "topMenu"); |
| | | return topMenu; |
| | | } |
| | | |
| | |
| | | // showLink:false 不添加到标签页 |
| | | if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink) |
| | | return; |
| | | const tagPath = tagVal.path; |
| | | const tagPath = tagVal?.path; |
| | | // 判断tag是否已存在 |
| | | const tagHasExits = this.multiTags.some(tag => { |
| | | return tag.path === tagPath; |
| | | return tag?.path === tagPath; |
| | | }); |
| | | |
| | | // 判断tag中的query键值是否相等 |
| | |
| | | </div> |
| | | <div class="right"> |
| | | <el-tabs v-model="activeName" class="demo-tabs"> |
| | | <el-tab-pane label="工程招标" name="first"> |
| | | 工程招标 |
| | | <el-tab-pane label="意向公开" name="first"> |
| | | 意向公开 |
| | | <!-- <div class="item"> |
| | | <span |
| | | ><span style="color: #145ccd; font-weight: 600">·</span |
| | |
| | | <span>2024-04-15 18:10</span> |
| | | </div> --> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="货物招标" name="second">货物招标</el-tab-pane> |
| | | <el-tab-pane label="服务招标" name="third">服务招标</el-tab-pane> |
| | | <el-tab-pane label="工程招标" name="second">工程招标</el-tab-pane> |
| | | <el-tab-pane label="货物招标" name="third">货物招标</el-tab-pane> |
| | | <el-tab-pane label="服务招标" name="fourth">服务招标</el-tab-pane> |
| | | <el-tab-pane label="网上竞价" name="fourth">网上竞价</el-tab-pane> |
| | | </el-tabs> |
| | | </div> |
| | |
| | | src="@/assets/home/car1.png" |
| | | alt="" |
| | | /> |
| | | 采购人/招标人 |
| | | 采购人 |
| | | </div> |
| | | <div>注册<span class="m-2">|</span>登录</div> |
| | | <div> |
| | | <span class="hover:cursor-pointer" @click="toRegister">注册</span |
| | | ><span class="m-2">|</span |
| | | ><span class="hover:cursor-pointer" @click="toLogin">登录</span> |
| | | </div> |
| | | </div> |
| | | <div class="item"> |
| | | <div class="box"> |
| | |
| | | src="@/assets/home/car.png" |
| | | alt="" |
| | | /> |
| | | 采购人 |
| | | 代理机构 |
| | | </div> |
| | | <div>注册<span class="m-2">|</span>登录</div> |
| | | </div> |
| | | <div class="item"> |
| | | <div class="box"> |
| | | <img |
| | | width="18px" |
| | | height="18px" |
| | | src="@/assets/home/car.png" |
| | | alt="" |
| | | />招标代理机构 |
| | | <div> |
| | | <span class="hover:cursor-pointer" @click="toRegister">注册</span |
| | | ><span class="m-2">|</span |
| | | ><span class="hover:cursor-pointer" @click="toLogin">登录</span> |
| | | </div> |
| | | <div>注册<span class="m-2">|</span>登录</div> |
| | | </div> |
| | | <div class="item"> |
| | | <div class="box"> |
| | |
| | | alt="" |
| | | />供应商 |
| | | </div> |
| | | <div>注册<span class="m-2">|</span>登录</div> |
| | | <div> |
| | | <span class="hover:cursor-pointer" @click="toRegister">注册</span |
| | | ><span class="m-2">|</span |
| | | ><span class="hover:cursor-pointer" @click="toLogin">登录</span> |
| | | </div> |
| | | </div> |
| | | <div class="item"> |
| | | <div class="box"> |
| | |
| | | alt="" |
| | | />评审专家 |
| | | </div> |
| | | <div>注册<span class="m-2">|</span>登录</div> |
| | | <div> |
| | | <span class="hover:cursor-pointer" @click="toRegister">注册</span |
| | | ><span class="m-2">|</span |
| | | ><span class="hover:cursor-pointer" @click="toLogin">登录</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="right" /> |
| | |
| | | import { ref } from "vue"; |
| | | import myFooter from "./component/myFooter.vue"; |
| | | let activeName = ref("first"); |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | defineOptions({ |
| | | name: "Home" |
| | | }); |
| | | const router = useRouter(); |
| | | const toRegister = () => { |
| | | router.push({ name: "Register" }); |
| | | }; |
| | | const toLogin = () => { |
| | | router.push({ name: "Login" }); |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | |
| | | padding: 40px 0; |
| | | .all { |
| | | width: 72%; |
| | | height: 482px; |
| | | height: 385px; |
| | | background: #fff; |
| | | margin: 0 auto; |
| | | display: flex; |
| | |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0 30px; |
| | | height: 20%; |
| | | height: 25%; |
| | | text-align: left; |
| | | color: #5f5f5f; |
| | | .box { |
| | |
| | | import { useRouter } from "vue-router"; |
| | | import { message } from "@/utils/message"; |
| | | import { loginRules } from "./utils/rule"; |
| | | import { ref, reactive, toRaw } from "vue"; |
| | | import { |
| | | reactive, |
| | | computed, |
| | | ref, |
| | | onMounted, |
| | | defineAsyncComponent, |
| | | onUnmounted, |
| | | watch |
| | | } from "vue"; |
| | | import { debounce } from "@pureadmin/utils"; |
| | | import { useNav } from "@/layout/hooks/useNav"; |
| | | import { useEventListener } from "@vueuse/core"; |
| | |
| | | import { useUserStoreHook } from "@/store/modules/user"; |
| | | import { initRouter, getTopMenu } from "@/router/utils"; |
| | | import { bg, avatar, logo1 } from "./utils/static"; |
| | | import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | // import { useRenderIcon } from "@/components/ReIcon/src/hooks"; |
| | | import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; |
| | | import { useRoute } from "vue-router"; |
| | | |
| | | import dayIcon from "@/assets/svg/day.svg?component"; |
| | | import darkIcon from "@/assets/svg/dark.svg?component"; |
| | | const route = useRoute(); |
| | | |
| | | // import dayIcon from "@/assets/svg/day.svg?component"; |
| | | // import darkIcon from "@/assets/svg/dark.svg?component"; |
| | | import Lock from "~icons/ri/lock-fill"; |
| | | import User from "~icons/ri/user-3-fill"; |
| | | import { captcha, phoneNumberCode, exRole } from "@/api/register/index.ts"; |
| | | |
| | | defineOptions({ |
| | | name: "Login" |
| | |
| | | const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange(); |
| | | dataThemeChange(overallStyle.value); |
| | | const { title } = useNav(); |
| | | // 获取验证码 |
| | | const getCaptcha = async () => { |
| | | // if (!state.captchaEnabled) return; |
| | | |
| | | const ruleForm = reactive({ |
| | | username: "admin", |
| | | password: "admin123" |
| | | state.ruleForm.code = ""; |
| | | const res = await captcha(); |
| | | console.log(res); |
| | | |
| | | state.captchaImage = "data:text/html;base64," + res.result?.img; |
| | | state.expirySeconds = res.result?.expirySeconds; |
| | | state.ruleForm.codeId = res.result?.id; |
| | | }; |
| | | const state = reactive({ |
| | | isShowPassword: false, |
| | | ruleForm: { |
| | | account: "", |
| | | nickName: "", |
| | | phone: "", |
| | | phoneVCode: "", |
| | | // tenantId: props.tenantInfo.id, |
| | | code: "", |
| | | codeId: 0, |
| | | email: "", |
| | | exRoleCode: "" |
| | | }, |
| | | rules: { |
| | | code: [ |
| | | { |
| | | required: true, |
| | | message: "请输入手机验证码", |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | phone: [ |
| | | { |
| | | required: true, |
| | | message: "请输入您的手机号码", |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | exRoleCode: [ |
| | | { |
| | | required: true, |
| | | message: "请选择角色", |
| | | trigger: "blur" |
| | | } |
| | | ] |
| | | // code: [{ required: true, message: t('message.account.placeholder4'), trigger: 'blur' }], |
| | | }, |
| | | loading: { |
| | | signIn: false |
| | | }, |
| | | captchaImage: "", |
| | | rotateVerifyVisible: false, |
| | | // rotateVerifyImg: verifyImg, |
| | | // rotateVerifyImg: themeConfig.value.logoUrl, |
| | | secondVerEnabled: false, |
| | | // captchaEnabled: false, |
| | | isPassRotate: false, |
| | | capsLockVisible: false, |
| | | hideTenantForLogin: false, |
| | | expirySeconds: 60, // 验证码过期时间 |
| | | phoneSeconds: 0, // 手机验证码倒计时 |
| | | roleList: [] |
| | | }); |
| | | // 验证码过期计时器 |
| | | let timer: any = null; |
| | | let phonetimer: any = null; |
| | | |
| | | // 页面初始化 |
| | | onMounted(async () => { |
| | | // 若URL带有Token参数(第三方登录) |
| | | const accessToken = route.query.token; |
| | | // if (accessToken) await saveTokenAndInitRoutes(accessToken); |
| | | // watch( |
| | | // () => themeConfig.value.isLoaded, |
| | | // isLoaded => { |
| | | // if (isLoaded) { |
| | | // 获取登录配置 |
| | | // state.hideTenantForLogin = themeConfig.value.hideTenantForLogin ?? true; |
| | | // state.secondVerEnabled = themeConfig.value.secondVer ?? true; |
| | | // state.captchaEnabled = themeConfig.value.captcha ?? true; |
| | | |
| | | // 获取验证码 |
| | | getCaptcha(); |
| | | exRole().then(res => { |
| | | state.roleList = res.result; |
| | | }); |
| | | // 注册验证码过期计时器 |
| | | // if (state.captchaEnabled) { |
| | | timer = setInterval(() => { |
| | | if (state.expirySeconds > 0) state.expirySeconds -= 1; |
| | | }, 1000); |
| | | |
| | | // } |
| | | // } |
| | | // }, |
| | | // { immediate: true } |
| | | // ); |
| | | }); |
| | | // 页面卸载 |
| | | onUnmounted(() => { |
| | | // 销毁验证码过期计时器 |
| | | clearInterval(timer); |
| | | timer = null; |
| | | clearInterval(phonetimer); |
| | | phonetimer = null; |
| | | }); |
| | | const onLogin = async (formEl: FormInstance | undefined) => { |
| | | if (!formEl) return; |
| | | await formEl.validate(valid => { |
| | |
| | | loading.value = true; |
| | | useUserStoreHook() |
| | | .loginByUsername({ |
| | | username: ruleForm.username, |
| | | password: ruleForm.password |
| | | phone: state.ruleForm.phone, |
| | | code: state.ruleForm.phoneVCode, |
| | | exRuleCode: state.ruleForm.exRoleCode |
| | | }) |
| | | .then(res => { |
| | | if (res.success) { |
| | | if (res.code == 200) { |
| | | // 获取后端路由 |
| | | return initRouter().then(() => { |
| | | disabled.value = true; |
| | | router |
| | | .push(getTopMenu(true).path) |
| | | .push(getTopMenu(true)?.path) |
| | | .then(() => { |
| | | message("登录成功", { type: "success" }); |
| | | }) |
| | | .finally(() => (disabled.value = false)); |
| | | }); |
| | | } else { |
| | | message("登录失败", { type: "error" }); |
| | | message(res?.message || "登录失败", { type: "error" }); |
| | | } |
| | | }) |
| | | .finally(() => (loading.value = false)); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const sendValidationCode = async () => { |
| | | if (!state.ruleForm.phone) { |
| | | return message("请先输入手机号", { type: "warning" }); |
| | | } |
| | | if (!state.ruleForm.code) { |
| | | return message("请先输入验证码", { type: "warning" }); |
| | | } |
| | | const res = await phoneNumberCode(state.ruleForm); |
| | | if (res?.code != 200) { |
| | | return message(res?.message, { type: "warning" }); |
| | | } |
| | | state.phoneSeconds = 60; |
| | | phonetimer = setInterval(() => { |
| | | if (state.phoneSeconds > 0) state.phoneSeconds -= 1; |
| | | }, 1000); |
| | | }; |
| | | const immediateDebounce: any = debounce( |
| | | formRef => onLogin(formRef), |
| | | 1000, |
| | |
| | | |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | :model="ruleForm" |
| | | :model="state.ruleForm" |
| | | :rules="loginRules" |
| | | size="large" |
| | | > |
| | | <Motion :delay="150"> |
| | | <el-form-item prop="exRoleCode"> |
| | | <el-radio-group v-model="state.ruleForm.exRoleCode"> |
| | | <el-radio |
| | | v-for="item in state.roleList" |
| | | :key="item.id" |
| | | :value="item.code" |
| | | >{{ item.name }}</el-radio |
| | | > |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </Motion> |
| | | <Motion :delay="100"> |
| | | <el-form-item |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请输入账号', |
| | | message: '请输入手机号', |
| | | trigger: 'blur' |
| | | } |
| | | ]" |
| | | prop="username" |
| | | prop="phone" |
| | | > |
| | | <el-input |
| | | v-model="ruleForm.username" |
| | | v-model="state.ruleForm.phone" |
| | | clearable |
| | | placeholder="账号" |
| | | :prefix-icon="useRenderIcon(User)" |
| | | placeholder="手机号" |
| | | /> |
| | | </el-form-item> |
| | | </Motion> |
| | | |
| | | <Motion :delay="150"> |
| | | <el-form-item prop="password"> |
| | | <el-form-item class="login-animation3" prop="code"> |
| | | <el-col :span="15"> |
| | | <el-input |
| | | ref="codeRef" |
| | | v-model="state.ruleForm.code" |
| | | text |
| | | maxlength="4" |
| | | placeholder="请输入验证码" |
| | | clearable |
| | | autocomplete="off" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="1" /> |
| | | <el-col :span="8"> |
| | | <div |
| | | :class="[ |
| | | state.expirySeconds > 0 |
| | | ? 'login-content-code' |
| | | : 'login-content-code-expired' |
| | | ]" |
| | | @click="getCaptcha" |
| | | > |
| | | <img |
| | | class="login-content-code-img" |
| | | width="130px" |
| | | height="38px" |
| | | :src="state.captchaImage" |
| | | style="cursor: pointer" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | </el-form-item> |
| | | |
| | | <!-- <el-form-item prop="password"> |
| | | <el-input |
| | | v-model="ruleForm.password" |
| | | clearable |
| | |
| | | placeholder="密码" |
| | | :prefix-icon="useRenderIcon(Lock)" |
| | | /> |
| | | </el-form-item> --> |
| | | </Motion> |
| | | <Motion :delay="150"> |
| | | <el-form-item prop="phoneVCode"> |
| | | <el-input |
| | | v-model.number="state.ruleForm.phoneVCode" |
| | | class="form-input" |
| | | placeholder="请输入验证码" |
| | | > |
| | | <template #suffix> |
| | | <span v-if="state.phoneSeconds == 0" id="suffix-span"> |
| | | <span |
| | | id="suffix-span-2" |
| | | ref="spanRef" |
| | | @click="sendValidationCode(state.ruleForm.phone)" |
| | | > |
| | | 获取验证码 |
| | | </span> |
| | | </span> |
| | | <span v-else id="suffix-span"> |
| | | <span id="suffix-span-2" ref="spanRef"> |
| | | {{ state.phoneSeconds }}秒后重新获取 |
| | | </span> |
| | | </span> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </Motion> |
| | | |
| | | <Motion :delay="250"> |
| | | <el-button |
| | | class="w-full mt-4!" |
| | |
| | | :deep(.el-input-group__append, .el-input-group__prepend) { |
| | | padding: 0; |
| | | } |
| | | #suffix-span { |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| | |
| | | <el-form |
| | | ref="ruleFormRef" |
| | | style="max-width: 600px" |
| | | :model="ruleForm" |
| | | :rules="rules" |
| | | :model="state.ruleForm" |
| | | :rules="state.rules" |
| | | label-width="auto" |
| | | size="large" |
| | | > |
| | | <el-form-item label="注册身份" prop="resource"> |
| | | <el-radio-group v-model="ruleForm.resource"> |
| | | <el-radio value="Sponsorship">供应商</el-radio> |
| | | <el-radio value="Venue">代理机构</el-radio> |
| | | <el-radio value="cgr">采购人</el-radio> |
| | | <el-form-item label="注册身份" prop="exRoleCode"> |
| | | <el-radio-group v-model="state.ruleForm.exRoleCode"> |
| | | <el-radio |
| | | v-for="item in state.roleList" |
| | | :key="item.id" |
| | | :value="item.code" |
| | | >{{ item.name }}</el-radio |
| | | > |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | <el-form-item label="企业名称" prop="name"> |
| | | <!-- <el-form-item label="企业名称" prop="name"> |
| | | <el-input |
| | | v-model="ruleForm.name" |
| | | placeholder="请输入营业执照上的企业名称" |
| | |
| | | </el-form-item> |
| | | <el-form-item label="用户名" prop="region"> |
| | | <el-input v-model="ruleForm.region" placeholder="请输入用户名" /> |
| | | </el-form-item> --> |
| | | <el-form-item label="姓名" prop="nickName"> |
| | | <el-input |
| | | v-model="state.ruleForm.nickName" |
| | | placeholder="请输入姓名" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="登录密码" prop="password"> |
| | | <el-form-item label="手机号码" prop="phone"> |
| | | <el-input |
| | | v-model="state.ruleForm.phone" |
| | | placeholder="请输入您的手机号码" |
| | | /> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="登录密码" prop="password"> |
| | | <el-input |
| | | v-model="ruleForm.password" |
| | | placeholder="8-20位数字+大小写字母+特殊字符的组合" |
| | |
| | | v-model="ruleForm.repassword" |
| | | placeholder="请输入联系人姓名" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="邮箱" prop="repassword"> |
| | | </el-form-item> --> |
| | | <el-form-item label="邮箱" prop="email"> |
| | | <el-input |
| | | v-model="ruleForm.repassword" |
| | | v-model="state.ruleForm.email" |
| | | placeholder="请输入联系邮箱" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="手机号码" prop="repassword"> |
| | | <el-input |
| | | v-model="ruleForm.repassword" |
| | | placeholder="请输入您的手机号码" |
| | | /> |
| | | <el-form-item label="验证码" class="login-animation3" prop="code"> |
| | | <el-col :span="15"> |
| | | <el-input |
| | | ref="codeRef" |
| | | v-model="state.ruleForm.code" |
| | | text |
| | | maxlength="4" |
| | | placeholder="请输入验证码" |
| | | clearable |
| | | autocomplete="off" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="1" /> |
| | | <el-col :span="8"> |
| | | <div |
| | | :class="[ |
| | | state.expirySeconds > 0 |
| | | ? 'login-content-code' |
| | | : 'login-content-code-expired' |
| | | ]" |
| | | @click="getCaptcha" |
| | | > |
| | | <img |
| | | class="login-content-code-img" |
| | | width="130px" |
| | | height="38px" |
| | | :src="state.captchaImage" |
| | | style="cursor: pointer" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | </el-form-item> |
| | | <el-form-item label="手机号码" prop="repassword"> |
| | | <el-form-item prop="phoneVCode" label="手机验证码"> |
| | | <el-input |
| | | v-model="ruleForm.repassword" |
| | | placeholder="请输入您的手机号码" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item prop="validationCode" label="手机验证码"> |
| | | <el-input |
| | | v-model.number="ruleForm.validationCode" |
| | | v-model.number="state.ruleForm.phoneVCode" |
| | | class="form-input" |
| | | placeholder="请输入验证码" |
| | | > |
| | | <template #suffix> |
| | | <span id="suffix-span"> |
| | | <span v-if="state.phoneSeconds == 0" id="suffix-span"> |
| | | <span |
| | | id="suffix-span-2" |
| | | ref="spanRef" |
| | | @click="sendValidationCode(ruleForm.repassword)" |
| | | @click="sendValidationCode(state.ruleForm.phone)" |
| | | > |
| | | {{ isSendValidationCode }} |
| | | 获取验证码 |
| | | </span> |
| | | </span> |
| | | <span v-else id="suffix-span"> |
| | | <span id="suffix-span-2" ref="spanRef"> |
| | | {{ state.phoneSeconds }}秒后重新获取 |
| | | </span> |
| | | </span> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | <el-form-item prop="repassword" label=" "> |
| | | <el-checkbox value="Online activities" name="type"> |
| | | 我已阅读并同意 《非政采招标采购交易平台用户协议》 |
| | | </el-checkbox> |
| | | <el-form-item label=" "> |
| | | <el-checkbox v-model="checked1" name="type" />我已阅读并同意 |
| | | <el-link type="primary" :underline="false" |
| | | >《非政采招标采购交易平台用户协议》</el-link |
| | | > |
| | | </el-form-item> |
| | | <el-form-item label=" "> |
| | | <el-button type="primary" @click="submitForm(ruleFormRef)"> |
| | |
| | | </el-button> |
| | | </el-form-item> |
| | | <el-form-item label=" "> |
| | | <span>已有账号?立即登录</span> |
| | | <span>已有账号?</span> |
| | | <el-link type="primary" :underline="false">立即登录</el-link> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script lang="ts" setup> |
| | | import { reactive, ref } from "vue"; |
| | | import { |
| | | reactive, |
| | | computed, |
| | | ref, |
| | | onMounted, |
| | | defineAsyncComponent, |
| | | onUnmounted, |
| | | watch |
| | | } from "vue"; |
| | | import type { FormInstance, FormRules } from "element-plus"; |
| | | |
| | | interface RuleForm { |
| | | name: string; |
| | | region: string; |
| | | count: string; |
| | | password: string; |
| | | repassword: string; |
| | | validationCode: string; |
| | | date1: string; |
| | | date2: string; |
| | | delivery: boolean; |
| | | location: string; |
| | | type: string[]; |
| | | resource: string; |
| | | desc: string; |
| | | } |
| | | |
| | | const ruleFormRef = ref<FormInstance>(); |
| | | const ruleForm = reactive<RuleForm>({ |
| | | name: "", |
| | | region: "", |
| | | password: "", |
| | | validationCode: "", |
| | | repassword: "", |
| | | count: "", |
| | | date1: "", |
| | | date2: "", |
| | | delivery: false, |
| | | location: "", |
| | | type: [], |
| | | resource: "", |
| | | desc: "" |
| | | import { |
| | | captcha, |
| | | phoneNumberCode, |
| | | register, |
| | | exRole |
| | | } from "@/api/register/index.ts"; |
| | | import { useRoute, useRouter } from "vue-router"; |
| | | import { message } from "@/utils/message"; |
| | | defineOptions({ |
| | | name: "Register" |
| | | }); |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const checked1 = ref(false); |
| | | const state = reactive({ |
| | | isShowPassword: false, |
| | | ruleForm: { |
| | | account: "", |
| | | nickName: "", |
| | | phone: "", |
| | | phoneVCode: "", |
| | | // tenantId: props.tenantInfo.id, |
| | | code: "", |
| | | codeId: 0, |
| | | email: "", |
| | | exRoleCode: "" |
| | | }, |
| | | rules: { |
| | | phoneVCode: [ |
| | | { |
| | | required: true, |
| | | message: "请输入手机验证码", |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | nickName: [ |
| | | { |
| | | required: true, |
| | | message: "请输入姓名", |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | phone: [ |
| | | { |
| | | required: true, |
| | | message: "请输入您的手机号码", |
| | | trigger: "blur" |
| | | } |
| | | ], |
| | | exRoleCode: [ |
| | | { |
| | | required: true, |
| | | message: "请选择角色", |
| | | trigger: "blur" |
| | | } |
| | | ] |
| | | // code: [{ required: true, message: t('message.account.placeholder4'), trigger: 'blur' }], |
| | | }, |
| | | loading: { |
| | | signIn: false |
| | | }, |
| | | captchaImage: "", |
| | | rotateVerifyVisible: false, |
| | | // rotateVerifyImg: verifyImg, |
| | | // rotateVerifyImg: themeConfig.value.logoUrl, |
| | | secondVerEnabled: false, |
| | | // captchaEnabled: false, |
| | | isPassRotate: false, |
| | | capsLockVisible: false, |
| | | hideTenantForLogin: false, |
| | | expirySeconds: 60, // 验证码过期时间 |
| | | phoneSeconds: 0, // 手机验证码倒计时 |
| | | roleList: [] |
| | | }); |
| | | // 验证码过期计时器 |
| | | let timer: any = null; |
| | | let phonetimer: any = null; |
| | | |
| | | // 页面初始化 |
| | | onMounted(async () => { |
| | | // 若URL带有Token参数(第三方登录) |
| | | const accessToken = route.query.token; |
| | | // if (accessToken) await saveTokenAndInitRoutes(accessToken); |
| | | // watch( |
| | | // () => themeConfig.value.isLoaded, |
| | | // isLoaded => { |
| | | // if (isLoaded) { |
| | | // 获取登录配置 |
| | | // state.hideTenantForLogin = themeConfig.value.hideTenantForLogin ?? true; |
| | | // state.secondVerEnabled = themeConfig.value.secondVer ?? true; |
| | | // state.captchaEnabled = themeConfig.value.captcha ?? true; |
| | | |
| | | // 获取验证码 |
| | | getCaptcha(); |
| | | exRole().then(res => { |
| | | state.roleList = res.result; |
| | | }); |
| | | // 注册验证码过期计时器 |
| | | // if (state.captchaEnabled) { |
| | | timer = setInterval(() => { |
| | | if (state.expirySeconds > 0) state.expirySeconds -= 1; |
| | | }, 1000); |
| | | |
| | | // } |
| | | // } |
| | | // }, |
| | | // { immediate: true } |
| | | // ); |
| | | |
| | | // 检测大小写按键/CapsLK |
| | | document.addEventListener("keyup", handleKeyPress); |
| | | }); |
| | | |
| | | // 页面卸载 |
| | | onUnmounted(() => { |
| | | // 销毁验证码过期计时器 |
| | | clearInterval(timer); |
| | | timer = null; |
| | | clearInterval(phonetimer); |
| | | phonetimer = null; |
| | | |
| | | document.removeEventListener("keyup", handleKeyPress); |
| | | }); |
| | | |
| | | // 检测大小写按键 |
| | | const handleKeyPress = (e: KeyboardEvent) => { |
| | | if (e.getModifierState != undefined) |
| | | state.capsLockVisible = e.getModifierState("CapsLock"); |
| | | }; |
| | | // 获取验证码 |
| | | const getCaptcha = async () => { |
| | | // if (!state.captchaEnabled) return; |
| | | |
| | | state.ruleForm.code = ""; |
| | | const res = await captcha(); |
| | | console.log(res); |
| | | |
| | | state.captchaImage = "data:text/html;base64," + res.result?.img; |
| | | state.expirySeconds = res.result?.expirySeconds; |
| | | state.ruleForm.codeId = res.result?.id; |
| | | }; |
| | | const ruleFormRef = ref<FormInstance>(); |
| | | |
| | | const locationOptions = ["Home", "Company", "School"]; |
| | | |
| | | const rules = reactive<FormRules<RuleForm>>({ |
| | | name: [ |
| | | { required: true, message: "Please input Activity name", trigger: "blur" }, |
| | | { min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur" } |
| | | ], |
| | | region: [ |
| | | { |
| | | required: true, |
| | | message: "Please select Activity zone", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | count: [ |
| | | { |
| | | required: true, |
| | | message: "Please select Activity count", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | date1: [ |
| | | { |
| | | type: "date", |
| | | required: true, |
| | | message: "Please pick a date", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | date2: [ |
| | | { |
| | | type: "date", |
| | | required: true, |
| | | message: "Please pick a time", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | location: [ |
| | | { |
| | | required: true, |
| | | message: "Please select a location", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | type: [ |
| | | { |
| | | type: "array", |
| | | required: true, |
| | | message: "Please select at least one activity type", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | resource: [ |
| | | { |
| | | required: true, |
| | | message: "Please select activity resource", |
| | | trigger: "change" |
| | | } |
| | | ], |
| | | desc: [ |
| | | { required: true, message: "Please input activity form", trigger: "blur" } |
| | | ] |
| | | }); |
| | | // 验证码区域文字说明 |
| | | const spanRef = ref(); |
| | | const isSendValidationCode = ref<string>("获取验证码"); |
| | | const submitForm = async (formEl: FormInstance | undefined) => { |
| | | if (!formEl) return; |
| | | await formEl.validate((valid, fields) => { |
| | | if (valid) { |
| | | console.log("submit!"); |
| | | } else { |
| | | console.log("error submit!", fields); |
| | | } |
| | | state.ruleForm.account = state.ruleForm.phone; |
| | | register(state.ruleForm).then(res => { |
| | | if (res?.code == 200) { |
| | | router.replace("/RegisterSucess"); |
| | | return message("注册成功!", { type: "success" }); |
| | | } else { |
| | | return message(res?.message, { type: "warning" }); |
| | | } |
| | | }); |
| | | } else if (!checked1.value) |
| | | return message("请勾选用户协议", { type: "warning" }); |
| | | }); |
| | | }; |
| | | |
| | | const sendValidationCode = () => {}; |
| | | const sendValidationCode = async () => { |
| | | if (!state.ruleForm.phone) { |
| | | return message("请先输入手机号", { type: "warning" }); |
| | | } |
| | | if (!state.ruleForm.code) { |
| | | return message("请先输入验证码", { type: "warning" }); |
| | | } |
| | | const res = await phoneNumberCode(state.ruleForm); |
| | | if (res?.code != 200) { |
| | | return message(res?.message, { type: "warning" }); |
| | | } |
| | | state.phoneSeconds = 60; |
| | | phonetimer = setInterval(() => { |
| | | if (state.phoneSeconds > 0) state.phoneSeconds -= 1; |
| | | }, 1000); |
| | | }; |
| | | |
| | | const resetForm = (formEl: FormInstance | undefined) => { |
| | | if (!formEl) return; |
| | |
| | | } |
| | | } |
| | | } |
| | | .el-form-item { |
| | | align-items: center; |
| | | } |
| | | #suffix-span { |
| | | cursor: pointer; |
| | | } |
| | | .login-content-code { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-around; |
| | | position: relative; |
| | | |
| | | .login-content-code-img { |
| | | width: 100%; |
| | | height: 40px; |
| | | line-height: 40px; |
| | | background-color: #ffffff; |
| | | border: 1px solid rgb(220, 223, 230); |
| | | cursor: pointer; |
| | | transition: all ease 0.2s; |
| | | border-radius: 4px; |
| | | user-select: none; |
| | | |
| | | &:hover { |
| | | border-color: #c0c4cc; |
| | | transition: all ease 0.2s; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .login-content-code-expired { |
| | | @extend .login-content-code; |
| | | &::before { |
| | | content: "验证码已过期"; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | border-radius: 4px; |
| | | background-color: rgba(0, 0, 0, 0.5); |
| | | color: #ffffff; |
| | | text-align: center; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div class="content"> |
| | | <div class="header"> |
| | | <div class="headimg"> |
| | | <img width="167px" height="44px" src="@/assets/home/logo.png" alt="" /> |
| | | </div> |
| | | </div> |
| | | <div class="center w-[70%] h-[584px] bg-white mx-auto mt-25"> |
| | | <el-button type="primary">马上登录</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | defineOptions({ |
| | | name: "RegisterSucess" |
| | | }); |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | .content { |
| | | background-color: #f8f8f8; |
| | | width: 100%; |
| | | height: 100%; |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 80px; |
| | | width: 100%; |
| | | margin: 0 auto; |
| | | background-color: #fff; |
| | | .headimg { |
| | | width: 1200px; |
| | | margin: 0 auto; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | port: VITE_PORT, |
| | | host: "0.0.0.0", |
| | | // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy |
| | | proxy: {}, |
| | | proxy: { |
| | | "/api": { |
| | | // 这里填写后端地址 |
| | | target: "http://192.168.0.36:5005", |
| | | changeOrigin: true, |
| | | rewrite: path => path.replace(/^\/api/, "") |
| | | } |
| | | }, |
| | | // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布 |
| | | warmup: { |
| | | clientFiles: ["./index.html", "./src/{views,components}/*"] |