mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-10-31 10:18:43 +08:00 
			
		
		
		
	| @@ -1,16 +1,19 @@ | ||||
| import request from '@/config/axios' | ||||
| import { getRefreshToken } from '@/utils/auth' | ||||
| import type { UserLoginVO } from './types' | ||||
| import { service } from '@/config/axios/service' | ||||
|  | ||||
| export interface CodeImgResult { | ||||
|   captchaOnOff: boolean | ||||
|   img: string | ||||
|   uuid: string | ||||
| } | ||||
|  | ||||
| export interface SmsCodeVO { | ||||
|   mobile: string | ||||
|   scene: number | ||||
| } | ||||
|  | ||||
| export interface SmsLoginVO { | ||||
|   mobile: string | ||||
|   code: string | ||||
| @@ -71,3 +74,51 @@ export const getCode = (data) => { | ||||
| export const reqCheck = (data) => { | ||||
|   return request.postOriginal({ url: 'system/captcha/check', data }) | ||||
| } | ||||
|  | ||||
| // ========== OAUTH 2.0 相关 ========== | ||||
| export type scopesType = string[] | ||||
| export interface paramsType { | ||||
|   responseType: string | ||||
|   clientId: string | ||||
|   redirectUri: string | ||||
|   state: string | ||||
|   scopes: scopesType | ||||
| } | ||||
| export const getAuthorize = (clientId) => { | ||||
|   return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId }) | ||||
| } | ||||
|  | ||||
| export function authorize( | ||||
|   responseType: string, | ||||
|   clientId: string, | ||||
|   redirectUri: string, | ||||
|   state: string, | ||||
|   autoApprove: boolean, | ||||
|   checkedScopes: scopesType, | ||||
|   uncheckedScopes: scopesType | ||||
| ) { | ||||
|   // 构建 scopes | ||||
|   const scopes = {} | ||||
|   for (const scope of checkedScopes) { | ||||
|     scopes[scope] = true | ||||
|   } | ||||
|   for (const scope of uncheckedScopes) { | ||||
|     scopes[scope] = false | ||||
|   } | ||||
|   // 发起请求 | ||||
|   return service({ | ||||
|     url: '/system/oauth2/authorize', | ||||
|     headers: { | ||||
|       'Content-type': 'application/x-www-form-urlencoded' | ||||
|     }, | ||||
|     params: { | ||||
|       response_type: responseType, | ||||
|       client_id: clientId, | ||||
|       redirect_uri: redirectUri, | ||||
|       state: state, | ||||
|       auto_approve: autoApprove, | ||||
|       scope: JSON.stringify(scopes) | ||||
|     }, | ||||
|     method: 'post' | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import axios, { | ||||
|   AxiosError, | ||||
|   AxiosInstance, | ||||
|   AxiosRequestHeaders, | ||||
|   AxiosResponse, | ||||
|   AxiosError, | ||||
|   InternalAxiosRequestConfig | ||||
| } from 'axios' | ||||
|  | ||||
| @@ -230,7 +230,7 @@ const handleAuthorized = () => { | ||||
|       wsCache.clear() | ||||
|       removeToken() | ||||
|       isRelogin.show = false | ||||
|       window.location.href = import.meta.env.VITE_BASE_PATH | ||||
|       window.location.href = '/login?redirect=/sso?' + window.location.href.split('?')[1] | ||||
|     }) | ||||
|   } | ||||
|   return Promise.reject(t('sys.api.timeoutMessage')) | ||||
|   | ||||
| @@ -129,6 +129,12 @@ export default { | ||||
|     btnMobile: '手机登录', | ||||
|     btnQRCode: '二维码登录', | ||||
|     qrcode: '扫描二维码登录', | ||||
|     sso: { | ||||
|       user: { | ||||
|         read: '访问你的个人信息', | ||||
|         write: '修改你的个人信息' | ||||
|       } | ||||
|     }, | ||||
|     btnRegister: '注册', | ||||
|     SmsSendMsg: '验证码已发送' | ||||
|   }, | ||||
| @@ -352,6 +358,7 @@ export default { | ||||
|     login: { | ||||
|       backSignIn: '返回', | ||||
|       signInFormTitle: '登录', | ||||
|       ssoFormTitle: '三方授权', | ||||
|       mobileSignInFormTitle: '手机登录', | ||||
|       qrSignInFormTitle: '二维码登录', | ||||
|       signUpFormTitle: '注册', | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import type { App } from 'vue' | ||||
| import type { RouteRecordRaw } from 'vue-router' | ||||
| import { createRouter, createWebHashHistory } from 'vue-router' | ||||
| import { createRouter, createWebHistory } from 'vue-router' | ||||
| import remainingRouter from './modules/remaining' | ||||
|  | ||||
| // 创建路由实例 | ||||
| const router = createRouter({ | ||||
|   history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# | ||||
|   history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带# | ||||
|   strict: true, | ||||
|   routes: remainingRouter as RouteRecordRaw[], | ||||
|   scrollBehavior: () => ({ left: 0, top: 0 }) | ||||
|   | ||||
| @@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [ | ||||
|       noTagsView: true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     path: '/sso', | ||||
|     component: () => import('@/views/Login/Login.vue'), | ||||
|     name: 'SSOLogin', | ||||
|     meta: { | ||||
|       hidden: true, | ||||
|       title: t('router.login'), | ||||
|       noTagsView: true | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     path: '/403', | ||||
|     component: () => import('@/views/Error/403.vue'), | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/types/auto-components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -21,15 +21,14 @@ declare module '@vue/runtime-core' { | ||||
|     Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default'] | ||||
|     Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default'] | ||||
|     DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] | ||||
|     DocAlert: typeof import('./../components/DocAlert/index.vue')['default'] | ||||
|     Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] | ||||
|     Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] | ||||
|     ElAlert: typeof import('element-plus/es')['ElAlert'] | ||||
|     ElBadge: typeof import('element-plus/es')['ElBadge'] | ||||
|     ElButton: typeof import('element-plus/es')['ElButton'] | ||||
|     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] | ||||
|     ElCard: typeof import('element-plus/es')['ElCard'] | ||||
|     ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] | ||||
|     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] | ||||
|     ElCol: typeof import('element-plus/es')['ElCol'] | ||||
|     ElCollapse: typeof import('element-plus/es')['ElCollapse'] | ||||
|     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] | ||||
| @@ -74,12 +73,8 @@ declare module '@vue/runtime-core' { | ||||
|     ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] | ||||
|     ElTabPane: typeof import('element-plus/es')['ElTabPane'] | ||||
|     ElTabs: typeof import('element-plus/es')['ElTabs'] | ||||
|     ElTag: typeof import('element-plus/es')['ElTag'] | ||||
|     ElTimeline: typeof import('element-plus/es')['ElTimeline'] | ||||
|     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] | ||||
|     ElTooltip: typeof import('element-plus/es')['ElTooltip'] | ||||
|     ElTree: typeof import('element-plus/es')['ElTree'] | ||||
|     ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] | ||||
|     ElUpload: typeof import('element-plus/es')['ElUpload'] | ||||
|     Error: typeof import('./../components/Error/src/Error.vue')['default'] | ||||
|     FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] | ||||
|   | ||||
| @@ -9,19 +9,19 @@ | ||||
|       > | ||||
|         <!-- 左上角的 logo + 系统标题 --> | ||||
|         <div class="flex items-center relative text-white"> | ||||
|           <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" /> | ||||
|           <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" /> | ||||
|           <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span> | ||||
|         </div> | ||||
|         <!-- 左边的背景图 + 欢迎语 --> | ||||
|         <div class="flex justify-center items-center h-[calc(100%-60px)]"> | ||||
|           <TransitionGroup | ||||
|             appear | ||||
|             tag="div" | ||||
|             enter-active-class="animate__animated animate__bounceInLeft" | ||||
|             tag="div" | ||||
|           > | ||||
|             <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" /> | ||||
|             <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div> | ||||
|             <div class="mt-5 font-normal text-white text-14px" key="3"> | ||||
|             <img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" /> | ||||
|             <div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div> | ||||
|             <div key="3" class="mt-5 font-normal text-white text-14px"> | ||||
|               {{ t('login.message') }} | ||||
|             </div> | ||||
|           </TransitionGroup> | ||||
| @@ -31,7 +31,7 @@ | ||||
|         <!-- 右上角的主题、语言选择 --> | ||||
|         <div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end"> | ||||
|           <div class="flex items-center @2xl:hidden @xl:hidden"> | ||||
|             <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" /> | ||||
|             <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" /> | ||||
|             <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span> | ||||
|           </div> | ||||
|           <div class="flex justify-end items-center space-x-10px"> | ||||
| @@ -52,20 +52,23 @@ | ||||
|             <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> | ||||
|             <!-- 注册 --> | ||||
|             <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> | ||||
|             <!-- 三方登录 --> | ||||
|             <SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" /> | ||||
|           </div> | ||||
|         </Transition> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script setup lang="ts"> | ||||
| <script lang="ts" setup> | ||||
| import { underlineToHump } from '@/utils' | ||||
|  | ||||
| import { useDesign } from '@/hooks/web/useDesign' | ||||
| import { useAppStore } from '@/store/modules/app' | ||||
| import { ThemeSwitch } from '@/layout/components/ThemeSwitch' | ||||
| import { LocaleDropdown } from '@/layout/components/LocaleDropdown' | ||||
| import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components' | ||||
|  | ||||
| import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components' | ||||
|  | ||||
| const { t } = useI18n() | ||||
| const appStore = useAppStore() | ||||
|   | ||||
| @@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon' | ||||
| import * as authUtil from '@/utils/auth' | ||||
| import { usePermissionStore } from '@/store/modules/permission' | ||||
| import * as LoginApi from '@/api/login' | ||||
| import { LoginStateEnum, useLoginState, useFormValid } from './useLogin' | ||||
| import { LoginStateEnum, useFormValid, useLoginState } from './useLogin' | ||||
|  | ||||
| const { t } = useI18n() | ||||
| const message = useMessage() | ||||
| @@ -240,7 +240,12 @@ const handleLogin = async (params) => { | ||||
|     if (!redirect.value) { | ||||
|       redirect.value = '/' | ||||
|     } | ||||
|     push({ path: redirect.value || permissionStore.addRouters[0].path }) | ||||
|     // 判断是否为SSO登录 | ||||
|     if (redirect.value.indexOf('sso') !== -1) { | ||||
|       window.location.href = window.location.href.replace('/login?redirect=', '') | ||||
|     } else { | ||||
|       push({ path: redirect.value || permissionStore.addRouters[0].path }) | ||||
|     } | ||||
|   } catch { | ||||
|     loginLoading.value = false | ||||
|   } finally { | ||||
| @@ -291,6 +296,7 @@ onMounted(() => { | ||||
|     color: var(--el-color-primary) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .login-code { | ||||
|   width: 100%; | ||||
|   height: 38px; | ||||
|   | ||||
| @@ -16,7 +16,8 @@ const getFormTitle = computed(() => { | ||||
|     [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'), | ||||
|     [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'), | ||||
|     [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'), | ||||
|     [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle') | ||||
|     [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'), | ||||
|     [LoginStateEnum.SSO]: t('sys.login.ssoFormTitle') | ||||
|   } | ||||
|   return titleObj[unref(getLoginState)] | ||||
| }) | ||||
|   | ||||
							
								
								
									
										187
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/views/Login/components/SSOLogin.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| <template> | ||||
|   <!-- 表单 --> | ||||
|   <div v-show="getShow" class="form-cont"> | ||||
|     <!--    <LoginFormTitle style="width: 100%" />--> | ||||
|     <el-tabs class="form" style="float: none" value="uname"> | ||||
|       <el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname" /> | ||||
|     </el-tabs> | ||||
|     <div> | ||||
|       <el-form ref="ssoForm" :model="loginForm" class="login-form"> | ||||
|         <!-- 授权范围的选择 --> | ||||
|         此第三方应用请求获得以下权限: | ||||
|         <el-form-item prop="scopes"> | ||||
|           <el-checkbox-group v-model="loginForm.scopes"> | ||||
|             <el-checkbox | ||||
|               v-for="scope in params.scopes" | ||||
|               :key="scope" | ||||
|               :label="scope" | ||||
|               style="display: block; margin-bottom: -10px" | ||||
|               >{{ formatScope(scope) }} | ||||
|             </el-checkbox> | ||||
|           </el-checkbox-group> | ||||
|         </el-form-item> | ||||
|         <!-- 下方的登录按钮 --> | ||||
|         <el-form-item style="width: 100%"> | ||||
|           <el-button | ||||
|             :loading="loading" | ||||
|             size="small" | ||||
|             style="width: 60%" | ||||
|             type="primary" | ||||
|             @click.prevent="handleAuthorize(true)" | ||||
|           > | ||||
|             <span v-if="!loading">同意授权</span> | ||||
|             <span v-else>授 权 中...</span> | ||||
|           </el-button> | ||||
|           <el-button size="small" style="width: 36%" @click.prevent="handleAuthorize(false)" | ||||
|             >拒绝 | ||||
|           </el-button> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| <script lang="ts" name="SSOLogin" setup> | ||||
| // import LoginFormTitle from './LoginFormTitle.vue' // TODO 艿艿你看看要不要这个表头 | ||||
| import { authorize, getAuthorize, paramsType, scopesType } from '@/api/login' | ||||
| import { LoginStateEnum, useLoginState } from './useLogin' | ||||
| import type { RouteLocationNormalizedLoaded } from 'vue-router' | ||||
|  | ||||
| const { t } = useI18n() | ||||
| const ssoForm = ref() // 表单Ref | ||||
| const { getLoginState, setLoginState } = useLoginState() | ||||
| const getShow = computed(() => unref(getLoginState) === LoginStateEnum.SSO) | ||||
| const loginForm = reactive<{ scopes: scopesType }>({ | ||||
|   scopes: [] // 已选中的 scope 数组 | ||||
| }) | ||||
| const params = reactive<paramsType>({ | ||||
|   // URL 上的 client_id、scope 等参数 | ||||
|   responseType: '', | ||||
|   clientId: '', | ||||
|   redirectUri: '', | ||||
|   state: '', | ||||
|   scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取 | ||||
| }) // 表单Ref | ||||
| const client = ref({ | ||||
|   // 客户端信息 | ||||
|   name: '', | ||||
|   logo: '' | ||||
| }) | ||||
| const loading = ref(false) | ||||
| const handleAuthorize = (approved) => { | ||||
|   ssoForm.value.validate((valid) => { | ||||
|     if (!valid) { | ||||
|       return | ||||
|     } | ||||
|     loading.value = true | ||||
|     // 计算 checkedScopes + uncheckedScopes | ||||
|     let checkedScopes | ||||
|     let uncheckedScopes | ||||
|     if (approved) { | ||||
|       // 同意授权,按照用户的选择 | ||||
|       checkedScopes = loginForm.scopes | ||||
|       uncheckedScopes = params.scopes.filter((item) => checkedScopes.indexOf(item) === -1) | ||||
|     } else { | ||||
|       // 拒绝,则都是取消 | ||||
|       checkedScopes = [] | ||||
|       uncheckedScopes = params.scopes | ||||
|     } | ||||
|     // 提交授权的请求 | ||||
|     doAuthorize(false, checkedScopes, uncheckedScopes) | ||||
|       .then((res) => { | ||||
|         const href = res.data | ||||
|         if (!href) { | ||||
|           return | ||||
|         } | ||||
|         location.href = href | ||||
|       }) | ||||
|       .finally(() => { | ||||
|         loading.value = false | ||||
|       }) | ||||
|   }) | ||||
| } | ||||
| const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => { | ||||
|   return authorize( | ||||
|     params.responseType, | ||||
|     params.clientId, | ||||
|     params.redirectUri, | ||||
|     params.state, | ||||
|     autoApprove, | ||||
|     checkedScopes, | ||||
|     uncheckedScopes | ||||
|   ) | ||||
| } | ||||
| const formatScope = (scope) => { | ||||
|   // 格式化 scope 授权范围,方便用户理解。 | ||||
|   // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。 | ||||
|   // TODO 这个之做了中文部分 | ||||
|   return t(`login.sso.${scope}`) | ||||
| } | ||||
| const route = useRoute() | ||||
| const init = () => { | ||||
|   // 防止在没有登录的情况下循环弹窗 | ||||
|   if (typeof route.query.client_id === 'undefined') return | ||||
|   // 解析参数 | ||||
|   // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write | ||||
|   // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read | ||||
|   params.responseType = route.query.response_type as string | ||||
|   params.clientId = route.query.client_id as string | ||||
|   params.redirectUri = route.query.redirect_uri as string | ||||
|   params.state = route.query.state as string | ||||
|   if (route.query.scope) { | ||||
|     params.scopes = (route.query.scope as string).split(' ') | ||||
|   } | ||||
|  | ||||
|   // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。 | ||||
|   if (params.scopes.length > 0) { | ||||
|     doAuthorize(true, params.scopes, []).then((res) => { | ||||
|       if (!res) { | ||||
|         console.log('自动授权未通过!') | ||||
|         return | ||||
|       } | ||||
|       location.href = res.data | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   // 获取授权页的基本信息 | ||||
|   getAuthorize(params.clientId).then((res) => { | ||||
|     client.value = res.client | ||||
|     // 解析 scope | ||||
|     let scopes | ||||
|     // 1.1 如果 params.scope 非空,则过滤下返回的 scopes | ||||
|     if (params.scopes.length > 0) { | ||||
|       scopes = [] | ||||
|       for (const scope of res.scopes) { | ||||
|         if (params.scopes.indexOf(scope.key) >= 0) { | ||||
|           scopes.push(scope) | ||||
|         } | ||||
|       } | ||||
|       // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它 | ||||
|     } else { | ||||
|       scopes = res.scopes | ||||
|       for (const scope of scopes) { | ||||
|         params.scopes.push(scope.key) | ||||
|       } | ||||
|     } | ||||
|     // 生成已选中的 checkedScopes | ||||
|     for (const scope of scopes) { | ||||
|       if (scope.value) { | ||||
|         loginForm.scopes.push(scope.key) | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| // =======SSO====== | ||||
| const { currentRoute } = useRouter() | ||||
| // 监听当前路由 | ||||
| watch( | ||||
|   () => currentRoute.value, | ||||
|   (route: RouteLocationNormalizedLoaded) => { | ||||
|     if (route.name === 'SSOLogin') { | ||||
|       setLoginState(LoginStateEnum.SSO) | ||||
|       init() | ||||
|     } | ||||
|   }, | ||||
|   { immediate: true } | ||||
| ) | ||||
| init() | ||||
| </script> | ||||
| @@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue' | ||||
| import LoginFormTitle from './LoginFormTitle.vue' | ||||
| import RegisterForm from './RegisterForm.vue' | ||||
| import QrCodeForm from './QrCodeForm.vue' | ||||
| import SSOLoginVue from './SSOLogin.vue' | ||||
|  | ||||
| export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm } | ||||
| export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue } | ||||
|   | ||||
| @@ -5,7 +5,8 @@ export enum LoginStateEnum { | ||||
|   REGISTER, | ||||
|   RESET_PASSWORD, | ||||
|   MOBILE, | ||||
|   QR_CODE | ||||
|   QR_CODE, | ||||
|   SSO | ||||
| } | ||||
|  | ||||
| const currentState = ref(LoginStateEnum.LOGIN) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 芋道源码
					芋道源码