diff --git a/code/base-project/App.vue b/code/base-project/App.vue
new file mode 100644
index 00000000..3a94ebbd
--- /dev/null
+++ b/code/base-project/App.vue
@@ -0,0 +1,162 @@
+
+
+
+
+ hover me
+ hover me
+
+
+
+
+
diff --git a/code/base-project/base-vue/.vscode/vue3.code-snippets b/code/base-project/base-vue/.vscode/vue3.code-snippets
new file mode 100644
index 00000000..8a6c6f5c
--- /dev/null
+++ b/code/base-project/base-vue/.vscode/vue3.code-snippets
@@ -0,0 +1,39 @@
+{
+ "Print AoDesignComponent TSX": {
+ "scope": "typescriptreact",
+ "prefix": "atsx",
+ "body": [
+ "import { defineComponent } from 'vue';",
+ "import { getPrefixCls } from '../_utils/getPrefixCls';",
+ "",
+ "export default defineComponent({",
+ " name: '${1:AoComponent}',",
+ " props: {",
+ " },",
+ " emits: [],",
+ " setup(props, { slots, emit }) {",
+ " const prefixCls = getPrefixCls('${2:component}');",
+ " return () => (",
+ "
",
+ " {slots.default?.()}",
+ "
",
+ " );",
+ " }",
+ "})"
+ ]
+ },
+ "Print AoDesignComponent SCSS": {
+ "scope": "scss",
+ "prefix": "ascss",
+ "body": [
+ "@use '../_styles/_variables.scss' as *;",
+ "@use 'sass:color';",
+ "",
+ "$${1:component}-prefix: #{$prefix}-${1:component};",
+ "",
+ ".#{${1:component}-prefix} {",
+ "}"
+ ]
+ }
+ }
+
\ No newline at end of file
diff --git a/code/base-project/base-vue/packages/ao-design/package.json b/code/base-project/base-vue/packages/ao-design/package.json
index 4b9ef31a..7ce4eb17 100644
--- a/code/base-project/base-vue/packages/ao-design/package.json
+++ b/code/base-project/base-vue/packages/ao-design/package.json
@@ -15,6 +15,7 @@
"vue": "^3.3.0"
},
"dependencies": {
+ "@floating-ui/vue": "^1.1.9"
},
"devDependencies": {
"sass": "^1.90.0",
diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/index.scss b/code/base-project/base-vue/packages/ao-design/src/popup/index.scss
index 3dd93799..680be5f0 100644
--- a/code/base-project/base-vue/packages/ao-design/src/popup/index.scss
+++ b/code/base-project/base-vue/packages/ao-design/src/popup/index.scss
@@ -1,58 +1,31 @@
-.popup-reference {
- display: inline-block; /* keeps reference size */
-}
+@use '../_styles/_variables.scss' as *;
+@use 'sass:color';
-.popup {
+$popup-prefix: "#{$prefix}-popup";
+
+.#{$popup-prefix} {
position: absolute;
- background: #333;
+ background-color: #333;
color: #fff;
- padding: 6px 10px;
- border-radius: 4px;
- font-size: 13px;
- white-space: nowrap;
+ padding: 4px 8px;
+ font-size: 12px;
+ border-radius: 2px;
z-index: 1000;
- box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
+ box-sizing: border-box;
- .popup-content {
- pointer-events: none;
- }
-
- .popup-arrow {
+ &-arrow {
position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
+ width: 8px;
+ height: 8px;
}
- &.popup-top .popup-arrow {
- bottom: -6px;
- left: 50%;
- transform: translateX(-50%);
- border-width: 6px 6px 0 6px;
- border-color: #333 transparent transparent transparent;
- }
-
- &.popup-bottom .popup-arrow {
- top: -6px;
- left: 50%;
- transform: translateX(-50%);
- border-width: 0 6px 6px 6px;
- border-color: transparent transparent #333 transparent;
- }
-
- &.popup-left .popup-arrow {
- right: -6px;
- top: 50%;
- transform: translateY(-50%);
- border-width: 6px 0 6px 6px;
- border-color: transparent transparent transparent #333;
- }
-
- &.popup-right .popup-arrow {
- left: -6px;
- top: 50%;
- transform: translateY(-50%);
- border-width: 6px 6px 6px 0;
- border-color: transparent #333 transparent transparent;
+ &-arrow::before {
+ position: absolute;
+ content: '';
+ width: 8px;
+ height: 8px;
+ background: #333;
+ transform: rotate(45deg);
+ box-sizing: border-box;
}
}
diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx
index 61dc6c82..feaccafa 100644
--- a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx
+++ b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx
@@ -1,216 +1,113 @@
-import type { PropType } from 'vue'
-import { cloneVNode, defineComponent, nextTick, onBeforeUnmount, onMounted, ref, Teleport } from 'vue'
+import type { VNode } from 'vue'
+import { arrow, offset, useFloating } from '@floating-ui/vue'
+import { cloneVNode, defineComponent, ref, Teleport, onMounted } from 'vue'
-type Placement = 'top' | 'bottom' | 'left' | 'right'
-type Trigger = 'hover' | 'click'
+import { getPrefixCls } from '../_utils/getPrefixCls'
export default defineComponent({
name: 'APopup',
props: {
- placement: { type: String as PropType, default: 'top' },
- trigger: { type: String as PropType, default: 'hover' },
- content: { type: String, required: true },
- attach: { type: [String, Object] as PropType, default: 'body' },
+ /**
+ * 触发方式
+ * - 'hover': 鼠标悬停显示/隐藏
+ * - 'click': 点击切换显示/隐藏
+ * - 'focus': 聚焦显示,失焦隐藏
+ */
+ trigger: {
+ type: String as () => 'hover' | 'click' | 'focus',
+ default: 'hover',
+ },
},
setup(props, { slots }) {
- const reference = ref(null)
- const popup = ref(null)
+ const prefixCls = getPrefixCls('popup')
+ const triggerRef = ref(null)
+ const floatingRef = ref(null)
+ const arrowRef = ref(null)
const visible = ref(false)
- const getAttachTarget = (): HTMLElement => {
- if (typeof props.attach === 'string') {
- const el = document.querySelector(props.attach)
- return (el as HTMLElement) || document.body
- }
- return props.attach as HTMLElement || document.body
- }
-
- const show = async () => {
- if (visible.value)
- return
- visible.value = true
- await nextTick()
- updatePosition()
- attachPopupHover()
- if (props.trigger === 'click')
- document.addEventListener('click', onClickOutside)
- }
-
- const hide = () => {
- if (!visible.value)
- return
- visible.value = false
- if (props.trigger === 'click')
- document.removeEventListener('click', onClickOutside)
- }
-
- const toggle = () => (visible.value ? hide() : show())
-
- const onClickOutside = (e: MouseEvent) => {
- const t = e.target as Node
- if (!reference.value || !popup.value)
- return
- if (!reference.value.contains(t) && !popup.value.contains(t))
- hide()
- }
-
- const updatePosition = () => {
- if (!reference.value || !popup.value)
- return
-
- const attachTarget = getAttachTarget()
- const rect = reference.value.getBoundingClientRect()
- const attachRect = attachTarget === document.body ? { left: 0, top: 0 } : attachTarget.getBoundingClientRect()
- const scrollX = attachTarget === document.body ? window.scrollX : attachTarget.scrollLeft
- const scrollY = attachTarget === document.body ? window.scrollY : attachTarget.scrollTop
-
- const tip = popup.value
- const gap = 8
-
- const prevDisplay = tip.style.display
- tip.style.display = 'block'
- tip.style.visibility = 'hidden'
- const tipRect = tip.getBoundingClientRect()
- tip.style.visibility = ''
- tip.style.display = visible.value ? 'block' : prevDisplay || 'none'
-
- let left = 0
- let top = 0
- let finalPlacement = props.placement
-
- const baseX = rect.left - attachRect.left + scrollX
- const baseY = rect.top - attachRect.top + scrollY
-
- // 自动翻转逻辑
- if (props.placement === 'top') {
- left = baseX + rect.width / 2 - tipRect.width / 2
- top = baseY - tipRect.height - gap
- if (top < 0) {
- finalPlacement = 'bottom'
- top = baseY + rect.height + gap
- }
- }
- else if (props.placement === 'bottom') {
- left = baseX + rect.width / 2 - tipRect.width / 2
- top = baseY + rect.height + gap
- const maxY = attachTarget === document.body ? window.innerHeight + scrollY : attachTarget.clientHeight + scrollY
- if (top + tipRect.height > maxY) {
- finalPlacement = 'top'
- top = baseY - tipRect.height - gap
- }
- }
- else if (props.placement === 'left') {
- left = baseX - tipRect.width - gap
- top = baseY + rect.height / 2 - tipRect.height / 2
- if (left < 0) {
- finalPlacement = 'right'
- left = baseX + rect.width + gap
- }
- }
- else {
- left = baseX + rect.width + gap
- top = baseY + rect.height / 2 - tipRect.height / 2
- const maxX = attachTarget === document.body ? window.innerWidth + scrollX : attachTarget.clientWidth + scrollX
- if (left + tipRect.width > maxX) {
- finalPlacement = 'left'
- left = baseX - tipRect.width - gap
- }
- }
-
- left = Math.max(6, left)
- top = Math.max(6, top)
-
- tip.style.left = `${left}px`
- tip.style.top = `${top}px`
-
- const arrow = tip.querySelector('.popup-arrow') as HTMLElement
- if (arrow) {
- const minOffset = 12
- if (finalPlacement === 'top' || finalPlacement === 'bottom') {
- let arrowOffset = baseX + rect.width / 2 - left
- arrowOffset = Math.min(Math.max(arrowOffset, minOffset), tipRect.width - minOffset)
- arrow.style.left = `${arrowOffset}px`
- arrow.style.top = ''
- }
- else {
- let arrowOffset = baseY + rect.height / 2 - top
- arrowOffset = Math.min(Math.max(arrowOffset, minOffset), tipRect.height - minOffset)
- arrow.style.top = `${arrowOffset}px`
- arrow.style.left = ''
- }
- }
-
- tip.className = `popup popup-${finalPlacement}`
- }
-
- const attachPopupHover = () => {
- if (!popup.value || props.trigger !== 'hover')
- return
- popup.value.addEventListener('mouseenter', show)
- popup.value.addEventListener('mouseleave', hide)
- }
-
- const detachPopupHover = () => {
- if (!popup.value)
- return
- popup.value.removeEventListener('mouseenter', show)
- popup.value.removeEventListener('mouseleave', hide)
- }
+ // 使用 useFloating 管理浮动层和箭头
+ const { floatingStyles, middlewareData } = useFloating(
+ triggerRef,
+ floatingRef,
+ {
+ placement: 'top',
+ middleware: [offset(10), arrow({ element: arrowRef })],
+ },
+ )
+ // 监听 triggerRef 挂载完成
onMounted(() => {
- if (reference.value) {
- if (props.trigger === 'hover') {
- reference.value.addEventListener('mouseenter', show)
- reference.value.addEventListener('mouseleave', hide)
- }
- else {
- reference.value.addEventListener('click', (e) => {
- e.stopPropagation()
- toggle()
- })
- }
- }
- window.addEventListener('resize', updatePosition)
- window.addEventListener('scroll', updatePosition, true)
+ console.log('triggerRef 已挂载:', triggerRef.value) // 正确打印
+ // 可以在此处调用 useFloating 的更新逻辑(如果有需要)
})
- onBeforeUnmount(() => {
- if (reference.value) {
- reference.value.removeEventListener('mouseenter', show)
- reference.value.removeEventListener('mouseleave', hide)
- reference.value.removeEventListener('click', toggle)
- }
- detachPopupHover()
- document.removeEventListener('click', onClickOutside)
- window.removeEventListener('resize', updatePosition)
- window.removeEventListener('scroll', updatePosition, true)
- })
+ // 显示/隐藏/切换浮动层
+ const showPopup = () => (visible.value = true)
+ const hidePopup = () => (visible.value = false)
+ const togglePopup = () => (visible.value = !visible.value)
- return () => {
- const defaultSlot = slots.default?.()
- if (!defaultSlot || defaultSlot.length !== 1)
- return null
+ // 绑定事件
+ const eventProps: Record void> = {}
+ if (props.trigger === 'hover') {
+ eventProps.onMouseenter = () => showPopup()
+ eventProps.onMouseleave = () => hidePopup()
+ }
+ else if (props.trigger === 'click') {
+ eventProps.onClick = () => togglePopup()
+ }
+ else if (props.trigger === 'focus') {
+ eventProps.onFocus = () => showPopup()
+ eventProps.onBlur = () => hidePopup()
+ }
- // 克隆触发元素并绑定 ref 和事件
- const triggerEl = cloneVNode(defaultSlot[0], { ref: reference })
+ // 获取 reference 插槽或默认插槽
+ const slotContent = slots.reference?.() || slots.default?.() || []
+ let triggerSlot: VNode | null = null
- return (
- <>
- {triggerEl}
-
-
-
- >
+ // 单元素直接绑定事件,多元素套 span
+ if (slotContent.length === 1) {
+ triggerSlot = cloneVNode(slotContent[0], {
+ ref: triggerRef,
+ ...eventProps,
+ })
+ }
+ else if (slotContent.length > 1) {
+ triggerSlot = (
+
+ {slotContent.map(vnode => cloneVNode(vnode, { ...eventProps }))}
+
)
}
+
+ return () => (
+ <>
+ {/* 原位渲染触发元素 */}
+ {triggerSlot}
+
+ {/* 浮动层挂载到 body */}
+
+ {visible.value && (
+
+ )}
+
+ >
+ )
},
})
diff --git a/code/base-project/base-vue/pnpm-lock.yaml b/code/base-project/base-vue/pnpm-lock.yaml
index 9a9ce552..764bfe10 100644
--- a/code/base-project/base-vue/pnpm-lock.yaml
+++ b/code/base-project/base-vue/pnpm-lock.yaml
@@ -69,6 +69,9 @@ importers:
packages/ao-design:
dependencies:
+ '@floating-ui/vue':
+ specifier: ^1.1.9
+ version: 1.1.9(vue@3.5.19(typescript@5.8.3))
'@iconify/vue':
specifier: 5.0.0
version: 5.0.0(vue@3.5.19(typescript@5.8.3))
diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue
index bb465f73..d37b46f7 100644
--- a/code/base-project/base-vue/src/App.vue
+++ b/code/base-project/base-vue/src/App.vue
@@ -1,112 +1,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/code/base-project/base-vue/src/main.ts b/code/base-project/base-vue/src/main.ts
index 85a9d670..86723405 100644
--- a/code/base-project/base-vue/src/main.ts
+++ b/code/base-project/base-vue/src/main.ts
@@ -1,6 +1,6 @@
+import naive from 'naive-ui'
import { createApp } from 'vue'
import App from './App.vue'
import './style.scss'
-
-createApp(App).mount('#app')
+createApp(App).use(naive).mount('#app')
diff --git a/code/email/Email-Netty/.gitignore b/code/email/Email-Netty/.gitignore
deleted file mode 100644
index 5ff6309b..00000000
--- a/code/email/Email-Netty/.gitignore
+++ /dev/null
@@ -1,38 +0,0 @@
-target/
-!.mvn/wrapper/maven-wrapper.jar
-!**/src/main/**/target/
-!**/src/test/**/target/
-
-### IntelliJ IDEA ###
-.idea/modules.xml
-.idea/jarRepositories.xml
-.idea/compiler.xml
-.idea/libraries/
-*.iws
-*.iml
-*.ipr
-
-### Eclipse ###
-.apt_generated
-.classpath
-.factorypath
-.project
-.settings
-.springBeans
-.sts4-cache
-
-### NetBeans ###
-/nbproject/private/
-/nbbuild/
-/dist/
-/nbdist/
-/.nb-gradle/
-build/
-!**/src/main/**/build/
-!**/src/test/**/build/
-
-### VS Code ###
-.vscode/
-
-### Mac OS ###
-.DS_Store
\ No newline at end of file
diff --git a/code/email/Email-Netty/.idea/.gitignore b/code/email/Email-Netty/.idea/.gitignore
deleted file mode 100644
index 7bc07ec2..00000000
--- a/code/email/Email-Netty/.idea/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Environment-dependent path to Maven home directory
-/mavenHomeManager.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/code/email/Email-Netty/.idea/encodings.xml b/code/email/Email-Netty/.idea/encodings.xml
deleted file mode 100644
index aa00ffab..00000000
--- a/code/email/Email-Netty/.idea/encodings.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/code/email/Email-Netty/.idea/misc.xml b/code/email/Email-Netty/.idea/misc.xml
deleted file mode 100644
index 132404bc..00000000
--- a/code/email/Email-Netty/.idea/misc.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/code/email/Email-Netty/.idea/vcs.xml b/code/email/Email-Netty/.idea/vcs.xml
deleted file mode 100644
index c2365ab1..00000000
--- a/code/email/Email-Netty/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/code/email/Email-Netty/pom.xml b/code/email/Email-Netty/pom.xml
deleted file mode 100644
index e75e1d94..00000000
--- a/code/email/Email-Netty/pom.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
- 4.0.0
-
- com.email
- Email-Netty
- 1.0-SNAPSHOT
-
-
- 8
- 8
- UTF-8
-
-
-
-
- io.netty
- netty-all
- 4.1.106.Final
-
-
-
-
\ No newline at end of file
diff --git a/code/email/Email-Netty/src/main/java/com/email/ImapClientHandler.java b/code/email/Email-Netty/src/main/java/com/email/ImapClientHandler.java
deleted file mode 100644
index 918e811b..00000000
--- a/code/email/Email-Netty/src/main/java/com/email/ImapClientHandler.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package com.email;
-
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
-
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.Base64;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class ImapClientHandler extends SimpleChannelInboundHandler {
-
- private final String username;
- private final String password;
-
- private enum State { LOGIN, SELECT, FETCH, DONE }
- private State state = State.LOGIN;
-
- private int tagCounter = 1;
- private StringBuilder headerBuffer = new StringBuilder();
- private StringBuilder bodyBuffer = new StringBuilder();
- private boolean inHeader = true;
- private String boundary = null;
-
- private boolean inAttachment = false;
- private String currentFilename = null;
-
- public ImapClientHandler(String username, String password) {
- this.username = username;
- this.password = password;
- }
-
- private String nextTag() {
- return "a" + (tagCounter++);
- }
-
- @Override
- public void channelActive(ChannelHandlerContext ctx) {
- System.out.println("Connected to IMAP server...");
- }
-
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, String line) {
- line = line.trim();
- if (line.isEmpty()) return;
-
- System.out.println("S: " + line);
-
- switch (state) {
- case LOGIN:
- if (line.startsWith("* OK")) {
- ctx.writeAndFlush(nextTag() + " LOGIN " + username + " " + password + "\r\n");
- } else if (line.contains("OK LOGIN")) {
- state = State.SELECT;
- ctx.writeAndFlush(nextTag() + " SELECT INBOX\r\n");
- }
- break;
-
- case SELECT:
- if (line.contains("OK [READ-WRITE]") || line.contains("OK [READ-ONLY]")) {
- state = State.FETCH;
- ctx.writeAndFlush(nextTag() + " FETCH 1:* (BODY.PEEK[])\r\n");
- }
- break;
-
- case FETCH:
- if (line.contains("OK FETCH completed")) {
- state = State.DONE;
- System.out.println("All emails fetched!");
- ctx.close();
- } else if (line.startsWith("*") && line.contains("FETCH")) {
- // 新邮件开始
- headerBuffer.setLength(0);
- bodyBuffer.setLength(0);
- inHeader = true;
- inAttachment = false;
- currentFilename = null;
- boundary = null;
- } else {
- if (inHeader) {
- headerBuffer.append(line).append("\r\n");
- if (line.isEmpty()) { // header结束
- inHeader = false;
-
- // 解析邮件标题
- Matcher subjectMatcher = Pattern.compile("(?i)^Subject: (.+)$", Pattern.MULTILINE).matcher(headerBuffer);
- if (subjectMatcher.find()) {
- String subject = subjectMatcher.group(1).trim();
- System.out.println("Email Subject: " + subject);
- }
-
- // 解析 boundary
- Matcher m = Pattern.compile("boundary=\"([^\"]+)\"").matcher(headerBuffer);
- if (m.find()) boundary = m.group(1);
- }
- } else {
- // body部分
- if (boundary != null && line.contains(boundary)) {
- // attachment结束
- if (inAttachment && currentFilename != null) {
- saveAttachment(bodyBuffer.toString(), currentFilename);
- System.out.println("Attachment saved: " + currentFilename);
- bodyBuffer.setLength(0);
- inAttachment = false;
- currentFilename = null;
- }
- } else {
- // 检测附件开始
- Matcher m = Pattern.compile("Content-Disposition: attachment; filename=\"([^\"]+)\"").matcher(line);
- if (m.find()) {
- currentFilename = m.group(1);
- inAttachment = true;
- bodyBuffer.setLength(0);
- } else if (inAttachment) {
- bodyBuffer.append(line);
- }
- }
- }
- }
- break;
-
- case DONE:
- break;
- }
- }
-
- private void saveAttachment(String base64Data, String filename) {
- try {
- byte[] data = Base64.getDecoder().decode(base64Data);
- try (OutputStream os = new FileOutputStream(filename)) {
- os.write(data);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
- cause.printStackTrace();
- ctx.close();
- }
-}
diff --git a/code/email/Email-Netty/src/main/java/com/email/NettyImapClient.java b/code/email/Email-Netty/src/main/java/com/email/NettyImapClient.java
deleted file mode 100644
index 87766fe9..00000000
--- a/code/email/Email-Netty/src/main/java/com/email/NettyImapClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.email;
-
-import io.netty.bootstrap.Bootstrap;
-import io.netty.channel.*;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-import io.netty.handler.codec.LineBasedFrameDecoder;
-import io.netty.handler.codec.string.StringDecoder;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-
-public class NettyImapClient {
-
- private final String host;
- private final int port;
- private final String username;
- private final String password;
-
- public NettyImapClient(String host, int port, String username, String password) {
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- }
-
- public void start() throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- SslContext sslCtx = SslContextBuilder.forClient().build();
-
- try {
- Bootstrap b = new Bootstrap();
- b.group(group)
- .channel(NioSocketChannel.class)
- .handler(new ChannelInitializer() {
- @Override
- protected void initChannel(SocketChannel ch) {
- ChannelPipeline p = ch.pipeline();
- p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
- p.addLast(new LineBasedFrameDecoder(8192));
- p.addLast(new StringDecoder());
- p.addLast(new ImapClientHandler(username, password));
- }
- });
-
- ChannelFuture f = b.connect(host, port).sync();
- f.channel().closeFuture().sync();
- } finally {
- group.shutdownGracefully();
- }
- }
-
- public static void main(String[] args) throws Exception {
- String host = "imap.qq.com"; // IMAP服务器
- int port = 993; // SSL端口
- String user = "717406575@qq.com";
- String pass = "dhqesrwzgblrbcac"; // 授权码
-
- new NettyImapClient(host, port, user, pass).start();
- }
-}
diff --git a/notes/前端开发/nvm/windows下nvm安装和使用.md b/notes/前端开发/nvm/windows下nvm安装和使用.md
index bdf226a2..5896af22 100644
--- a/notes/前端开发/nvm/windows下nvm安装和使用.md
+++ b/notes/前端开发/nvm/windows下nvm安装和使用.md
@@ -98,7 +98,7 @@ nvm install 18.16.0
(3)使用指定版本或切换到指定版本
```
-nvm ues 18.16.0
+nvm use 18.16.0
```
