Просмотр исходного кода

开票审批钉钉通知功能

huangguoce 3 недель назад
Родитель
Сommit
617af59699

+ 22 - 10
src/api/auth/loginService.js

@@ -16,6 +16,14 @@ export default {
 			data: data,
 		});
 	},
+	ddlogin: function (inputForm) {
+		return request({
+			url: prefix + "/user/sys/ddLogin",
+			method: "post",
+			data: inputForm,
+		});
+	},
+
 	logout: function () {
 		return request({
 			url: prefix + "/user/logout",
@@ -27,20 +35,24 @@ export default {
 			url: prefix + "/user/getLoginCodeNumber",
 			method: "get",
 			params: { userName: userName },
-		})
+		});
 	},
-	getPhoneCode (loginForm) {
+	getPhoneCode(loginForm) {
 		return request({
-			url: sysPrefix + '/sys/user/getPhoneCode',
-			method: 'get',
-			params: {mobile: loginForm},
-		})
+			url: sysPrefix + "/sys/user/getPhoneCode",
+			method: "get",
+			params: { mobile: loginForm },
+		});
 	},
 	savePwd: function (loginForm) {
 		return request({
-			url:sysPrefix + "/sys/user/saveNewPassword",
-			method:"get",
-			params:{mobile:loginForm.phoneNumber,code:loginForm.phoneCode,newPassword: loginForm.newPassword},
-		})
+			url: sysPrefix + "/sys/user/saveNewPassword",
+			method: "get",
+			params: {
+				mobile: loginForm.phoneNumber,
+				code: loginForm.phoneCode,
+				newPassword: loginForm.newPassword,
+			},
+		});
 	},
 };

+ 14 - 0
src/api/cw/invoice/CwFinanceInvoiceService.js

@@ -197,4 +197,18 @@ export default {
 			params: params,
 		});
 	},
+	updateDictValue(data) {
+		return request({
+			url: prefix + "/cw_finance/invoice/updateDictValue",
+			method: "post",
+			data: data,
+		});
+	},
+	getDingNoticeDictValue(data) {
+		return request({
+			url: prefix + "/cw_finance/invoice/getDingNoticeDictValue",
+			method: "post",
+			data: data,
+		});
+	},
 };

+ 1 - 1
src/router/index.js

@@ -47,7 +47,7 @@ router.beforeEach(async (to, from, next) => {
 
 	let token = tool.data.get(config.TOKEN);
 
-	if (to.path === "/login") {
+	if (to.path === "/login" || to.path === "/ddlogin") {
 		//删除路由(替换当前layout路由)
 		router.addRoute(routes[0]);
 		//删除路由(404)

+ 37 - 5
src/router/systemRouter.js

@@ -80,10 +80,35 @@ const routes = [
 				component: () => import("@/layout/other/empty.vue"),
 				meta: { target: "iframe" },
 			},
-			{path: '/flowable/task/CenterForm', component: () => import('@/views/flowable/task/CenterForm'), name: 'center-form', meta: {title: '流程详情-其他系统'}},
-			{path: '/generateForm/GenerateForm', component: () => import('@/views/generateForm/GenerateForm.vue'), name: 'generate-form', meta: {title: '流程详情动态页面-其他系统'}},
-			{path: '/generateForm/InvoiceUpdateGenerateForm', component: () => import('@/views/generateForm/InvoiceUpdateGenerateForm.vue'), name: 'invoiceUpdateGenerate-form', meta: {title: '发票流程详情动态页面-其他系统'}},
-			{path: '/generateForm/InvoiceGenerateForm', component: () => import('@/views/generateForm/InvoiceGenerateForm.vue'), name: 'invoiceGenerate-form', meta: {title: '发票流程详情动态页面-其他系统'}},
+			{
+				path: "/flowable/task/CenterForm",
+				component: () => import("@/views/flowable/task/CenterForm"),
+				name: "center-form",
+				meta: { title: "流程详情-其他系统" },
+			},
+			{
+				path: "/generateForm/GenerateForm",
+				component: () =>
+					import("@/views/generateForm/GenerateForm.vue"),
+				name: "generate-form",
+				meta: { title: "流程详情动态页面-其他系统" },
+			},
+			{
+				path: "/generateForm/InvoiceUpdateGenerateForm",
+				component: () =>
+					import(
+						"@/views/generateForm/InvoiceUpdateGenerateForm.vue"
+					),
+				name: "invoiceUpdateGenerate-form",
+				meta: { title: "发票流程详情动态页面-其他系统" },
+			},
+			{
+				path: "/generateForm/InvoiceGenerateForm",
+				component: () =>
+					import("@/views/generateForm/InvoiceGenerateForm.vue"),
+				name: "invoiceGenerate-form",
+				meta: { title: "发票流程详情动态页面-其他系统" },
+			},
 		],
 	},
 	{
@@ -92,7 +117,14 @@ const routes = [
 		meta: {
 			title: "登录",
 		},
-	}
+	},
+	{
+		path: "/ddLogin",
+		component: () => import("@/views/sys/login/ddLogin.vue"),
+		meta: {
+			title: "钉钉免登录",
+		},
+	},
 ];
 
 export default routes;

+ 1 - 0
src/style/app.scss

@@ -36,6 +36,7 @@ a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: bo
   display: flex;
   align-items: center;
   padding-left:20px;
+  overflow: hidden;
   .logo-bar{
     width: 200px;
   }

+ 42 - 34
src/views/cw/invoice/InvoiceFormTask.vue

@@ -13,33 +13,37 @@
 				<el-col :span="24">
 					<vxe-table border :footer-method="footerMethod2" show-overflow show-footer ref="baseTable"
 						:key="baseKey" class="vxe-table-element" :data="inputForm.financeInvoiceBaseDTOList"
-						style="margin-left: 5em" @cell-click="" @edit-closed="" keep-source highlight-current-row
-						:edit-rules="tableRules"
+						:style="{ marginLeft: isMobile ? '0' : '5em' }" @cell-click="" @edit-closed="" keep-source
+						highlight-current-row :edit-rules="tableRules"
 						:edit-config="{ trigger: 'click', mode: 'cell', showStatus: true, autoClear: true, icon: '-' }">
 						<vxe-table-column field="programName" align="center" title="项目名称" :edit-render="{}"
-							show-overflow="title">
+							show-overflow="title" :min-width="isMobile ? '200' : ''">
 							<template v-slot:edit="scope">
 								<el-input @focus="openProgramPageForm(scope.$rowIndex)" placeholder="请填写项目名称"
 									:readonly="true" v-model="scope.row.programName" />
 							</template>
 						</vxe-table-column>
-						<vxe-table-column field="contractName" align="center" title="合同名称" :edit-render="{}">
+						<vxe-table-column field="contractName" align="center" title="合同名称" :edit-render="{}"
+							:min-width="isMobile ? '200' : ''">
 							<template v-slot:edit="scope">
 								<el-input @focus="openContractForm()" placeholder="请填写合同名称" :readonly="true"
 									v-model="scope.row.contractName" />
 							</template>
 						</vxe-table-column>
-						<vxe-table-column field="programNo" align="center" title="项目编号" :edit-render="{}">
+						<vxe-table-column field="programNo" align="center" title="项目编号" :edit-render="{}"
+							:min-width="isMobile ? '200' : ''">
 							<template v-slot:edit="scope">
 								<el-input :readonly="true" placeholder="请填写项目编号" v-model="scope.row.programNo" />
 							</template>
 						</vxe-table-column>
-						<vxe-table-column field="reportNo" align="center" title="报告号" :edit-render="{}">
+						<vxe-table-column field="reportNo" align="center" title="报告号" :edit-render="{}"
+							:min-width="isMobile ? '200' : ''">
 							<template v-slot:edit="scope">
 								<el-input :readonly="true" placeholder="请填写项目编号" v-model="scope.row.reportNo" />
 							</template>
 						</vxe-table-column>
-						<vxe-table-column field="account" align="center" title="发票金额(元)" :edit-render="{}">
+						<vxe-table-column field="account" align="center" title="发票金额(元)" :edit-render="{}"
+							:min-width="isMobile ? '200' : ''">
 							<template v-slot:edit="scope">
 								<el-input v-on:input="scope.row.account = scope.row.account.replace(/[^-0-9.]/g, '')
 									.replace(/^\./g, '')
@@ -49,7 +53,8 @@
 								</el-input>
 							</template>
 						</vxe-table-column>
-						<vxe-table-column field="isPreInvoice" align="center" title="是否预开票" :edit-render="{}">
+						<vxe-table-column field="isPreInvoice" align="center" title="是否预开票" :edit-render="{}"
+							:min-width="isMobile ? '100' : ''">
 							<template #default="scope">
 								{{ $dictUtils.getDictLabel('is_pre_invoice', scope.row.isPreInvoice, '') }}
 							</template>
@@ -64,7 +69,7 @@
 						<!--                <el-input v-model="scope.row.reportNo" placeholder="请填写报告号"/>-->
 						<!--              </template>-->
 						<!--            </vxe-table-column>-->
-						<vxe-table-column title="操作" align="center" width="100">
+						<vxe-table-column title="操作" align="center" width="100" :min-width="isMobile ? '120' : ''">
 							<template v-slot="scope">
 								<el-button type="danger"
 									@click="removeEvent(scope.row, scope.$rowIndex, 'base')">删除</el-button>
@@ -676,14 +681,16 @@
 					<el-col :span="24">
 						<vxe-table border show-overflow show-footer ref="detailTable" :key="detailKey"
 							class="vxe-table-element" :data="inputForm.financeInvoiceDetailDTOList"
-							style="margin-left: 5em" highlight-current-row
+							:style="{ marginLeft: isMobile ? '0' : '5em' }" highlight-current-row
 							:edit-config="{ trigger: 'click', mode: 'cell', showStatus: true, autoClear: true }">
-							<vxe-table-column field="code" title="发票代码" :edit-render="{}">
+							<vxe-table-column field="code" title="发票代码" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input :disabled="isDisabled" v-model="scope.row.code" placeholder="请填写发票代码" />
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="number" title="发票号" :edit-render="{}">
+							<vxe-table-column field="number" title="发票号" :edit-render="{}"
+								:min-width="isMobile ? '200' : ''">
 								<template #edit="scope">
 									<el-input :disabled="isDisabled" oninput="value=value.replace(/\D|^/g,'')"
 										placeholder="请填写发票号" maxlength="20"
@@ -692,7 +699,8 @@
 									</el-input>
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="account" title="开票金额(元)" :edit-render="{}">
+							<vxe-table-column field="account" title="开票金额(元)" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input-number :disabled="isDisabled"
 										@blur="checkAccount(scope.row, scope.$rowIndex)" v-model="scope.row.account"
@@ -701,7 +709,8 @@
 									</el-input-number>
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="rate" title="税率(%)" :edit-render="{}">
+							<vxe-table-column field="rate" title="税率(%)" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input-number :disabled="isDisabled"
 										@blur="checkRate(scope.row, scope.$rowIndex)" v-model="scope.row.rate"
@@ -710,19 +719,22 @@
 									</el-input-number>
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="amount" title="金额" :edit-render="{}">
+							<vxe-table-column field="amount" title="金额" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input :disabled="isDisabled" :readonly="true" v-model="scope.row.amount"
 										placeholder="请填写金额" />
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="tax" title="税额" :edit-render="{}">
+							<vxe-table-column field="tax" title="税额" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input :disabled="isDisabled" :readonly="true" v-model="scope.row.tax"
 										placeholder="请填写税额" />
 								</template>
 							</vxe-table-column>
-							<vxe-table-column field="allAmount" title="累计登记金额" :edit-render="{}">
+							<vxe-table-column field="allAmount" title="累计登记金额" :edit-render="{}"
+								:min-width="isMobile ? '120' : ''">
 								<template #edit="scope">
 									<el-input-number :disabled="isDisabled" v-model="scope.row.allAmount"
 										controls-position="right" :controls="false" style="width:100%;" :precision="2"
@@ -730,7 +742,7 @@
 									</el-input-number>
 								</template>
 							</vxe-table-column>
-							<vxe-table-column title="操作" width="100">
+							<vxe-table-column title="操作" width="100" :min-width="isMobile ? '120' : ''">
 								<template v-slot="scope">
 									<el-button :disabled="isDisabled" type="danger"
 										@click="removeEvent(scope.row, scope.$rowIndex, 'detail')">删除</el-button>
@@ -967,12 +979,15 @@ export default {
 			userEmail: '',
 			userPhone: '',
 			isPreInvoice: '',
-			isDisabled: false
+			isDisabled: false,
+			isMobile: false,
 		}
 	},
 	commonApi: null,
 	created() {
 		this.commonApi = new CommonApi()
+		this.judgmentMobile()
+		window.addEventListener('resize', this.judgmentMobile)
 	},
 	mounted() {
 	},
@@ -1022,6 +1037,14 @@ export default {
 		}
 	},
 	methods: {
+		// 是否是移动端,宽度判断
+		judgmentMobile() {
+			if (window.innerWidth <= 768) {
+				this.isMobile = true
+			} else {
+				this.isMobile = false
+			}
+		},
 		getKeyWatch(keyWatch) {
 			this.keyWatch = keyWatch
 		},
@@ -1593,21 +1616,6 @@ export default {
 								this.inputForm.title = `${this.$store.state.user.name} 发起了 ${data.no} [财务-发票申请]`
 							}
 						}
-						// // 请求开票接口
-						// if (this.inputForm.status === '12') {
-						// 	// 区分红票,蓝票
-						// 	if (this.inputForm.redInvoiceFlag === '1') {
-						// 		let params = {
-						// 			originalInvno: this.inputForm.invoiceNumberStr,
-						// 			workInvoiceId: this.inputForm.id,
-						// 			redInvoiceRelevancyId: this.inputForm.redInvoiceRelevancyId
-						// 		}
-						// 		financeInvoiceService.invoiceAllScenarioRedOMSView(params)
-						// 	}
-						// 	if (this.inputForm.redInvoiceFlag === '0') {
-						// 		financeInvoiceService.invoiceOMSView(this.inputForm.id)
-						// 	}
-						// }
 
 						this.inputForm.id = data.businessId
 						callback(data.businessTable, data.businessId, this.inputForm, data.recordType)

Разница между файлами не показана из-за своего большого размера
+ 1195 - 1133
src/views/cw/invoice/InvoiceList.vue


Разница между файлами не показана из-за своего большого размера
+ 1819 - 1760
src/views/finance/invoice/InvoiceFormTask.vue


Разница между файлами не показана из-за своего большого размера
+ 990 - 935
src/views/finance/invoice/InvoiceList.vue


+ 1 - 1
src/views/flowable/task/TaskForm.vue

@@ -1734,7 +1734,7 @@ export default {
 	flex-flow: row nowrap;
 	justify-content: center;
 	align-items: center;
-	left: 275px;
+	left: 0;
 	display: flex;
 	-webkit-transition: inline-block 0.3s, left 0.3s, width 0.3s,
 		margin-left 0.3s, font-size 0.3s;

+ 556 - 0
src/views/sys/login/ddLogin.vue

@@ -0,0 +1,556 @@
+<template>
+	<div class="dd-login-page">
+		<div class="dd-login-bg dd-login-bg-left"></div>
+		<div class="dd-login-bg dd-login-bg-right"></div>
+
+		<div class="dd-login-panel">
+			<div class="dd-login-brand">
+				<div class="dd-login-logo">
+					<img alt="logo" src="/img/logo.svg" />
+				</div>
+				<div class="dd-login-brand-text">
+					<h1>{{ isRebackMode ? "审批处理结果" : "钉钉快捷登录" }}</h1>
+					<p>{{ isRebackMode ? "当前审批流程已处理完成" : "正在进入系统,请稍候" }}</p>
+				</div>
+			</div>
+
+			<div class="dd-login-card" :class="{ 'dd-login-card-success': isRebackMode }">
+				<div class="dd-login-icon" :class="{ 'is-success': isRebackMode }">
+					<div class="dd-login-icon-ring"></div>
+					<div class="dd-login-icon-core">{{ isRebackMode ? "✓" : "钉" }}</div>
+				</div>
+
+				<div class="dd-login-title">{{ isRebackMode ? "审批已处理" : "企业身份校验中" }}</div>
+				<div class="dd-login-subtitle">
+					{{ isRebackMode ? "流程已完成审批处理,你可以返回钉钉继续后续操作。" : "检测到你正在钉钉中打开系统,这里可以作为免登录跳转中转页。" }}
+				</div>
+
+				<div class="dd-login-status">
+					<div class="dd-login-status-row">
+						<span class="label">{{ isRebackMode ? "审批结果" : "当前状态" }}</span>
+						<span class="value" :class="{ loading: !isRebackMode }" :style="{
+							color: isRebackMode || loginStatus.includes('成功') ? '#52c41a' : '#1890ff'
+						}">{{ loginStatus }}</span>
+					</div>
+				</div>
+			</div>
+
+			<div class="dd-login-footer">
+				安全登录 · 统一认证 · 钉钉快速进入
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import config from "@/config";
+import loginService from "@/api/auth/loginService";
+import userService from "@/api/sys/userService";
+import tenantService from "@/api/sys/tenantService";
+import financeInvoiceService from '@/api/cw/invoice/CwFinanceInvoiceService'
+import FinanceInvoiceService from '@/api/finance/invoice/FinanceInvoiceService'
+import taskService from '@/api/flowable/TaskService'
+import pick from 'lodash.pick'
+export default {
+	name: "DdLogin",
+	data() {
+		return {
+			loginStatus: "正在校验身份...",
+			isRebackMode: false
+		};
+	},
+	pgFinanceInvoiceService: null,
+	mounted() {
+
+		this.isRebackMode = this.$route.query.reback != false && this.$route.query.reback != "false";
+
+		if (this.isRebackMode) {
+			this.loginStatus = "审批已处理";
+			return;
+		}
+		this.pgFinanceInvoiceService = new FinanceInvoiceService()
+		console.log("DdLogin mounted, starting login process...");
+		this.handleLogin()
+	},
+	methods: {
+		handleLogin() {
+			let nowToken = this.$TOOL.data.get(config.TOKEN);
+			const type = this.$route.query.type;
+
+			//从url的参数获取id,用route获取
+			let ddId = this.$route.query.ddid
+			const infoId = this.$route.query.infoId;
+			if (ddId && infoId) {
+				let params = {
+					ddId: ddId
+				}
+				loginService.ddlogin(params).then(async (res) => {
+					this.$TOOL.data.set(config.TOKEN, res.token);
+					this.$cookies.set(config.TOKEN, res.token, -1);
+					let { user } = await userService.info();
+					this.$TOOL.data.set("USER_INFO", user);
+					this.$store.commit("user/updateUser", user);
+					let isPrimaryTenant = user.tenantDTO.id === "10000";
+					this.$TOOL.data.set(
+						"IS_PRIMARY_TENANT",
+						isPrimaryTenant
+					);
+					if (isPrimaryTenant) {
+						tenantService.list().then((data) => {
+							this.$TOOL.data.set(
+								"TENANT_LIST",
+								data.records
+							);
+						});
+					}
+					let { menuList, permissions, dictList } =
+						await userService.getMenus();
+					if (menuList.length == 0) {
+						this.loading = false;
+						this.$alert(
+							"当前用户无任何菜单权限,请联系系统管理员",
+							"无权限访问",
+							{
+								type: "error",
+								center: true,
+							}
+						);
+						return false;
+					}
+					// 布局
+					this.$store.commit("SET_layout", "left");
+					this.$TOOL.data.set("APP_LAYOUT", "left");
+					this.$TOOL.data.set("MENU", menuList);
+					this.$TOOL.data.set("PERMISSIONS", permissions);
+					this.$TOOL.data.set("DICT_LIST", dictList);
+					this.$dictUtils.refreshDictList();
+
+					this.loginStatus = "身份校验成功,正在跳转...";
+					//从url的参数获取redirect
+					if (type == "cw") {
+						financeInvoiceService
+							.queryById(infoId)
+							.then((data) => {
+								this.todo(data)
+							})
+					}
+					if (type == "pg") {
+						this.pgFinanceInvoiceService.queryById(infoId).then((data) => {
+							this.todo(data)
+						})
+					}
+				})
+					.catch(err => {
+						this.loginStatus = "登录请求失败,请检查网络或稍后再试";
+					});
+			} else {
+				// 跳转登录
+				this.loginStatus = "登录失败,请联系管理员";
+			}
+		},
+		todo(row) {
+			let cUser = false
+			taskService.getTaskDefInfo({
+				taskId: row.invoiceTaskId
+			}).then((data) => {
+				this.$router.push({
+					path: '/flowable/task/TaskForm',
+					query: {
+						...pick(data, 'formType', 'formUrl', 'procDefKey', 'taskDefKey', 'procInsId', 'procDefId', 'taskId', 'status', 'title', 'businessId'),
+						isShow: false,
+						formReadOnly: true,
+						formTitle: `${data.taskName}`,
+						cUser: cUser,
+						title: `审批【${data.taskName || ''}】`,
+						routePath: '/ddLogin' // 数据处理后需要跳转的页面路径
+					}
+				})
+			})
+
+		},
+	},
+};
+</script>
+
+<style lang="less" scoped>
+.dd-login-page {
+	position: relative;
+	display: flex;
+	align-items: flex-start;
+	justify-content: center;
+	min-height: 100vh;
+	min-height: 100dvh;
+	padding: calc(env(safe-area-inset-top, 0px) + 20px) 14px calc(env(safe-area-inset-bottom, 0px) + 20px);
+	overflow: hidden;
+	background: linear-gradient(180deg, #f2f8ff 0%, #e8f3ff 48%, #f8fbff 100%);
+}
+
+.dd-login-bg {
+	position: absolute;
+	border-radius: 50%;
+	filter: blur(2px);
+	pointer-events: none;
+
+	&.dd-login-bg-left {
+		top: -140px;
+		left: -150px;
+		width: 300px;
+		height: 300px;
+		background: radial-gradient(circle, rgba(24, 144, 255, 0.2) 0%, rgba(24, 144, 255, 0) 70%);
+	}
+
+	&.dd-login-bg-right {
+		right: -160px;
+		bottom: -180px;
+		width: 280px;
+		height: 280px;
+		background: radial-gradient(circle, rgba(64, 169, 255, 0.2) 0%, rgba(64, 169, 255, 0) 72%);
+	}
+}
+
+.dd-login-panel {
+	position: relative;
+	width: 100%;
+	max-width: 420px;
+	display: grid;
+	grid-template-columns: 1fr;
+	gap: 16px;
+	align-items: stretch;
+	z-index: 1;
+}
+
+.dd-login-brand {
+	padding: 4px 4px 0;
+	text-align: center;
+
+	.dd-login-logo {
+		width: 56px;
+		height: 56px;
+		margin: 0 auto 14px;
+		padding: 10px;
+		border-radius: 16px;
+		background: rgba(255, 255, 255, 0.72);
+		box-shadow: 0 12px 28px rgba(24, 144, 255, 0.1);
+		backdrop-filter: blur(10px);
+
+		img {
+			width: 100%;
+			height: 100%;
+			object-fit: contain;
+		}
+	}
+
+	.dd-login-brand-text {
+		h1 {
+			margin: 0 0 8px;
+			color: #1f2d3d;
+			font-size: 24px;
+			font-weight: 700;
+			line-height: 1.35;
+		}
+
+		p {
+			margin: 0;
+			max-width: 100%;
+			color: #5f6b7a;
+			font-size: 13px;
+			line-height: 1.7;
+		}
+	}
+}
+
+.dd-login-card {
+	position: relative;
+	padding: 24px 18px 20px;
+	border: 1px solid rgba(24, 144, 255, 0.12);
+	border-radius: 22px;
+	background: rgba(255, 255, 255, 0.88);
+	box-shadow: 0 16px 40px rgba(31, 45, 61, 0.1);
+	backdrop-filter: blur(14px);
+	margin-top: 10%;
+}
+
+.dd-login-card-success {
+	border-color: rgba(82, 196, 26, 0.18);
+	background: rgba(255, 255, 255, 0.92);
+	box-shadow: 0 18px 42px rgba(63, 140, 40, 0.12);
+}
+
+.dd-login-icon {
+	position: relative;
+	width: 76px;
+	height: 76px;
+	margin: 0 auto 16px;
+
+	&-ring {
+		position: absolute;
+		inset: 0;
+		border-radius: 50%;
+		background: linear-gradient(135deg, rgba(24, 144, 255, 0.14), rgba(24, 144, 255, 0.02));
+		border: 1px solid rgba(24, 144, 255, 0.2);
+	}
+
+	&-core {
+		position: absolute;
+		left: 50%;
+		top: 50%;
+		transform: translate(-50%, -50%);
+		width: 48px;
+		height: 48px;
+		border-radius: 15px;
+		background: linear-gradient(135deg, #1890ff 0%, #36c3ff 100%);
+		color: #ffffff;
+		font-size: 20px;
+		font-weight: 700;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		box-shadow: 0 14px 28px rgba(24, 144, 255, 0.3);
+	}
+}
+
+.dd-login-icon.is-success {
+	.dd-login-icon-ring {
+		background: linear-gradient(135deg, rgba(82, 196, 26, 0.16), rgba(82, 196, 26, 0.02));
+		border-color: rgba(82, 196, 26, 0.24);
+	}
+
+	.dd-login-icon-core {
+		background: linear-gradient(135deg, #52c41a 0%, #95de64 100%);
+		box-shadow: 0 14px 28px rgba(82, 196, 26, 0.28);
+		font-size: 24px;
+	}
+}
+
+.dd-login-title {
+	margin-bottom: 8px;
+	color: #1f2d3d;
+	font-size: 20px;
+	font-weight: 700;
+	text-align: center;
+}
+
+.dd-login-subtitle {
+	margin: 0 auto 18px;
+	max-width: 420px;
+	color: #6b7785;
+	font-size: 13px;
+	line-height: 1.7;
+	text-align: center;
+}
+
+.dd-login-status {
+	padding: 12px 14px;
+	border-radius: 16px;
+	background: #f6fbff;
+	border: 1px solid #e0f0ff;
+}
+
+.dd-login-status-row {
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+	gap: 12px;
+	padding: 10px 0;
+	font-size: 13px;
+
+	&+.dd-login-status-row {
+		border-top: 1px solid #eaf4ff;
+	}
+
+	.label {
+		color: #7b8794;
+	}
+
+	.value {
+		color: #1f2d3d;
+		font-weight: 600;
+		word-break: break-word;
+
+		&.loading {
+			color: #1890ff;
+		}
+	}
+}
+
+.dd-login-actions {
+	display: flex;
+	flex-direction: column;
+	gap: 12px;
+	margin-top: 18px;
+
+	button {
+		width: 100%;
+		height: 44px;
+		border-radius: 14px;
+		font-size: 14px;
+		font-weight: 600;
+		cursor: pointer;
+		transition: all 0.2s ease;
+		-webkit-tap-highlight-color: transparent;
+	}
+
+	.primary-btn {
+		border: none;
+		background: linear-gradient(135deg, #1890ff 0%, #36c3ff 100%);
+		color: #ffffff;
+		box-shadow: 0 12px 24px rgba(24, 144, 255, 0.24);
+	}
+
+	.ghost-btn {
+		border: 1px solid #cfe7ff;
+		background: #ffffff;
+		color: #1890ff;
+	}
+}
+
+.dd-login-tips {
+	margin-top: 16px;
+	padding-top: 14px;
+	border-top: 1px dashed #d9ecff;
+}
+
+.tip-item {
+	display: flex;
+	align-items: flex-start;
+	gap: 8px;
+	color: #73808f;
+	font-size: 12px;
+	line-height: 1.7;
+
+	&+.tip-item {
+		margin-top: 8px;
+	}
+
+	.dot {
+		width: 6px;
+		height: 6px;
+		margin-top: 8px;
+		border-radius: 50%;
+		background: #36a3ff;
+		flex-shrink: 0;
+	}
+}
+
+.dd-login-footer {
+	margin-top: 2px;
+	color: #8d99a8;
+	font-size: 12px;
+	text-align: center;
+	line-height: 1.6;
+}
+
+@media (min-width: 768px) {
+	.dd-login-panel {
+		max-width: 960px;
+		grid-template-columns: 1.05fr 0.95fr;
+		gap: 28px;
+		align-items: center;
+	}
+
+	.dd-login-brand {
+		padding: 20px 12px;
+		text-align: left;
+
+		.dd-login-logo {
+			width: 72px;
+			height: 72px;
+			margin: 0 0 24px;
+			padding: 14px;
+			border-radius: 20px;
+		}
+
+		.dd-login-brand-text h1 {
+			margin-bottom: 14px;
+			font-size: 38px;
+		}
+
+		.dd-login-brand-text p {
+			max-width: 460px;
+			margin: 0;
+			font-size: 16px;
+		}
+	}
+
+	.dd-login-page {
+		align-items: center;
+		padding: 32px;
+	}
+
+	.dd-login-bg {
+		&.dd-login-bg-left {
+			top: -180px;
+			left: -120px;
+			width: 420px;
+			height: 420px;
+		}
+
+		&.dd-login-bg-right {
+			right: -160px;
+			bottom: -220px;
+			width: 460px;
+			height: 460px;
+		}
+	}
+
+	.dd-login-card {
+		padding: 36px 32px 28px;
+		border-radius: 28px;
+	}
+
+	.dd-login-icon {
+		width: 92px;
+		height: 92px;
+		margin-bottom: 20px;
+	}
+
+	.dd-login-actions {
+		flex-direction: row;
+		margin-top: 24px;
+
+		button {
+			width: auto;
+			flex: 1;
+			height: 46px;
+			font-size: 15px;
+		}
+	}
+
+	.dd-login-status-row {
+		flex-direction: row;
+		align-items: center;
+		justify-content: space-between;
+		font-size: 14px;
+	}
+
+	.dd-login-title {
+		font-size: 24px;
+	}
+
+	.dd-login-subtitle {
+		margin-bottom: 24px;
+		font-size: 14px;
+	}
+
+	.dd-login-footer {
+		margin-top: 18px;
+		font-size: 13px;
+	}
+}
+
+@media (max-width: 480px) {
+	.dd-login-status-row {
+		gap: 6px;
+	}
+
+	.dd-login-card {
+		padding-left: 16px;
+		padding-right: 16px;
+	}
+
+	.dd-login-brand .dd-login-brand-text h1 {
+		font-size: 22px;
+	}
+}
+</style>