一个简单的Vue3 轮播图组件

<script setup>
import { onMounted, onUnmounted, ref, useSlots } from 'vue'

const slots = useSlots()
const slides = ref([])
const originalSlidesLength = ref(0)
const currentIndex = ref(0)
const isDragging = ref(false)
const startX = ref(0)
const currentTranslate = ref(0)
const prevTranslate = ref(0)
const props = defineProps({
  autoPlay: {
    type: Boolean,
    default: false
  }
})

onMounted(() => {
  const originalSlides = slots.default()[0].children.map((slide, index) => ({ content: slide, key: index }))
  originalSlidesLength.value = originalSlides.length
  slides.value = [...originalSlides, originalSlides[0]]
  startAutoPlay()
})

let autoPlayInterval
const startAutoPlay = () => {
  if (!props.autoPlay) return
  autoPlayInterval = setInterval(() => {
    goToNextSlide()
  }, 3000)
}

const goToNextSlide = () => {
  if (currentIndex.value < slides.value.length - 2) {
    currentIndex.value += 1
  } else {
    currentIndex.value = 0
    updateTranslate(true)
    return
  }
  updateTranslate()
}

const startDrag = (event) => {
  isDragging.value = true
  startX.value = event.clientX
  prevTranslate.value = currentTranslate.value
  clearInterval(autoPlayInterval)
}

const onDrag = (event) => {
  if (isDragging.value) {
    const currentX = event.clientX
    const diff = currentX - startX.value
    currentTranslate.value = prevTranslate.value + (diff / window.innerWidth) * 100
  }
}

const endDrag = () => {
  if (isDragging.value) {
    isDragging.value = false
    const movedBy = currentTranslate.value - prevTranslate.value

    if (movedBy < -10 && currentIndex.value < slides.value.length - 2) {
      currentIndex.value += 1
    } else if (movedBy > 10 && currentIndex.value > 0) {
      currentIndex.value -= 1
    } else if (movedBy > 10 && currentIndex.value === 0) {
      currentIndex.value = slides.value.length - 2
    } else if (movedBy < -10 && currentIndex.value === slides.value.length - 2) {
      currentIndex.value = 0
    }

    updateTranslate()
    startAutoPlay()
  }
}

const updateTranslate = (instant = false) => {
  prevTranslate.value = -currentIndex.value * 100
  currentTranslate.value = prevTranslate.value
  if (instant) {
    requestAnimationFrame(() => {
      currentTranslate.value = prevTranslate.value
    })
  }
}

const goToSlide = (index) => {
  currentIndex.value = index
  updateTranslate()
}

onUnmounted(() => {
  clearInterval(autoPlayInterval)
})
</script>

<template>
  <div class="carousel-container">
    <div
      class="carousel"
      @mousedown="startDrag"
      @mousemove="onDrag"
      @mouseup="endDrag"
      @mouseleave="endDrag"
      :style="{ transform: `translateX(${currentTranslate}%)`, transition: isDragging ? 'none' : 'transform 0.3s ease' }"
    >
      <div v-for="(slide, index) in slides" :key="slide.key" class="slide">
        <component :is="slide.content"></component>
      </div>
    </div>

    <div class="indicators">
      <span
        v-for="(indicator, index) in slides.slice(0, originalSlidesLength)"
        :key="index"
        class="indicator"
        :class="{ active: currentIndex === index || (currentIndex === slides.length - 1 && index === 0) }"
        @click="goToSlide(index)"
      ></span>
    </div>
  </div>
</template>

<style scoped>
.carousel-container {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0 auto;
  overflow: hidden;
  padding-bottom: 30px;
}

.carousel {
  height: 100%;
  display: flex;
}

.slide {
  min-width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2em;
  color: white;
  user-select: none;
}

.indicators {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 10px;
}

.indicator {
  width: 10px;
  height: 6px;
  background-color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.indicator.active {
  background-color: rgba(255, 255, 255, 1);
}
</style>

如何使用 unplugin-auto-import 和 unplugin-vue-components 自动导入组件、ui库、单文件组件

自动导入Vue 、Vue-route、pinia、AntdV、Vant

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from '@vant/auto-import-resolver'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import GetLoginTemplate from './src/utils/GetLoginTemplate'
import { AntDesignIconResolver,AntDesignApiResolver } from './src/utils/AntDesignResolver'

export default defineConfig({
  server: {
    host: '',
    proxy: {
      '/api': 'http://localhost:4000'
    }
  },
  plugins: [
    vue(),
    vueJsx(),
    vueDevTools(),
    AutoImport({
      dirs: ['@/utils/Composable/**'],
      eslintrc: {
        enabled: true,
        filepath: '.eslintrc-auto-import.json',
        globalsPropValue: true
      },
      imports: ['vue', 'vue-router', 'pinia'],
      include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/],
      dts: 'auto-imports.d.ts',
      resolvers: [
        VantResolver(),
        AntDesignVueResolver({ importStyle: 'less' }),
        AntDesignApiResolver()
      ]
    }),
    Components({
      dirs: ['./src/components/**', './src/utils/Composable/**', './src/Layout/**'],
      extensions: ['vue'],
      exclude: [/node_modules/, /\.git/, /\.nuxt/, /dist/, /public/, /test/, /mocks/],
      include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.ts$/, /\.js$/, /\.jsx$/, /\.tsx$/],
      deep: true,
      dts: 'components.d.ts',
      resolvers: [
        VantResolver(),
        AntDesignVueResolver({ importStyle: 'less' }),
        AntDesignIconResolver()
      ]
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@import "@/assets/variables.scss";'
      }
    }
  }
})
继续阅读“如何使用 unplugin-auto-import 和 unplugin-vue-components 自动导入组件、ui库、单文件组件”

如何通过 openvpn 连接服务器

sudo curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh

sudo chmod +x openvpn-install.sh

sudo ./openvpn-install.sh

根据提示选择
如何通过 openvpn 连接服务器插图

下载配置文件

导入客户端即可连接

如何通过 openvpn 连接服务器插图1
如何通过 openvpn 连接服务器插图2

注意:如果是云服务器需要开放对应的tcp/udp端口

scp远程复制

注:如果使用 root 用户,需要配置sshd_config文件 打开root远程登录

ListenAddress 改为 0.0.0.0 或 ip地址
PermitRootLogin 改为 yes

使用 systemctl restart sshd 重启

一、 从 本地 复制到 远程

1. 复制文件

(1.)不修改文件名

scp local_file remote_username@remote_ip:remote_folder

(2.)修改文件名

scp local_file remote_username@remote_ip:remote_file

2. 复制目录

scp -r local_folder remote_username@remote_ip:remote_folder

二、从 远程 复制到 本地

1. 从 复制文件

(1.)不修改文件名

scp remote_username@remote_ip:remote_folder local_file

(2.)修改文件名

scp remote_username@remote_ip:remote_file local_file

2. 复制目录

scp -r remote_username@remote_ip:remote_folder local_folder

Docker 网络代理配置

[Service]

Environment=”HTTP_PROXY=http://192.168.0.49:3128″

Environment=”HTTPS_PROXY=http://192.168.0.49:3128″

mkdir /etc/systemd/system/docker.service.d && vim /etc/systemd/system/docker.service.d/http-proxy.conf

brew update 更新失败

cd "$(brew --repo)"
git remote set-url origin https://mirrors.ustc.edu.cn/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git

重新执行 brew update

Spring Boot 中使用 SseEmitter 实现服务器推送消息

Spring Boot 中使用 SseEmitter 实现服务器推送消息的功能,可以通过维护一个连接池来记录当前连接数。具体步骤如下:

  1. 定义一个 ConcurrentHashMap 用于保存连接池,key 为用户 id 或其它唯一标识符,value 为 SseEmitter 对象。
swiftCopy codeprivate final ConcurrentHashMap<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
继续阅读“Spring Boot 中使用 SseEmitter 实现服务器推送消息”

关于ConcurrentHashMap

ConcurrentHashMap 是 Java 并发包中的一个线程安全的哈希表实现,可以在多线程环境下高效地并发读写,而不需要使用显式的同步机制(如 synchronized 关键字)。

ConcurrentHashMap 的主要特点如下:

  1. 并发读写:ConcurrentHashMap 可以同时支持多个线程并发读取和写入数据,而不需要加锁。在读操作中,ConcurrentHashMap 采用了读写分离的技术,将数据分成了多个段,每个段都有自己的锁,不同线程对不同段的数据进行读取时不会互相干扰。
  2. 高效性能:ConcurrentHashMap 在多线程并发读写时可以保持高效性能,相比于同步的 HashMap,在并发读写时能够提供更高的吞吐量和更低的延迟。
  3. 线程安全:ConcurrentHashMap 是线程安全的,多个线程可以同时对其进行读写操作,不需要额外的同步机制,因此避免了死锁和其他并发问题。
  4. 支持高并发:ConcurrentHashMap 适用于高并发场景,可以应对大量的读写操作,并且具有较好的可伸缩性。
  5. 可扩展性:ConcurrentHashMap 支持动态扩容和收缩,可以根据实际情况自动调整内部数据结构的大小。

总之,ConcurrentHashMap 是一种高效、线程安全、支持高并发和可扩展的哈希表实现,适用于多线程环境下的并发读写操作。