Vue vs React:你需要知道的一切 - chuckQu - 博客园

mikel阅读(463)

来源: Vue vs React:你需要知道的一切 – chuckQu – 博客园

Vue 和 React 是创建 JavaScript 网络应用程序最常用的两种工具。但我们该如何在两者之间做出选择呢?在本篇 Vue 与 React 的对比中,我们将尝试找出答案。我们将回顾每种工具的优缺点,提供选择使用哪种工具的技巧,并帮助你开始使用。

总览

  1. 什么是Vue?
    • Vue趣事
  2. 什么是React?
    • React趣事
  3. Vue vs React:全面比较
    • React 灵活性
    • Vue 灵活性
    • 使用 React 进行开发
    • 使用 Vue 进行开发
    • React 工具
    • Vue 工具
    • React 性能
    • Vue 性能
    • React 的文档和社区支持
    • Vue 的文档和社区支持
  4. Vue vs React:总结
  5. 受欢迎程度
    • 使用 Vue 和 React 创建的知名应用程序
  6. Vue vs React:何时使用?
  7. 10条FAQ
    1. Vue 和 React 之间的实际区别是什么?
    2. 为什么 Vue 比 React 更快?
    3. 为什么 React 比 Vue 更受欢迎?
    4. Vue 比 React 更好吗?
    5. Vue 比 React 更简单吗?
    6. 如果我们知道 React,我们能学习 Vue 吗(反之亦然)?
    7. 我们应该从 React 转向 Vue 吗?
    8. Vue 比 React 更适合我的职业生涯吗?
    9. Vue 可以与 React 一起使用吗?
    10. 那么,我们应该学习 Vue 还是 React?
  8. 总结

什么是Vue?

Vue 是一个用于构建用户界面的渐进式、可逐步采用的 JavaScript 框架。它由 Evan You 于 2014 年创建,并由一个活跃的开发者社区负责维护。

Vue 设计得非常轻量级、灵活和强大。它建立在一个基于组件的架构上,以组件为中心,可以更轻松地创建动态用户界面。它还有一个强大的双向数据绑定系统,可以让我们轻松地保持数据和用户界面同步。

Vue 的API简单但功能强大,易于上手,而且体积小(约 20KB),非常适合移动应用程序或单页应用程序(SPA)。说 Vue 是一个”渐进式”框架,意味着它不会自动提供一堆我们可能不需要的东西。相反,我们可以从最基本的功能开始构建响应式应用程序,然后随着项目的发展逐步导入更多功能。所有这些都让我们避免了模板代码和尺寸过大的困扰。

Vue趣事

  • Vue 的语法受到 Angular 和 React 的启发,旨在让已经熟悉这些框架的开发人员易于学习。
  • Vue 的名称源自法语动词 “vue”(看)。
  • Vue 的标志是一个抽象的字母 “V”。

什么是React?

React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 于 2013 年创建。它最初用于构建 Facebook 网站,后来成为开发web应用程序最流行的 JavaScript 框架之一。

React 为开发人员提供了一种声明式编程风格,更易于阅读和调试。它还使用虚拟 DOM,速度比传统 DOM 更快,性能更好。

React 基于组件的方法使开发人员能够利用可快速、轻松地组合在一起的可重用组件创建强大的应用程序。这使它成为快速原型开发和应用程序的绝佳选择。

React趣事

  • 全球一些最受欢迎的网站都在使用 React,其中包括 Facebook、Netflix 和 Airbnb。
  • React 非常注重性能,其渲染速度明显快于主要竞争对手 Angular。
  • React 是最流行的前端 JavaScript 库。

Vue vs React:全面比较

现在,我们将进行一次 Vue 与 React 的压力测试,看看它们在整个开发链路中的一些关键方面有哪些优势。这些方面包括以下内容:

  • 灵活性。根据项目需求定制和调整有多容易,既可以使用其核心组件,也可以通过第三方附加组件来实现,而第三方附加组件通常无法获得同等程度的文档和支持。
  • 开发和工具。建立项目和开始编码有多容易。这一点非常重要,因为它将决定我们启动项目的速度,以及新开发人员提高工作效率的速度。
  • 性能。这一点非常重要,因为它决定了应用程序在用户设备上的运行效果。我们不应该低估这一点,因为它意味着愉快的应用体验(用户会不断回访)和令人沮丧的应用体验(用户可能会放弃)之间的差别。
  • 文档和社区支持。这将从根本上决定开发人员在遇到困难时获得帮助的速度,以及找到常见问题解决方案的难易程度。

下面就开始比较吧。

React 灵活性

React 以其灵活性而著称,因为它的核心只是一个UI库。它允许开发人员在编写代码时使用基于组件的编程方法或函数式编程方法。这意味着开发人员可以选择自己最擅长的编码风格,并以此创建功能强大的应用程序。

React 还支持各种库和工具,因此很容易与现有技术和项目集成。例如,React 可以与 Redux 结合使用以进行状态管理,也可以与 Next.js 结合使用以进行服务端渲染

  • 优点:高度可定制和可扩展;支持广泛的库和工具。
  • 缺点:可扩展性主要通过第三方插件实现。

Vue 灵活性

Vue 也提供很大的灵活性,因为我们可以在开始时只使用核心库,然后随着项目的发展逐步采用所需的内容。Vue 也支持基于组件的编程方法或函数式编程方法(包括 JSX),并支持各种库和工具。

但与 React 不同的是,这些扩展大部分都是由同一个团队开发的,因此集成和支持都非常出色。例如,Vue 甚至原生提供了服务端渲染功能。这让我们可以放心地扩展我们的项目,因为我们知道,无论将来我们需要做什么,Vue 都能满足我们的需求。

  • 优点:高度可定制和可扩展,对库和工具提供广泛的原生支持。
  • 缺点:无。

使用 React 进行开发

通过第三方插件,React 还提供路由状态管理,以管理导航流和处理应用程序数据。React 的支柱之一 JSX 可以让开发人员轻松地在 JavaScript 中编写类似 HTML 的语法,从而创建组件并将它们快速组合在一起。

  • 优点:易于设置和上手;有许多第三方插件。
  • 缺点:我们可能经常使用的许多扩展都不是官方的。

使用 Vue 进行开发

Vue 为路由状态管理提供了原生模块,从而实现了导航流和应用程序数据的管理过程。Vue 还提供模板语法,允许开发人员创建易于重用和维护的组件。

尽管如此,由于 Vue 社区不像 React 社区那么庞大,要找到好的第三方解决方案还是有点困难。(不过有一个庞大的中文 Vue 社区,我们稍后再谈)。

  • 优点:易于设置和上手;原生模块可满足大多数开发需求。
  • 缺点:没有那么多第三方插件,维护也不完善。

React 工具

React 很容易设置并开始开发,它自带用于开发和调试的 Visual Studio Code 扩展(由微软开发)。

名为 Create React App 的官方 CLI 允许我们快速设置基本的脚手架,以便立即开始编码。

React 还有一套开发人员工具(目前处于测试阶段),用于检查 React 组件、编辑props和状态以及发现性能问题。

  • 优点:用于调试和检查组件的强大工具。
  • 缺点:CLI 功能有限,性能不佳。

Vue 工具

Vue 也很容易设置和开始开发,它也有自己的 Visual Studio Code 扩展、用于快速开发 SPA 的 CLI(比 React 的 CLI 功能更全面、更强大)以及自己的插件系统

此外还有 Vue Devtools,一个用于调试 Vue 应用程序的官方浏览器 devtools 扩展。

此外,Vite 是用于现代web开发的下一代构建工具,具有极快的冷启动和增量编译时间。它是 Vue 团队的一个官方项目,因此能与 Vue 生态系统无缝集成,并提供一些高级功能,如热模块替换和tree-shaking。

  • 优点:用于调试和检查组件的强大工具,以及用于快速开发的 Vite。
  • 缺点:无。

React 性能

React 的速度相当快,因为它的设计目的只有一个而且做得很好:渲染用户界面。因此,无需太多额外工作,React 的性能就会非常出色;当然,还有进一步优化性能的具体指导原则

React 支持服务端渲染,这有助于进一步提高web应用程序的性能,尤其是在移动设备和较慢网络连接上。

  • 优点:开箱即用,性能稳定;可根据特定指南进行优化;支持服务端渲染。
  • 缺点:速度不如 Vue 快。

Vue 性能

React 在性能方面非常出色,而 Vue 甚至更快。它的虚拟 DOM 可以确保在数据发生变化时只对必要的元素进行重新渲染,因此它的性能和效率都很高。

Vue 可以比 React 做得更多,但性能仍然优于 React,这充分证明了 Vue 创建者 Evan You 的软件工程技能和强大的设计原则。👏

Vue 还原生支持服务端渲染,因此可以进一步提高性能。

  • 优点:开箱即用,性能非常快;可根据特定指南进行优化;支持服务端渲染。
  • 缺点:无。

React 的文档和社区支持

React 拥有详尽的文档和非常活跃的社区。以下是一些最活跃的社区:

Vue 的文档和社区支持

诚然,Vue 社区比 React 社区小,但仍有许多爱好者:

Vue 的文档一直备受赞誉,可能是此类项目中最好的文档。它非常清晰、简洁、易懂,而且布局精美。(这也是我成为 Vue 开发人员的一个关键因素)。

Vue vs React:总结

经过全面比较,我们可以得出这样的结论:React 和 Vue 都是开发现代web应用的绝佳选择,它们提供了大量的灵活性和强大的工具来完成工作。

不过,Vue 在性能、灵活性和文档方面更胜一筹,而 React 则在采用率、第三方工具和社区支持方面更胜一筹。

React Vue
灵活性 高(但主要通过第三方插件) 高(主要是原生扩展)
开发 容易上手,有很多第三方插件。 容易上手,有很多原生模块,但第三方插件不多。
工具 工具功能强大,但性能不佳。 工具功能强大,性能卓越。
性能 很好,可以进一步优化。 非常好,可以进一步优化。
文档和社区支持 详尽的文档和一个活跃的大型社区。 出色的文档和一个较小但活跃的社区。

受欢迎程度

就受欢迎程度而言,React 是迄今为止这两种框架中最受欢迎的,而 Vue 则落后于 Angular(本文不涉及 Angular)。

React 自 2013 年诞生以来,拥有一个庞大的开发者社区,他们不断使用并为该平台做出贡献。它还得到了 Meta(Facebook)的支持,并被用于创建一些世界上最流行的应用程序。

另一方面,Vue 比 React 更年轻,有时被描述为 Angular 和 React 的混合体。Vue 诞生于 2014 年,由于其易用性和对开发人员友好的特性,从那时起,Vue 就受到了越来越多的关注。Vue 还被用于创建流行的应用程序,这将在下一节中介绍。

使用 Vue 和 React 创建的知名应用程序

React 已被用于创建一些世界上最流行的网站和移动应用程序。其中包括 Facebook、Instagram、Airbnb、Dropbox 和 Twitter。

Vue 也被用于创建一些世界上最流行的应用程序。其中包括 9GAG、Alibaba、Xiaomi、GitLab 和 Wizz Air。

以下是一些有用的、非官方的 Vue 和 React 项目展示列表:

Vue vs React:何时使用?

好吧,我们在开始这一部分之前就知道没有”正确”答案,好吗?

  • 对于需要轻量级灵活框架的项目来说,Vue 是一个不错的选择。对于需要大量交互性的项目来说,Vue 也是不错的选择。
  • 对于需要高度可扩展性和可维护性的项目来说,React 是一个不错的选择。它也非常适合需要大量定制的项目。

以上就是在 Vue 和 React 之间做出选择的技术原因。不过,在决定使用哪种技术时,还有一些非技术性的原因(可以这么说),比如支持、社区甚至偏见。接下来,我们将仔细分析其中的一些原因。

10条FAQ

在本节中,我们将介绍有关 Vue 与 React 的十个实用注意事项。

Vue 和 React 之间的实际区别是什么?

Vue 和 React 的主要区别在于,React 是一个用于构建用户界面的 JavaScript 库,而 Vue 则是一个用于构建 Web 应用程序的 JavaScript/TypeScript 渐进式框架。”渐进”意味着其功能和范围可以随着项目的增长而轻松扩展。

除此之外,React 和 Vue 都采用基于组件的方法,允许开发人员将复杂的UI分解成较小的组件,以便在不同的项目中重用。Vue 和 React 的性能都很高,当然比 Angular 更强。

为什么 Vue 比 React 更快?

首先要说的是,React(它只是一个UI库)已经相当快了。然而,Vue 一般来说比 React 更快,因为 Vue 团队非常注重使其具有高性能和轻量级。考虑到我们可以用 Vue 做更多的事情,这已经是一项了不起的成就了。

Vue 之所以更快,还因为它采用了虚拟 DOM 实现,只在数据发生变化时才更新 DOM,而不是每次渲染页面时都更新。

为什么 React 比 Vue 更受欢迎?

至少在西方世界,React 比 Vue 更受欢迎。React 出现的时间更长,熟悉它的开发者社区也更大。

此外,React 是由 Facebook 创建的,这也是吸引许多开发人员的一个因素。相比之下,Vue 背后并没有大的技术公司(这取决于你的观点,也许是坏事,也许不是)。

尽管如此,事实上,在包括中国在内的一些亚洲国家,Vue 比其他任何框架都更受欢迎!

image.png

Vue 比 React 更好吗?

在回答这个问题时,我们不可能不引起网络开发者社区的关注,因此,我们可以妥协地说,React 和 Vue 都是很棒的工具。

React 因其简单性和庞大的社区而成为许多开发人员的首选,而 Vue 则提供了更多的内置功能,在某些用例中比 React 性能更好。

最后的答案可以归结为以下两点:

  • 如果我们了解 Vue,或者对 React 没有什么经验,那么 Vue 会更好
  • 如果我们了解 React,但没有使用过 Vue,那么 React 会更好(但 Vue 仍然优于 React)。

Vue 比 React 更简单吗?

是的。Vue 不仅比 React 更容易学习,而且因为 React 基本上是一个(非常好的)UI 库,而 Vue 实际上是一个框架,所以我们可以用它做更多的事情。

因此,Vue 的”性价比”(可以这么说)–基于你所花的学习时间和你之后能做的事情–是相当高的。

如果我们知道 React,我们能学习 Vue 吗(反之亦然)?

是的,我们可以。虽然这两种技术的开发方法不同,但基本原理是相同的。

无论学习 React 还是 Vue,我们都需要学习一种新的语言/扩展–学习 Vue 时需要学习 TypeScript(为了获得更好的开发体验),学习 React 时需要学习 JSX(出于同样的原因)。尽管存在诸多不同,但这两种框架都使用组件,并具有相似的 API,这使得它们之间的切换更加容易。

我们应该从 React 转向 Vue 吗?

如果我们的团队在使用其中任何一种产品时都已经非常出色,并且取得了丰硕成果,那么就坚持使用它。此外,如果担心第三方扩展和社区支持,那么使用 React 可能会更好。

现在,如果我们尚未启动项目,正在寻找更简单、更灵活的方法,那么 Vue 可能是更好的选择,因为 Vue 提供了更多开箱即用的功能,而且一般来说比 React 更快。

寻找具有 Vue 专业知识的开发人员比较困难,但教授 JavaScript 开发人员如何使用 Vue 则比较容易。

Vue 比 React 更适合我的职业生涯吗?

这要看情况。如果你正在寻找更多的工作机会,那就不一定了。毕竟,React 仍然更受欢迎,甚至可以说是标准(至少在西方)。而在包括中国在内的亚洲许多地方,情况恰恰相反。

如果你想专门从事 JavaScript 开发,那么这两个选项都是不错的选择,因为 Vue 的市场份额仍在不断扩大,尽管没有以前那么快。

Vue 可以与 React 一起使用吗?

不,不要这样做。Vue 和 React 是完全不同的工具,彼此不兼容。

那么,我们应该学习 Vue 还是 React?

还是要看情况。比方说,我们需要这样一个框架:

  • 开箱即能做很多事情
  • 可以通过官方插件进一步扩展,这些插件将遵循与核心软件包相同的实践和指导原则
  • 如果需要,可以与 TypeScript 一起使用
  • 易于学习
  • 在亚洲非常流行
  • 没有任何大公司支持

如果是这样的话,我们就应该学习 Vue

另一方面,我们可能需要一个库:

  • 使我们能够创建用户界面
  • 还可以使用官方和非官方插件进行扩展
  • 当使用 React Native 时,它甚至可以成为移动项目的基础
  • 在西方世界最为流行
  • 由 Meta 提供支持

如果是这样,我们就应该学习 React

总结

Vue 和 React 都是创建 web 应用程序的强大工具。React 因其庞大的社区和 Meta 的支持而更受欢迎,而 Vue 则因其简洁性和性能在过去几年中获得了越来越多的关注。

得到科技巨头的支持并非无关紧要!这基本上意味着资金和开发人员。对于 Vue 来说,独立是一个长期的挑战,因为它需要获得企业的采用和开源社区的支持,才能保持良好的状态。

总而言之,如果我们正在寻找一个可以帮助我们快速、轻松地创建用户界面的库,React 是一个不错的选择–这些界面以后可以成为使用 React Native 构建移动应用程序的基础。如果我们需要更强大、功能更丰富的东西,Vue 可能是更好的选择,因为它提供了更多开箱即用的功能,而且一般比 React 更快。

以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~

Vue3中的几个坑,你都见过吗? - 葡萄城技术团队 - 博客园

mikel阅读(460)

来源: Vue3中的几个坑,你都见过吗? – 葡萄城技术团队 – 博客园

Vue3 目前已经趋于稳定,不少代码库都已经开始使用它,很多项目未来也必然要迁移至Vue3。本文记录我在使用Vue3时遇到的一些问题,希望能为其他开发者提供帮助。

1. 使用reactive封装基础数据类型

传统开发模式中,数据声明很简单。但是在Vue中有多个响应式变量声明方式,整体的使用规则如下:

使用reactive来封装Object,Array,Map,Set数据类型;

使用ref封装String,Number,Boolean类型。

如果使用reactive来封装基础数据类型,会产生警告,同时封装的值不会成为响应式对象。

<script setup>
import { reactive } from "vue";
const count = reactive(0);
</script>

但是,可以使用ref来封装Object、Array等数据类型,内部会调用reactive。

2. 解构reactive对象

下面代码中,count封装成了reactive对象,点击按钮时,count会自增。

<template>
  Counter: {{ state.count }}
  <button @click="add">Increase</button>
 </template>


 <script>
 import { reactive } from "vue";
 export default {
  setup() {
   const state = reactive({ count: 0 });


   function add() {
    state.count++;
   }

   return {
    state,
    add,
   };
  },
 };
 </script>

如果需要使用ES6结构赋值对state进行结构,需要使用如下的代码:

<template>
  <div>Counter: {{ count }}</div>
  <button @click="add">Increase</button>
</template>

<script>
import { reactive } from "vue";
export default {
  setup() {
    const state = reactive({ count: 0 });

    function add() {
      state.count++;
    }

    return {
      ...state,
      add,
    };
  },
};
</script>

结构复制完成之后,点击按钮,效果如下:

代码看起来比较相似,而且根据以前的表现,逻辑是可以正常执行的。但事实上,Vue的响应式追踪通过属性获取,这意味着我们不能去解构响应式对象,会导致引用连接丢失。这是响应式代理的限制之一。

3. 使用.value造成的困惑

Ref接受一个值并返回一个响应式对象,该值只有在内部对象.value属性下可用。

const count = ref(0)

 console.log(count) // { value: 0 }
 console.log(count.value) // 0

 count.value++
 console.log(count.value) // 1

但是ref如果应用在template中,不需要对ref进行解包,也就是不需要使用.vue。

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }} // 不需要调用.value
  </button>
</template>

需要注意的是,解包只作用于一级属性,下边的代码会返回[object Object]

<script setup>
import { ref } from 'vue'
const object = { foo: ref(1) }
</script>
<template>
  {{ object.foo + 1 }}  // [object Object]
</template>

正确使用.value需要时间,初学者偶尔会忘记它。在使用时,要注意只在合适的场景下使用它。

4. Emitted事件

从Vue发布以来,子组件可以通过emits与父组件通信,只需要添加一个自定义的监听器来监听事件即可。

this.$emit('my-event')
<my-component @my-event="doSomething" />

Vue3中,需要使用编译器宏defineEmits来声明emits。

const emit = defineEmits(['my-event'])
 emit('my-event')
 </script>

在setup语法糖下,defineEmits和defineProps会被自动引入。其它情况下,需要主动引入。

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>

最后,由于Vue3中,事件必须声明,因此再需要使用.native修饰符,该修饰符已被移除。

5.声明组件选项

setup不支持如下组件选项声明:

  • name
  • inheritAttrs
  • customOptions

如果需要继续使用这些属性,可以声明多个script脚本,如下所示:

<script>
  export default {
    name: 'CustomName',
    inheritAttrs: false,
    customOptions: {}
  }
</script>

<script setup>
  // script setup logic
</script>

6.使用Reactivity Transform

Reactivity Transform是Vue3中一个预览属性,有一定的争议性,默认是禁用的。它主要用来简化组件的声明方式。这个想法是利用编译时转换来自动解包ref,从而避免使用.value。从Vue3.3中已经删除该功能,作为一个扩展包提供。由于它不是Vue的核心部分,且目前风险还是比较多,建议先不要在此方面投入太多事件。感兴趣可以参考文章:Reactivity Transform

7. 定义异步组件

异步组件以前是通过将它们包含在方法中来声明的。

const asyncModal = () => import('./Modal.vue')

Vue3中需要使用defineAsyncComponent来声明异步组件。

import { defineAsyncComponent } from 'vue'
 const asyncModal = defineAsyncComponent(() 
=> import('./Modal.vue'))

8. template中使用不必要的包装元素

<!-- Layout.vue -->
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Vue3中支持多个根元素,不再需要使用外层div元素包裹。

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

9. 生命周期函数

所有组件生命周期函数都通过添加on前缀或完全更名实现,下图详细列出了具体的变化:

10. 产品文档

官方对文档已经做了更新,补充更新了API,并包含很多有价值的注释、指南和最佳实践。即使你现在使用的是Vue2,通过阅读新的文档也会学到一些新知识。

总结

每个框架都有学习曲线,Vue3相对Vue2更加陡峭,在框架切换之间也会有一定的学习成本。但Vue3组合式API相对Vue2选项式API确实更加简洁易用。如果您在使用过程中有什么疑问,也欢迎留言交流。

本文翻译自文章:

https://fadamakis.com/10-mistakes-to-avoid-when-starting-with-vue-3-1d1ced8552ae

扩展链接:

Spring Boot框架下实现Excel服务端导入导出

项目实战:在线报价采购系统(React +SpreadJS+Echarts)

React + Springboot + Quartz,从0实现Excel报表自动化

如何使用Vite创建Vue3的uniapp项目 - CanotStop - 博客园

mikel阅读(852)

来源: 如何使用Vite创建Vue3的uniapp项目 – CanotStop – 博客园

项目结构

my-vue3-project
├─ .env //默认环境变量
├─ .env.development //开发环境变量
├─ .eslintrc-auto-import.json //(autoimport变量,eslint配置)由auto-import插件生成
├─ .eslintrc.js //eslint配置文件
├─ .gitignore
├─ auto-imports.d.ts //(autoimport变量,ts声明文件)由auto-import插件生成
├─ index.html
├─ jsconfig.json
├─ package-lock.json
├─ package.json
├─ src
│ ├─ App.vue
│ ├─ components
│ ├─ main.js
│ ├─ manifest.json //uniapp项目配置文件
│ ├─ pages
│ │ └─ index
│ │ └─ IndexView.vue
│ ├─ pages.json //页面配置文件
│ ├─ services // 请求后端服务目录
│ │ ├─ api.js // 后端api
│ │ └─ http.js //请求
│ ├─ shime-uni.d.ts
│ ├─ static //静态文件目录
│ │ └─ logo.png
│ ├─ store //pinia全局状态库
│ │ └─ useUserStore.js
│ ├─ uni.scss
│ └─ utils //公共的工具方法
└─ vite.config.js

创建历程

项目创建

npx degit dcloudio/uni-preset-vue#vite my-vue3-project
  • Vue3/Vite 版要求 node 版本^14.18.0 || >=16.0.0
  • 如果使用 HBuilderX(3.6.7 以下版本)运行 Vue3/Vite 创建的最新的 cli 工程,需要在 HBuilderX 运行配置最底部设置 node 路径 为自己本机高版本 node 路径(注意需要重启 HBuilderX 才可以生效)
  • HBuilderX Mac 版本菜单栏左上角 HBuilderX->偏好设置->运行配置->node 路径
  • HBuilderX Windows 版本菜单栏 工具->设置->运行配置->node 路径

项目依赖安装

Eslint

Eslint 的相关配置可参考文章:Web 项目如何配置 Eslint
注意:在配置完 Eslint 之后,在项目中使用 uni 的时候会报 uni 未被定义的错,需在.eslintrc.js 中加上以下代码

globals: {
uni: true;
}

vite.config.js 中 eslintPlugin 配置

import eslintPlugin from ‘vite-plugin-eslint’
export default defineConfig({
plugins: [
eslintPlugin({
cache: true,
include: [‘src/**/*.js’, ‘src/**/*.vue’, ‘src/*.js’, ‘src/*.vue’, ‘src/*.nvue’],
failOnError: false // eslint报错不影响运行
}),
]

Pinia

安装

注意:在这个项目中安装的 vue 版本是:3.2.47,pinia:3.1 及以上版本需要依赖 vue:3.3 及以上版本才能运行成功。

@REM npm 安装
npm i pinia@2.0
@REM node 安装
yarn add pinia@2.0

安装依赖时出现如下报错可尝试通过参考文章解决:npm 安装依赖时出现 Peer Dependencies 冲突报错

While resolving: uni-preset-vue@0.0.0
Found: vue@3.2.47
node_modules/vue
peer vue@”^3.2.25″ from @vitejs/plugin-vue@4.3.4
node_modules/@vitejs/plugin-vue
Conflicting peer dependency: vue@3.3.4
node_modules/vue
peer vue@”>= 2.5 < 2.7″ from @vue/composition-api@1.7.2
node_modules/@vue/composition-api
peerOptional @vue/composition-api@”^1.4.0″ from pinia@2.0.34
node_modules/pinia
pinia@”2.0.34″ from the root project
Fix the upstream dependency conflict, or retry
this command with –force or –legacy-peer-deps
to accept an incorrect (and potentially broken) dependency resolution.

Luch-Request

uniapp 原生的 uni.request 使用比较麻烦,它并非使用 Promise 的形式也不支持请求拦截和相应拦截的配置,而 luch-request 基于 Promise 对象实现更简单的 request 使用方式,支持请求和响应拦截

  • 支持全局挂载
  • 支持多个全局配置实例
  • 支持自定义验证器
  • 支持文件上传/下载
  • 支持 task 操作
  • 支持自定义参数
  • 支持多拦截器
  • 对参数的处理比 uni.request 更强

安装

npm install luch-request -S

Sass 支持

安装

npm i sass sass-loader -D

unplugin-auto-import

unplugin-auto-import 是为 Vite、Webpack、Rollup 和 esbuild 按需自动导入 API,同时支持 TypeScript。
使用它在 vue3 项目中使用预设导入的 api 就不需要 import,可以通过预设自动导入模块,增强开发体验

安装

npm i -D unplugin-auto-import

在 Vite.config.js 中加入如下配置

import AutoImport from “unplugin-auto-import/vite”;
export default defineConfig({
plugins: [
AutoImport({
// 需要自动引入的文件
include: [
/\.[j]sx?$/,
/\.vue$/,
/\.vue\?vue/,
/\.nvue$/,
/\.nvue\?nvue/,
/\.md$/,
],
// 全局自动引入api预设
imports: [
// 插件预设支持导入的api
“vue”,
// ‘vue-router’,
“pinia”,
“uni-app”,
// 自定义导入的api
],
// eslint配置
eslintrc: {
enabled: true, // Default `false`
filepath: “./.eslintrc-auto-import.json”, // Default `./.eslintrc-auto-import.json`
globalsPropValue: true, // Default `true`, (true | false | ‘readonly’ | ‘readable’ | ‘writable’ | ‘writeable’)
},
dts: “./auto-imports.d.ts”,
// dts:false
}),
],
});

生成 eslint 配置文件并使用

通过执行命令npm run dev:h5 运行项目,运行成功时项目根目录会生成auto-imports.d.ts.eslintrc-auto-import.json两个文件,然后在.eslintrc.js 文件中的 extends 属性中,引入该文件

extends: [
‘standard’,
‘plugin:vue/vue3-essential’,
‘./.eslintrc-auto-import.json’
]

注意:引入后需重新运行项目才能生效

添加默认和开发环境变量文件

在项目根目录添加.env 和.env.development 文件,以配置环境变量
详细配置教程可参照官网:Vite 环境变量和模式

配置 UI 库 uni-ui

安装

npm i @dcloudio/uni-ui

配置

vite.config.js

注意 cli 项目默认是不编译 node_modules 下的组件的,导致条件编译等功能失效 ,导致组件异常 需要在 vite.config.js 增加 @dcloudio/uni-ui 包的编译即可正常

transpileDependencies: [“@dcloudio/uni-ui”];

pages.json

正常来说这样子引入的话,使用组件时,需要在页面处 import 才可以使用,然而可以通过 npm+easycom 的形式来全局引入组件。在 pages.json 中加入如下代码,即可在使用组件而不需在页面处 import。

“easycom”: {
“autoscan”: true,
“custom”: {
“^uni-(.*)”: “@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue”
}
}

项目到这里就结束了,这里提供该项目的 github 地址,方便拉取直接使用

这个项目中 master 分支中使用的 Eslint 风格为 Standard,而 prettier 分支使用的风格为 Prettier

https://github.com/luxigaola/my-uniapp-project

  1. 内容来自 uniapp 官网,详情可查看使用 vue-cli 查看目录 ↩︎
  2. pinia 官网 ↩︎
  3. luch-request 官网 ↩︎
  4. sass 官网 ↩︎
  5. unplugin-auto-import 官网 ↩︎
  6. uni-ui Github 地址 ↩︎

10款Visual Studio实用插件 - 追逐时光者 - 博客园

mikel阅读(923)

来源: 10款Visual Studio实用插件 – 追逐时光者 – 博客园

10款Visual Studio实用插件 

前言

俗话说的好工欲善其事必先利其器,安装一些Visual Studio实用插件对自己日常的开发和工作效率能够大大的提升,避免996从选一款好的IDE实用插件开始。以下是我认为比较实用的Visual Studio插件希望对大家有用,大家有更好的插件推荐可在文末留言🤞。

Visual Studio插件搜索

扩展=>管理扩展:

Visual Studio插件市场

https://marketplace.visualstudio.com/👉

ReSharper(付费)

ReSharper 是一个用于 Visual Studio 的强大插件,它为.NET开发者提供了许多功能,以提高编码效率、代码质量和开发体验。旨在帮助.NET开发者编写高质量、高效的代码,并提供了许多辅助工具来改进开发流程和减少常见的编码错误。(注意电脑内存小的同学慎用,比较占用内存)

插件市场地址:https://marketplace.visualstudio.com/items?itemName=JetBrains.ReSharper👉

GitHub Copilot(付费)

GitHub Copilot 是一款AI辅助工具,帮助开发者更快速、智能地编写高质量代码。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=GitHub.copilotvs👉

CodeMaid(免费)

CodeMaid 是 Visual Studio 的开源扩展,用于清理和简化 C#、C++、F#、VB、PHP、PowerShell、R、JSON、XAML、XML、ASP、HTML、CSS、LESS、SCSS、JavaScript 和 TypeScript 编码。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=SteveCadwallader.CodeMaidVS2022👉

CSharpier(免费)

CSharpier 是一个用于 C# 代码的代码格式化工具。它是一个基于 Roslyn 的工具,它会解析您的 C# 代码,然后根据特定的规则重新格式化它,使其符合 CSharpier 的代码风格。这个工具的设计目的是使代码风格保持一致,从而提高代码的可读性和可维护性。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=csharpier.CSharpier#report-abuse👉

格式之前:

格式之后:

Visual Studio Theme Pack(免费)

Visual Studio流行主题集。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=idex.vsthemepack👉

Indent Guides(免费)

显示代码缩进线,有助于保持代码的结构清晰。页宽标记有三种样式:实线、点线面和虚线,有粗细之分,颜色也可自定义。默认为灰色虚线,如图所示。每个缩进级别可以有不同的样式和颜色。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=SteveDowerMSFT.IndentGuides2022👉

Markdown Editor(免费)

功能齐全的 Markdown 编辑器,具有实时预览和语法高亮功能。支持 GitHub 版本的 Markdown。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor👉

HTML Snippet Pack(免费)

它提供了一组预定义的 HTML 代码段(snippets),以帮助开发者更快速地编写 HTML 代码。这些代码段是预先定义的代码模板,可以通过简单的代码缩写触发,然后自动生成相应的 HTML 代码块,从而提高编写 HTML 的效率和准确性。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=MadsKristensen.HTMLSnippetPack👉

Ozcode(免费)

Ozcode 是一个用于 Visual Studio 的插件拓展,旨在优化和简化 C# 调试过程。它提供了一系列功能,可以帮助开发人员更快速地检测和解决代码中的错误,从而提高调试效率和开发生产力。这个插件拓展使调试变得更加直观和高效,有助于加速软件开发周期。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=CodeValueLtd.OzCode👉

Live Share(免费)

它的主要功能是支持实时协作开发,并让开发人员能够在他们最喜欢的开发工具中进行协作。

插件市场地址:https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsls-vs👉

webapi开发框架实践 - 星仔007 - 博客园

mikel阅读(493)

来源: webapi开发框架实践 – 星仔007 – 博客园

项目链接以及目录结构

liuzhixin405/efcore-template (github.com)

这是一个纯webapi的开发框架。

1、支持的orm有efcore6、dapper,可以灵活切换数据库。

复制代码
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Elfie.Model;
using Microsoft.EntityFrameworkCore;
using project.Context;
using project.Repositories;
using project.Services;
using RepositoryComponent.DbFactories;

namespace project.Extensions
{
    public static partial class TheExtensions
    {
        public static void AddDatabase(this WebApplicationBuilder builder)
        {
            ///sqlserver   
            if (builder.Configuration["DbType"]?.ToLower() == "sqlserver")
            {
                builder.Services.AddDbContext<ReadProductDbContext>(options => options.UseSqlServer(builder.Configuration["ConnectionStrings:SqlServer:ReadConnection"]), ServiceLifetime.Scoped);
                builder.Services.AddDbContext<WriteProductDbContext>(options => options.UseSqlServer(builder.Configuration["ConnectionStrings:SqlServer:WriteConnection"]), ServiceLifetime.Scoped);

            }
            ///mysql
            else if (builder.Configuration["DbType"]?.ToLower() == "mysql")
            {
                builder.Services.AddDbContext<ReadProductDbContext>(options => options.UseMySQL(builder.Configuration["ConnectionStrings:MySql:ReadConnection"]), ServiceLifetime.Scoped);
                builder.Services.AddDbContext<WriteProductDbContext>(options => options.UseMySQL(builder.Configuration["ConnectionStrings:MySql:WriteConnection"]), ServiceLifetime.Scoped);

            }
            else
            {
                //throw new ArgumentNullException("δ����ȷ��ע�����ݿ�");
                builder.Services.AddDbContext<ReadProductDbContext>(options => options.UseInMemoryDatabase("test_inmemory_db"), ServiceLifetime.Scoped);
                builder.Services.AddDbContext<WriteProductDbContext>(options => options.UseInMemoryDatabase("test_inmemory_db"), ServiceLifetime.Scoped);

            }

            builder.Services.AddScoped<Func<ReadProductDbContext>>(provider => () => provider.GetService<ReadProductDbContext>() ?? throw new ArgumentNullException("ReadProductDbContext is not inject to program"));
            builder.Services.AddScoped<Func<WriteProductDbContext>>(provider => () => provider.GetService<WriteProductDbContext>() ?? throw new ArgumentNullException("WriteProductDbContext is not inject to program"));

            builder.Services.AddScoped<DbFactory<WriteProductDbContext>>();
            builder.Services.AddScoped<DbFactory<ReadProductDbContext>>();

            builder.Services.AddTransient<IReadProductRepository, ProductReadRepository>();
            builder.Services.AddTransient<IWriteProductRepository, ProductWriteRepository>();
            builder.Services.AddTransient<IProductService, ProductService>();

            builder.Services.AddTransient<ICustomerService, CustomerService>();
        }
    }
}
复制代码

2、至于消息中间件有rabbitmq、kafka,也是通过配置文件来指定哪一个实现。

复制代码
using MessageMiddleware.Factory;
using MessageMiddleware.RabbitMQ;

namespace project.Extensions
{
    public static partial class TheExtensions
    {
        public static void AddMq(this WebApplicationBuilder builder)
        {
            var rabbitMqSetting = new RabbitMQSetting
            {
                ConnectionString = builder.Configuration["MqSetting:RabbitMq:ConnectionString"].Split(';'),
                Password = builder.Configuration["MqSetting:RabbitMq:PassWord"],
                Port = int.Parse(builder.Configuration["MqSetting:RabbitMq:Port"]),
                SslEnabled = bool.Parse(builder.Configuration["MqSetting:RabbitMq:SslEnabled"]),
                UserName = builder.Configuration["MqSetting:RabbitMq:UserName"],
            };
            var kafkaSetting = new MessageMiddleware.Kafka.Producers.ProducerOptions
            {
                BootstrapServers = builder.Configuration["MqSetting:Kafka:BootstrapServers"],
                SaslUsername = builder.Configuration["MqSetting:Kafka:SaslUserName"],
                SaslPassword = builder.Configuration["MqSetting:Kafka:SaslPassWord"],
                Key = builder.Configuration["MqSetting:Kafka:Key"]
            };
            var mqConfig = new MQConfig
            {
                ConsumerLog = bool.Parse(builder.Configuration["MqSetting:ConsumerLog"]),
                PublishLog = bool.Parse(builder.Configuration["MqSetting:PublishLog"]),
                Rabbit = rabbitMqSetting,
                Use = int.Parse(builder.Configuration["MqSetting:Use"]),
                Kafka = kafkaSetting
            };
            builder.Services.AddSingleton<MQConfig>(sp => mqConfig);
            builder.Services.AddMQ(mqConfig);
        }
    }
}
复制代码

3、该项目还集成了mongodb和elasticsearch,在project项目中没有写实现案例,实现起来也很简单。

4、下面是分布式雪花id的实现,先注入代码,使用的时候直接使用distributedid即可。

复制代码
 builder.Services.AddDistributedLock(x =>
 {
     x.LockType = LockType.InMemory;
     x.RedisEndPoints = new string[] { builder.Configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串")};
 }).AddCache(new CacheOptions
 {
     CacheType = CacheTypes.Redis,
     RedisConnectionString = builder.Configuration["DistributedRedis:ConnectionString"] ?? throw new Exception("$未能获取distributedredis连接字符串")
 }).AddDistributedId(new DistributedIdOptions
 {
     Distributed = true
 });
复制代码
1
newProduct.Id = _distributedId.NewLongId().ToString();

5、缓存使用的是分布式缓存和内存缓存,其中分布式缓存有一般实现和指定序列化格式的实现。

复制代码
using System.Text;
using System.Text.Json.Serialization;
using MessagePack;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Core.Abstractions;
using StackExchange.Redis.Extensions.Core.Configuration;
using StackExchange.Redis.Extensions.Core.Implementations;

namespace project.Utility.Helper
{
    public class CacheHelper
    {
        private static IRedisClientFactory _factory_with_msgpack;
        private static IRedisDatabase _redis_with_msgpack => _factory_with_msgpack.GetDefaultRedisDatabase();

        private static IRedisClientFactory _factory;
        private static IRedisDatabase _redis => _factory.GetDefaultRedisDatabase();
        public static void Init(IConfiguration configuration)
        {
            var config = configuration.GetSection("Redis").Get<RedisConfiguration>();
            _factory = new RedisClientFactory(new[] { config }, null, new RedisSerializer());
            _factory_with_msgpack = new RedisClientFactory(new[] { config }, null, new RedisMessagepackSerializer());
        }
        static CacheHelper() { }

        public static T Get<T>(string key)
        {
            return _redis.GetAsync<T>(key).GetAwaiter().GetResult();
        }
        public static async Task<T> GetAsync<T>(string key)
        {
            return await _redis.GetAsync<T>(key);
        }
        public static async Task<T> GetAsync_With_Msgpack<T>(string key)
        {
            return await _redis_with_msgpack.GetAsync<T>(key);
        }

        public static string Get(string key)
        {
            return _redis.GetAsync<string>(key).GetAwaiter().GetResult();
        }

        public static bool Set(string key, object value, TimeSpan expiresIn)
        {
            return _redis.AddAsync(key, value, expiresIn).GetAwaiter().GetResult();
        }
        public static async Task<bool> SetAsync(string key, object value, TimeSpan expiresIn)
        {
            return await _redis.AddAsync(key, value, expiresIn);
        }

        public static async Task<bool> SetAsync_With_Msgpack(string key, object value, TimeSpan expiresIn)
        {
            return await _redis_with_msgpack.AddAsync(key, value, expiresIn);
        }

        /// <summary>
        /// 以秒为单位,返回给定 key 的剩余生存时间
        /// </summary>

        public static long GetExpirin(string key)
        {
            var timespan = _redis.Database.KeyTimeToLive(key);
            if (timespan == null) { return 0; }
            return (long)timespan.Value.TotalSeconds;
        }
        public static bool KeyExpire(string key, TimeSpan expiresIn)
        {
            return _redis.Database.KeyExpire(key, expiresIn);
        }
        public static async Task<bool> RemoveKeyAsync(string key)
        {
            return await _redis.Database.KeyDeleteAsync(key);
        }
        public static long RemoveKey(string key)
        {
            var result = _redis.Database.KeyDelete(key);
            return result ? 1 : 0;
        }
    }


    public class RedisSerializer : ISerializer
    {
        public T? Deserialize<T>(byte[] serializedObject)
        {
            var data = Encoding.UTF8.GetString(serializedObject);
            return System.Text.Json.JsonSerializer.Deserialize<T>(data);
        }

        public byte[] Serialize<T>(T? item)
        {
            var data = System.Text.Json.JsonSerializer.Serialize(item);
            return Encoding.UTF8.GetBytes(data);
        }
    }

    public class RedisMessagepackSerializer : ISerializer
    {
        private MessagePackSerializerOptions _options;
        public RedisMessagepackSerializer()
        {
            _options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);
        }
        public T? Deserialize<T>(byte[] serializedObject)
        {
            return MessagePackSerializer.Deserialize<T>(serializedObject, _options);
        }

        public byte[] Serialize<T>(T? item)
        {
            return MessagePackSerializer.Serialize(item, _options);
        }
    }
}
复制代码

6、单元测试、集成测试没有写。

更细节的需要自己看代码,这应该是一个基本的开发具备的功能。

该项目下载下来可以直接运行。

阿里云图生视频工具:I2VGen-XL - image to video - AIBetas

mikel阅读(1177)

来源: 阿里云图生视频工具:I2VGen-XL – image to video – AIBetas

2VGen-XL简介

I2VGen-XL项目由阿里云达摩院研发,用于根据输入图像生成高清视频。模型经大规模视频和图像数据混合预训练,并在少量精品数据上微调,具有良好的泛化性。相比现有的视频生成模型,I2VGen-XL在清晰度、质感、语义、时序连续性等方面具有优势。

  • [💡] I2VGen-XL模型的核心部分包含两个阶段,分别解决语义一致性和清晰度的问题,参数量共计约37亿。
  • [👍] I2VGen-XL模型在大规模视频和图像数据混合预训练,并在少量精品数据上微调,具有良好的泛化性。
  • [🎯] 相比现有的视频生成模型,I2VGen-XL在清晰度、质感、语义、时序连续性等方面具有优势。
  • [🎬] I2VGen-XL模型可以直接生成720P分辨率的视频,无水印,适用更多视频平台,减少许多限制。
  • [🔧] I2VGen-XL模型在处理小目标生成、快速运动目标生成以及生成速度等方面存在局限性。
  • [📚] I2VGen-XL模型的训练数据来源广泛,类别分布广,数据来源于开源数据、视频网站以及其他内部数据,具有多分辨率、长宽比等。

2VGen-XL生成效果

图片[1] - 阿里云图生视频工具:I2VGen-XL - image to video - AIBetas
图片[2] - 阿里云图生视频工具:I2VGen-XL - image to video - AIBetas

2VGen-XL模型体验

Demo:https://modelscope.cn/studios/damo/I2VGen-XL-Demo/summary

图片[3] - 阿里云图生视频工具:I2VGen-XL - image to video - AIBetas

2VGen-XL项目

Paper: https://arxiv.org/abs/2306.02018

项目开源地址:https://modelscope.cn/models/damo/Image-to-Video/summary

Github地址:https://www.github.com/camenduru/I2VGen-XL-colab

.NET开源最全的第三方登录整合库 - CollectiveOAuth - 追逐时光者 - 博客园

mikel阅读(503)

来源: .NET开源最全的第三方登录整合库 – CollectiveOAuth – 追逐时光者 – 博客园

前言

我相信很多同学都对接过各种各样的第三方平台的登录授权获取用户信息(如:微信登录、支付宝登录、GitHub登录等等)。今天给大家推荐一个.NET开源最全的第三方登录整合库:CollectiveOAuth。

官方项目介绍

.Net平台(C#) 史上最全的整合第三方登录的开源库 => 环境支持 .NET Framework 4.5 ~ 4.6.2 和 .NetCore 3.1。目前已包含Github、Gitee、钉钉、百度、支付宝、微信、企业微信、腾讯云开发者平台(Coding)、OSChina、微博、QQ、Google、Facebook、抖音、领英、小米、微软、今日头条、Teambition、StackOverflow、Pinterest、人人、华为、酷家乐、Gitlab、美团、饿了么、等第三方平台的授权登录。

项目特点

  • 全:已集成二十多家第三方平台(国内外常用的基本都已包含),仍然还在持续扩展中!
  • 简:API就是奔着最简单去设计的(见后面快速开始),尽量让您用起来没有障碍感!

企业微信扫码授权快速开始

0、企业微信开发对接文档

文档介绍

企业微信扫码授权登录官方文档地址:https://developer.work.weixin.qq.com/document/path/91025,在进行企业微信扫码授权绑定/登录之前需要先自建应用,同时需要开启网页授权登录,具体自建应用的相关操作可以参考博文:https://developer.aliyun.com/article/1136114

管理平台接入

完成了上面企业微信管理后台的相关配置之后,我们就可以按照文档步骤开始操作了

构造二维码

关于构造企业微信扫码绑定/登录二维码一共有两种方式:构造独立窗口登录二维码、构造内嵌登录二维码,下面简单说一下构造独立窗口登录二维码

构造独立窗口登录二维码

构造独立窗口登录二维码,可以在页面放置一个 button 按钮,添加点击事件,在触发点击事件时访问连接https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE,效果如图:

连接参数说明:

 

1、引入依赖

下载源码,添加Come.CollectiveOAuth类库。

 

2、配置企业微信扫码授权(appsettings.json)中配置

{
  "AppSettings": {
    //企业微信扫码授权
    "CollectiveOAuth_WECHAT_ENTERPRISE_SCAN_ClientId": "xxxxxxxxxxxxxxxxx",
    "CollectiveOAuth_WECHAT_ENTERPRISE_SCAN_AgentId""xxxxxx",
    "CollectiveOAuth_WECHAT_ENTERPRISE_SCAN_ClientSecret""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "CollectiveOAuth_WECHAT_ENTERPRISE_SCAN_RedirectUri""https://yours.domain.com/oauth2/callback?authSource=WECHAT_ENTERPRISE_SCAN"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft""Warning",
      "Microsoft.Hosting.Lifetime""Information"
    }
  },
  "AllowedHosts": "*"
}

构建授权Url方法

        /// <summary>
        /// 构建授权Url方法
        /// </summary>
        /// <param name="authSource"></param>
        /// <returns>RedirectUrl</returns>
        public IActionResult Authorization(string authSource= "WECHAT_ENTERPRISE_SCAN")
        {
            AuthRequestFactory authRequest = new AuthRequestFactory();
            var request = authRequest.getRequest(authSource);
            var authorize = request.authorize(AuthStateUtils.createState());
            Console.WriteLine(authorize);
            return Redirect(authorize);
        }
    public class AuthRequestFactory
    {

        #region 从Webconfig中获取默认配置(可以改造成从数据库中读取)
        public Dictionary<string, ClientConfig> _clientConfigs;

        public Dictionary<string, ClientConfig> ClientConfigs
        {
            get
            {
                if (_clientConfigs == null)
                {
                    var _defaultPrefix = "CollectiveOAuth_";
                    _clientConfigs = new Dictionary<string, ClientConfig>();

                    #region 或者默认授权列表数据
                    var defaultAuthList = typeof(DefaultAuthSourceEnum).ToList().Select(a => a.Name.ToUpper()).ToList();
                    foreach (var authSource in defaultAuthList)
                    {
                        var clientConfig = new ClientConfig();
                        clientConfig.clientId = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_ClientId");
                        clientConfig.clientSecret = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_ClientSecret");
                        clientConfig.redirectUri = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_RedirectUri");
                        clientConfig.alipayPublicKey = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_AlipayPublicKey");
                        clientConfig.unionId = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_UnionId");
                        clientConfig.stackOverflowKey = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_StackOverflowKey");
                        clientConfig.agentId = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_AgentId");
                        clientConfig.scope = AppSettingUtils.GetStrValue($"{_defaultPrefix}{authSource}_Scope");
                        _clientConfigs.Add(authSource, clientConfig);
                    }
                    #endregion
                }
                return _clientConfigs;
            }
        }


        public ClientConfig GetClientConfig(string authSource)
        {
            if (authSource.IsNullOrWhiteSpace())
            {
                return null;
            }

            if (!ClientConfigs.ContainsKey(authSource))
            {
                return null;
            }
            else
            {
                return ClientConfigs[authSource];
            }
        } 
        #endregion

        /**
        * 返回AuthRequest对象
        *
        * @return {@link AuthRequest}
        */
        public IAuthRequest getRequest(string authSource)
        {
            // 获取 CollectiveOAuth 中已存在的
            IAuthRequest authRequest = getDefaultRequest(authSource);
            return authRequest;
        }


        /// <summary>
        /// 获取默认的 Request
        /// </summary>
        /// <param name="authSource"></param>
        /// <returns>{@link AuthRequest}</returns>
        private IAuthRequest getDefaultRequest(string authSource)
        {
            ClientConfig clientConfig = GetClientConfig(authSource);
            IAuthStateCache authStateCache = new DefaultAuthStateCache();

            DefaultAuthSourceEnum authSourceEnum = GlobalAuthUtil.enumFromString<DefaultAuthSourceEnum>(authSource);

            switch (authSourceEnum)
            {

                case DefaultAuthSourceEnum.WECHAT_MP:
                    return new WeChatMpAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.WECHAT_OPEN:
                    return new WeChatOpenAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.WECHAT_ENTERPRISE:
                    return new WeChatEnterpriseAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.WECHAT_ENTERPRISE_SCAN:
                    return new WeChatEnterpriseScanAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.ALIPAY_MP:
                    return new AlipayMpAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.GITEE:
                    return new GiteeAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.GITHUB:
                    return new GithubAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.BAIDU:
                    return new BaiduAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.XIAOMI:
                    return new XiaoMiAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.DINGTALK_SCAN:
                    return new DingTalkScanAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.OSCHINA:
                    return new OschinaAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.CODING:
                    return new CodingAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.LINKEDIN:
                    return new LinkedInAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.WEIBO:
                    return new WeiboAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.QQ:
                    return new QQAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.DOUYIN:
                    return new DouyinAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.GOOGLE:
                    return new GoogleAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.FACEBOOK:
                    return new FackbookAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.MICROSOFT:
                    return new MicrosoftAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.TOUTIAO:
                    return new ToutiaoAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.TEAMBITION:
                    return new TeambitionAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.RENREN:
                    return new RenrenAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.PINTEREST:
                    return new PinterestAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.STACK_OVERFLOW:
                    return new StackOverflowAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.HUAWEI:
                    return new HuaweiAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.KUJIALE:
                    return new KujialeAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.GITLAB:
                    return new GitlabAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.MEITUAN:
                    return new MeituanAuthRequest(clientConfig, authStateCache);

                case DefaultAuthSourceEnum.ELEME:
                    return new ElemeAuthRequest(clientConfig, authStateCache);

                default:
                    return null;
            }
        }
    }

API列表

https://gitee.com/rthinking/CollectiveOAuth#api%E5%88%97%E8%A1%A8

 

项目源码地址

更多实用功能特性欢迎前往开源地址查看👀,别忘了给项目一个Star💖。

https://gitee.com/rthinking/CollectiveOAuth

优秀项目和框架精选

该项目已收录到C#/.NET/.NET Core优秀项目和框架精选中,关注优秀项目和框架精选能让你及时了解C#、.NET和.NET Core领域的最新动态和最佳实践,提高开发效率和质量。坑已挖,欢迎大家踊跃提交PR,自荐(让优秀的项目和框架不被埋没🤞)。

https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.md

加入DotNetGuide技术交流群

  1. 提供.NET开发者分享自己优质文章的群组和获取更多全面的C#/.NET/.NET Core学习资料、视频、文章、书籍,社区组织,工具和常见面试题资源,帮助大家更好地了解和使用 .NET技术。
  2. 在这个群里,开发者们可以分享自己的项目经验、遇到的问题以及解决方案,倾听他人的意见和建议,共同成长与进步。
  3. 可以结识更多志同道合的开发者,甚至可能与其他开发者合作完成有趣的项目。通过这个群组,我们希望能够搭建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值。

C# 怎么用OpenCVSharp4实现图片表格识别

mikel阅读(1346)

要使用OpenCVSharp4来实现图片表格识别,你可以按照以下步骤进行操作:

安装OpenCVSharp4:首先,你需要安装OpenCVSharp4库。你可以通过NuGet包管理器或者从OpenCVSharp的官方网站下载并安装它。

导入必要的命名空间:在你的C#项目中,导入OpenCVSharp4的命名空间,以便能够使用它的功能。

using OpenCvSharp;
using OpenCvSharp.Extensions;

读取图片:使用OpenCVSharp4加载你要进行表格识别的图片。

Mat image = Cv2.ImRead(“your_image_path.jpg”);

图像预处理:在进行表格识别之前,你可能需要对图像进行一些预处理,以增强表格的检测和识别。这包括灰度化、二值化、去噪等操作,具体取决于你的图像特点。

Mat grayImage = new Mat();
Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY);
Cv2.Threshold(grayImage, grayImage, 0, 255, ThresholdTypes.Binary);

表格检测:使用OpenCVSharp4的功能来检测图像中的表格。这可以通过查找图像中的直线、边缘或者轮廓来实现。

HoughLinesP(grayImage, out LineSegmentPoint[] lines, 1, Math.PI / 180, 100, 100, 10);

表格识别:一旦你检测到了表格的线条,你可以使用这些线条来提取表格中的内容。你可以根据线条的位置来切割图像,然后进一步处理每个表格单元格中的内容。

请注意,图像表格识别是一个复杂的任务,它可能需要一些调试和参数调整,以适应不同的图像和表格样式。你可能还需要使用OCR(光学字符识别)工具来提取单元格中的文本信息。

这只是一个基本的示例,实际的实现可能需要更多的步骤和细节。你可能需要深入研究OpenCVSharp4的文档以获取更多关于表格检测和图像处理的信息。

当你已经完成了表格检测并切割了图像以获取表格单元格的区域后,接下来的步骤通常包括:

单元格内容识别:对于每个切割出的表格单元格区域,你可以使用OCR工具来识别文本内容。Tesseract是一个常用的OCR引擎,你可以将其集成到你的C#项目中。以下是一个使用Tesseract的示例:

using Tesseract;

using (var engine = new TesseractEngine(@”tessdataPath”, “eng”, EngineMode.Default))
{
using (var img = Pix.LoadFromFile(“cell_image.png”))
{
using (var page = engine.Process(img))
{
string text = page.GetText();
// 处理识别出的文本内容
}
}
}

这里,tessdataPath应该是Tesseract数据文件的路径,”eng”表示使用英语语言模型,你可以根据需要选择其他语言模型。

处理识别结果:一旦你获取了每个表格单元格的识别文本,你可以根据需要对文本进行进一步的处理,如数据清理、格式化或存储。

结果输出:最后,你可以将识别出的表格数据输出到所需的格式,如Excel、CSV等,或者将其显示在用户界面中

请注意,表格识别是一个具有挑战性的任务,特别是对于复杂的表格和不同的图像质量。你可能需要调整和优化算法、参数和图像预处理步骤,以确保准确性和性能。另外,对于不同的语言和文本字体,OCR的准确性也可能会有所不同。

最好的方式是根据你的具体需求和数据集进行实验和测试,以找到最适合你的表格识别方法。同时,记得保持代码的可维护性和可扩展性,以便随着项目的发展进行进一步的改进和优化。

C#结合OpenCVSharp4图片相似度识别 - 宣君 - 博客园

mikel阅读(1717)

OpenCVSharp4

来源: C#结合OpenCVSharp4图片相似度识别 – 宣君 – 博客园

OpenCVSharp4图片相似度识别

需求背景:需要计算两个图片的相似度,然后将相似的图片进行归纳

1. 图片相似度算法

由于我是CRUD后端仔,对图像处理没什么概念。因此网上调研了几种相似度算法分析其适用场景。

直方图算法

获取要比较的2个图片的直方图数据,然后再将直方图数据归一化比较,最终得到一个相似指数,通过设定相似指数的边界,以此判断是否相同图片。

平均值哈希算法 aHash

转灰度压缩之后计算均值,最终通过像素比较得出哈希值,速度很快,但敏感度很高,稍有变化就会极大影响判定结果,精准度较差。因此比较适用于缩略图比较,最常用的就是以图搜图

感知哈希算法 pHash

在均值哈希基础上加入DCT(离散余弦变化),两次DCT就可以很好的将图像按照频度分开,取左上角高能低频信息做均值哈希,因此,精确度很高,但是速度方面较差一些。相比较aHashpHash更加适合用于缩略图比较,也非常适合比较两个近似图片是否相等。

差异值哈希算法 dHash

灰度压缩之后,比较相邻像素之间差异。假设有10×10的图像,每行10个像素,就会产生9个差异值,一共10行,就一共有9×10=90个差异值。最终生成哈希值即指纹。速度上来说,介于aHashpHash之间,精准度同样也介于aHashpHash之间。

结构相似性算法 SSIM

SSIM(structural similarity),结构相似性,是一种衡量两幅图像相似度的指标。SSIM算法主要用于检测两张相同尺寸的图像的相似度、或者检测图像的失真程度。原论文中,SSIM算法主要通过分别比较两个图像的亮度,对比度,结构,然后对这三个要素加权并用乘积表示。

SSIM算法在设计上考虑了人眼的视觉特性,它能够考虑到图像的结构信息在人的感知上的模糊变化,该模型还引入了一些与感知上的变化有关的感知现象,包含亮度mask和对比mask,结构信息指的是像素之间有着内部的依赖性,尤其是空间上靠近的像素点。这些依赖性携带着目标对象视觉感知上的重要信息。

经过调研对比,这里就选择SSIM算法。

2. 下载OpenCVSharp4

通过NuGet包管理器进行下载。搜索OpenCVSharp4下载。

请注意其描述信息:OpenCV wrapper for .NET. Since this package includes only core managed libraries, another package of native bindings for your OS is required (OpenCvSharp4.runtime.*).

这是说:OpenCV 包只是一个核心库,如需在你的系统上使用,还需要对应的运行时包,这里是Windows系统,因此还需下载 OpenCvSharp4.runtime.win

image


3. 使用

在项目中引入OpenCvSharp

using OpenCvSharp;

由于OpenCVSharp4没有直接提供封装SSIM算法的接口,因此需要自行写这部分代码。完整代码如下

public Scalar Compare_SSIM(string imgFile1, string imgFile2)
{
var image1 = Cv2.ImRead(imgFile1);
var image2Tmp = Cv2.ImRead(imgFile2);
// 将两个图片处理成同样大小,否则会有错误: The operation is neither ‘array op array’ (where arrays have the same size and the same number of channels), nor ‘array op scalar’, nor ‘scalar op array’
var image2 = new Mat();
Cv2.Resize(image2Tmp, image2, new OpenCvSharp.Size(image1.Size().Width, image1.Size().Height));
double C1 = 6.5025, C2 = 58.5225;
var validImage1 = new Mat();
var validImage2 = new Mat();
image1.ConvertTo(validImage1, MatType.CV_32F); //数据类型转换为 float,防止后续计算出现错误
image2.ConvertTo(validImage2, MatType.CV_32F);
Mat image1_1 = validImage1.Mul(validImage1); //图像乘积
Mat image2_2 = validImage2.Mul(validImage2);
Mat image1_2 = validImage1.Mul(validImage2);
Mat gausBlur1 = new Mat(), gausBlur2 = new Mat(), gausBlur12 = new Mat();
Cv2.GaussianBlur(validImage1, gausBlur1, new OpenCvSharp.Size(11, 11), 1.5); //高斯卷积核计算图像均值
Cv2.GaussianBlur(validImage2, gausBlur2, new OpenCvSharp.Size(11, 11), 1.5);
Cv2.GaussianBlur(image1_2, gausBlur12, new OpenCvSharp.Size(11, 11), 1.5);
Mat imageAvgProduct = gausBlur1.Mul(gausBlur2); //均值乘积
Mat u1Squre = gausBlur1.Mul(gausBlur1); //各自均值的平方
Mat u2Squre = gausBlur2.Mul(gausBlur2);
Mat imageConvariance = new Mat(), imageVariance1 = new Mat(), imageVariance2 = new Mat();
Mat squreAvg1 = new Mat(), squreAvg2 = new Mat();
Cv2.GaussianBlur(image1_1, squreAvg1, new OpenCvSharp.Size(11, 11), 1.5); //图像平方的均值
Cv2.GaussianBlur(image2_2, squreAvg2, new OpenCvSharp.Size(11, 11), 1.5);
imageConvariance = gausBlur12 – gausBlur1.Mul(gausBlur2);// 计算协方差
imageVariance1 = squreAvg1 – gausBlur1.Mul(gausBlur1); //计算方差
imageVariance2 = squreAvg2 – gausBlur2.Mul(gausBlur2);
var member = ((2 * gausBlur1.Mul(gausBlur2) + C1).Mul(2 * imageConvariance + C2));
var denominator = ((u1Squre + u2Squre + C1).Mul(imageVariance1 + imageVariance2 + C2));
Mat ssim = new Mat();
Cv2.Divide(member, denominator, ssim);
var sclar = Cv2.Mean(ssim);
return sclar; // 变化率,即差异
}

实际检测效果如下

image

这两幅图的相似度大约是92.21%,基本符合预期

image

这两幅图居然还有约18%的相似度,根据SSIM算法特性,这应该是图片大小的相似。

虽然也是拿来主义,毕竟我不是研究算法的大佬,需要站在巨人肩膀上干活~

做个笔记。

Visual Studio如何使用插件改变主题_visual studio 主题_Liquor999的博客-CSDN博客

mikel阅读(479)

Goodnight Theme

来源: Visual Studio如何使用插件改变主题_visual studio 主题_Liquor999的博客-CSDN博客

Visual Studio自带的主题太过于枯燥,此篇文章将详细讲述如何改变主题。

1.打开Visual Studio,在导航栏选择工具 -> 主题 -> 获取更多主题

此时浏览器会跳转到这个页面。选择一款自己喜欢的主题(可以搜索)点击,这里推荐One Dark Pro主题(本人VS里面有这个主题了,演示的时候使用Goodnight Theme)。

点开之后选择Download下载这个插件。

2.关闭Visual Studio,双击点开.vsix插件**

3.弹出来安装窗口,点击Install,即可成功安装。

安装成功,点击Close即可。

4.打开Visual Studio,在导航栏选择工具 -> 主题 -> Goodnight 就可以使用我们的下载的主题了。

5.效果对比
Goodnight主题效果如下图。

One Dark Pro主题效果如下图。

————————————————
版权声明:本文为CSDN博主「Liquor999」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45959662/article/details/125696277