feat: update vue example using modern-vue-template

This commit is contained in:
byoungd 2022-02-09 10:19:59 +08:00
parent 6c1164c5e0
commit 5187b31a8b
108 changed files with 28401 additions and 10502 deletions

View File

@ -412,6 +412,7 @@ In the Vue runtime environment, the default is the installed code that has been
No back-end API is configured in the `Vue` case. For details, please refer to `React` and `api` to set reverse proxy
> Vue example powered by [**modern-vue-template**](https://github.com/byoungd/modern-vue-template)
## Contribution
Thanks [pleasedmi](https://github.com/pleasedmi)、[Elena211314](https://github.com/Elena211314)、[zb201307](https://github.com/zb201307) for donation

View File

@ -0,0 +1,3 @@
module.exports = {
extends: ['cz'],
}

View File

@ -0,0 +1,33 @@
module.exports = {
types: [
{ value: 'feat', name: 'feat: 增加新功能' },
{ value: 'fix', name: 'fix: 修复bug' },
{ value: 'ui', name: 'ui: 更新UI' },
{ value: 'refactor', name: 'refactor: 代码重构' },
{ value: 'release', name: 'release: 发布' },
{ value: 'deploy', name: 'deploy: 部署' },
{ value: 'docs', name: 'docs: 修改文档' },
{ value: 'test', name: 'test: 增删测试' },
{ value: 'chore', name: 'chore: 更改配置文件' },
{ value: 'style', name: 'style: 样式修改不影响逻辑' },
{ value: 'revert', name: 'revert: 版本回退' },
{ value: 'add', name: 'add: 添加依赖' },
{ value: 'minus', name: 'minus: 版本回退' },
{ value: 'del', name: 'del: 删除代码/文件' },
{ value: 'init', name: 'init: 初始提交' },
],
scopes: [],
messages: {
type: '选择更改类型:\n',
scope: '更改的范围:\n',
// 如果allowcustomscopes为true则使用
// customScope: 'Denote the SCOPE of this change:',
subject: '简短描述:\n',
body: '详细描述. 使用"|"换行:\n',
breaking: 'Breaking Changes列表:\n',
footer: '关闭的issues列表. E.g.: #31, #34:\n',
confirmCommit: '确认提交?',
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
}

View File

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -0,0 +1,16 @@
# server env
VITE_SERVER_ENV = 'development'
# open dev login
VITE_OPEN_DEV_LOGIN = 'YES'
# drop console
VITE_BUILD_DROP_CONSOLE = 'NO'
# base api
VITE_BASE_API = 'https://api.xxx.com'
# Versions
VITE_VER_MAIN = '1'
VITE_VER_SUB = '0.0.1'
VITE_VER_STATE = 'dev'

View File

@ -0,0 +1,16 @@
# server env
VITE_SERVER_ENV = 'production'
# open dev login
VITE_OPEN_DEV_LOGIN = 'NO'
# drop console
VITE_BUILD_DROP_CONSOLE = 'YES'
# base api
VITE_BASE_API = 'https://api.xxx.com'
# Versions
VITE_VER_MAIN = '1'
VITE_VER_SUB = '0.0.1'
VITE_VER_STATE = 'prod'

16
examples/vue/.env.test Normal file
View File

@ -0,0 +1,16 @@
# server env
VITE_SERVER_ENV = 'production'
# open dev login
VITE_OPEN_DEV_LOGIN = 'YES'
# drop console
VITE_BUILD_DROP_CONSOLE = 'NO'
# base api
VITE_BASE_API = 'https://api.xxx.com'
# Versions
VITE_VER_MAIN = '1'
VITE_VER_SUB = '0.0.1'
VITE_VER_STATE = 'prod'

View File

@ -0,0 +1,17 @@
node_modules
public
apps/modern-vue/dist
apps/modern-vue/build
.build/*
.nuxt
.history
apps/modern-vue/static/*
apps/modern-vue/static/js
apps/modern-vue/static/sw.js
apps/modern-vue/static/pdf
apps/modern-vue/static/simple-project
jsconfig.json
package.json
package-lock.json
yarn.lock
types/generated/*

31
examples/vue/.eslintrc.js Normal file
View File

@ -0,0 +1,31 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-strongly-recommended',
'@vue/typescript/recommended',
'@vue/eslint-config-prettier',
'@vue/eslint-config-typescript',
'prettier',
],
parserOptions: {
ecmaVersion: 2020,
},
plugins: ['vue', 'prettier'],
// add your custom rules here
rules: {
'prettier/prettier': [
'error',
{},
{
usePrettierrc: true,
},
],
'@typescript-eslint/ban-ts-comment': 'off',
},
}

14
examples/vue/.gitattributes vendored Normal file
View File

@ -0,0 +1,14 @@
# Don't allow people to merge changes to these generated files, because the result
# may be invalid. You need to run "rush update" again.
pnpm-lock.yaml merge=text
shrinkwrap.yaml merge=binary
npm-shrinkwrap.json merge=binary
yarn.lock merge=binary
# Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic
# syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor
# may also require a special configuration to allow comments in JSON.
#
# For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088
#
*.json linguist-language=JSON-with-Comments

View File

@ -1,23 +1,111 @@
# Logs
logs
*.log
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
build
.vite-ssg-temp
# gen
src/auto-imports.d.ts
src/components.d.ts

10
examples/vue/.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"printWidth": 90,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"proseWrap": "always",
"endOfLine": "auto",
"bracketSpacing": true
}

21
examples/vue/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 han
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

194
examples/vue/README.md Normal file
View File

@ -0,0 +1,194 @@
## Modern Vue
[Modern Vue](https://github.com/byoungd/modern-vue-template) stack 2022 with **Micro front
end** & **Monorepo** 🎉.
Joyful development experience 😄.
The `main` branch will keep clean for quickly creating Vue3 web app.
Monorepo architecture please visit branch
[monorepo](https://github.com/byoungd/modern-vue-template/tree/monorepo).
<p align='center'>
<b>English</b> | <a href="https://github.com/byoungd/modern-vue-template/blob/main/README.zh-CN.md">简体中文</a>
</p>
## Features
- ⚡️ [Vue 3](https://github.com/vuejs/vue-next),
[Vite 2](https://github.com/vitejs/vite), [pnpm](https://pnpm.js.org/),
[ESBuild](https://github.com/evanw/esbuild) - born with fastness
- ⚡️ Build Optimization with compress
- ⚡️ CDN by Uploading static files to OSS
- 🦾 Environmental distinction
- 🦾 **Monorepo** by Rush
- 🎨 Commitlint
- 🎨 Formatting with **prettier** and **pretty-quick**
- 🗂 File based routing
- 📦 Components auto importing
- 🍍 [State Management via Pinia](https://pinia.esm.dev/)
- 📑 Layout system
- [Extend Script Setup Component Name to co-operate with Vue Devtools](https://github.com/vbenjs/vite-plugin-vue-setup-extend)
- 📲 [PWA](https://github.com/antfu/vite-plugin-pwa)
- 🎨 [Windi CSS](https://github.com/windicss/windicss) - next generation utility-first CSS
framework
- 😃
[Use icons from any icon sets, with no compromise](https://github.com/antfu/unplugin-icons)
- 🌍 I18n ready
- 🗒 [Markdown Support](https://github.com/antfu/vite-plugin-md)
- 🔥 Use the [new `<script setup>` syntax](https://github.com/vuejs/rfcs/pull/227)
- 📥 [APIs auto importing](https://github.com/antfu/unplugin-auto-import) - use
Composition API and others directly
- 🖨 Server-side generation (SSG) via [vite-ssg](https://github.com/antfu/vite-ssg)
- 🦔 Critical CSS via [critters](https://github.com/GoogleChromeLabs/critters)
- 🦾 TypeScript, of course
- ⚙️ Unit Testing with [Vitest](https://github.com/vitest-dev/vitest), E2E Testing with
[Cypress](https://cypress.io/) on [GitHub Actions](https://github.com/features/actions)
- ☁️ Deploy on Netlify, zero-config
## Pre-packed
### UI Frameworks
- [Windi CSS](https://github.com/windicss/windicss) (On-demand
[TailwindCSS](https://tailwindcss.com/)) - lighter and faster, with a bunch of
additional features!
- [Windi CSS Typography](https://windicss.org/plugins/official/typography.html)
### Icons
- [Iconify](https://iconify.design) - use icons from any icon sets
[🔍Icônes](https://icones.netlify.app/)
- [`unplugin-icons`](https://github.com/antfu/unplugin-icons) - icons as Vue components
### Plugins
- [Vue Router](https://github.com/vuejs/vue-router)
- [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) - file system
based routing
- [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) -
layouts for pages
- [Pinia](https://pinia.esm.dev) - Intuitive, type safe, light and flexible Store for Vue
using the composition api
- [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components) -
components auto import
- [`unplugin-auto-import`](https://github.com/antfu/unplugin-auto-import) - Directly use
Vue Composition API and others without importing
- [`vite-plugin-pwa`](https://github.com/antfu/vite-plugin-pwa) - PWA
- [`vite-plugin-windicss`](https://github.com/antfu/vite-plugin-windicss) - Windi CSS
Integration
- [`vite-plugin-md`](https://github.com/antfu/vite-plugin-md) - Markdown as components /
components in Markdown
- [`markdown-it-prism`](https://github.com/jGleitz/markdown-it-prism) -
[Prism](https://prismjs.com/) for syntax highlighting
- [`prism-theme-vars`](https://github.com/antfu/prism-theme-vars) - customizable
Prism.js theme using CSS variables
- [Vue I18n](https://github.com/intlify/vue-i18n-next) - Internationalization
- [`vite-plugin-vue-i18n`](https://github.com/intlify/vite-plugin-vue-i18n) - Vite
plugin for Vue I18n
- [VueUse](https://github.com/antfu/vueuse) - collection of useful composition APIs
- [`@vueuse/head`](https://github.com/vueuse/head) - manipulate document head reactively
### Coding Style
- Use Composition API with
[`<script setup>` SFC syntax](https://github.com/vuejs/rfcs/pull/227)
- [ESLint](https://eslint.org/) with
[@antfu/eslint-config](https://github.com/antfu/eslint-config), single quotes, no semi.
### Dev tools
- [TypeScript](https://www.typescriptlang.org/)
- [Vitest](https://github.com/vitest-dev/vitest) - Unit testing powered by Vite
- [Cypress](https://cypress.io/) - E2E testing
- [pnpm](https://pnpm.js.org/) - fast, disk space efficient package manager
- [`vite-ssg`](https://github.com/antfu/vite-ssg) - Server-side generation
- [critters](https://github.com/GoogleChromeLabs/critters) - Critical CSS
- [Netlify](https://www.netlify.com/) - zero-config deployment
- [VS Code Extensions](./.vscode/extensions.json)
- [Vite](https://marketplace.visualstudio.com/items?itemName=antfu.vite) - Fire up Vite
server automatically
- [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) - Vue
3 `<script setup>` IDE support
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) -
Icon inline display and autocomplete
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) -
All in one i18n support
- [Windi CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=voorjaar.windicss-intellisense) -
IDE support for Windi CSS
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
## Try it now!
> Requires Node >=14
### GitHub Template
[Create a repo from this template on GitHub](https://github.com/byoungd/modern-vue-template/generate).
### Clone to local
If you prefer to do it manually with the cleaner git history
## Usage
### Development
```
pnpm i
pnpm dev
```
### Build
To build the App, run
```bash
pnpm build
```
With Env:
```bash
pnpm build:test
```
And you will see the generated file in `dist` that ready to be served.
## Thanks
- [Vitesse](https://github.com/antfu/vitesse)
## Final
enjoy :)

View File

@ -0,0 +1,178 @@
## Modern Vue
[现代化的 Vue 技术栈](https://github.com/byoungd/modern-vue-template) 支持 **Micro front
end** 和 **Monorepo** 🎉。
即刻享受令人愉悦的开发体验 😄。
`main` 分支将保持极简以快速创建 vue3 应用。
`monorepo`架构的项目请查
看[monorepo 分支](https://github.com/byoungd/modern-vue-template/tree/monorepo)
## 特点
- ⚡️ [Vue 3](https://github.com/vuejs/vue-next),
[Vite 2](https://github.com/vitejs/vite), [pnpm](https://pnpm.js.org/),
[ESBuild](https://github.com/evanw/esbuild) - 生而为快
- ⚡️ 打包时进行压缩优化
- ⚡️ 上传静态资源至 OSS 以支持 CDN
- 🦾 通过 .env 文件进行环境隔离 轻松使用多套环境以应对开发和生产环境
- 🦾 使用 `Rush`搭建 `Monorepo`
- 🦾 使用 `micro-app` 作为微前端方案 配合 monorepo 轻松接入多个不同技术栈的项目
- 🎨 支持 `Commitlint` 以规范代码提交
- 🎨 使用 prettier 和 pretty-quick 进行代码自动格式化
- 🗂 基于文件结构的路由系统
- 📦 自动引入组件
- 🍍 [使用 Pinia 进行状态管理](https://pinia.esm.dev/)
- 📑 [布局系统](./src/layouts)
- [扩展 Script Setup 提供组件名称 以更好的配合 Vue Devtools](https://github.com/vbenjs/vite-plugin-vue-setup-extend)
- 📲 [PWA](https://github.com/antfu/vite-plugin-pwa)
- 🎨 [Windi CSS](https://github.com/windicss/windicss) - next generation utility-first CSS
framework
- 😃 [无妥协使用任意 icons](https://github.com/antfu/unplugin-icons)
- 🌍 [I18n](./locales)
- 🗒 [支持 Markdown](https://github.com/antfu/vite-plugin-md)
- 🔥 使用 [ `<script setup>` 写法进行高效开发](https://github.com/vuejs/rfcs/pull/227)
- 📥 [APIs 自动引入](https://github.com/antfu/unplugin-auto-import) - use Composition API
and others directly
- 🖨 服务端页面生成 (SSG) 通过 [vite-ssg](https://github.com/antfu/vite-ssg)
- 🦔 规范的 CSS [critters](https://github.com/GoogleChromeLabs/critters)
- 🦾 全面支持 TypeScript
- ⚙️ 使用 [Vitest] 单元测试 (https://github.com/vitest-dev/vitest), E2E Testing with
[Cypress](https://cypress.io/) on [GitHub Actions](https://github.com/features/actions)
- ☁️ 零配置部署至 Netlify
<br>
## 使用到的库
### UI Frameworks
- [Windi CSS](https://github.com/windicss/windicss) (On-demand
[TailwindCSS](https://tailwindcss.com/)) - lighter and faster, with a bunch of
additional features!
- [Windi CSS Typography](https://windicss.org/plugins/official/typography.html)
### 图标
- [Iconify](https://iconify.design) - use icons from any icon sets
[🔍Icônes](https://icones.netlify.app/)
- [`unplugin-icons`](https://github.com/antfu/unplugin-icons) - icons as Vue components
### 插件
- [Vue Router](https://github.com/vuejs/vue-router)
- [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) - file system
based routing
- [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) -
layouts for pages
- [Pinia](https://pinia.esm.dev) - Intuitive, type safe, light and flexible Store for Vue
using the composition api
- [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components) -
components auto import
- [`unplugin-auto-import`](https://github.com/antfu/unplugin-auto-import) - Directly use
Vue Composition API and others without importing
- [`vite-plugin-pwa`](https://github.com/antfu/vite-plugin-pwa) - PWA
- [`vite-plugin-windicss`](https://github.com/antfu/vite-plugin-windicss) - Windi CSS
Integration
- [`vite-plugin-md`](https://github.com/antfu/vite-plugin-md) - Markdown as components /
components in Markdown
- [`markdown-it-prism`](https://github.com/jGleitz/markdown-it-prism) -
[Prism](https://prismjs.com/) for syntax highlighting
- [`prism-theme-vars`](https://github.com/antfu/prism-theme-vars) - customizable
Prism.js theme using CSS variables
- [Vue I18n](https://github.com/intlify/vue-i18n-next) - Internationalization
- [`vite-plugin-vue-i18n`](https://github.com/intlify/vite-plugin-vue-i18n) - Vite
plugin for Vue I18n
- [VueUse](https://github.com/antfu/vueuse) - collection of useful composition APIs
- [`@vueuse/head`](https://github.com/vueuse/head) - manipulate document head reactively
### 编码风格
- Use Composition API with
[`<script setup>` SFC syntax](https://github.com/vuejs/rfcs/pull/227)
### Dev tools
- [TypeScript](https://www.typescriptlang.org/)
- [Vitest](https://github.com/vitest-dev/vitest) - Unit testing powered by Vite
- [Cypress](https://cypress.io/) - E2E testing
- [pnpm](https://pnpm.js.org/) - fast, disk space efficient package manager
- [`vite-ssg`](https://github.com/antfu/vite-ssg) - Server-side generation
- [critters](https://github.com/GoogleChromeLabs/critters) - Critical CSS
- [Netlify](https://www.netlify.com/) - zero-config deployment
- [VS Code Extensions](./.vscode/extensions.json)
- [Vite](https://marketplace.visualstudio.com/items?itemName=antfu.vite) - Fire up Vite
server automatically
- [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) - Vue
3 `<script setup>` IDE support
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) -
Icon inline display and autocomplete
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) -
All in one i18n support
- [Windi CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=voorjaar.windicss-intellisense) -
IDE support for Windi CSS
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
## 特别鸣谢
- [Vitesse](https://github.com/antfu/vitesse)
## 运行环境
> Node >=14
### GitHub Template
[Create a repo from this template on GitHub](https://github.com/byoungd/modern-vue-template/generate).
## 如何使用
### 本地开发
```
pnpm i
pnpm dev
```
### 打包
打包项目命令为:
```bash
pnpm build
```
使用 Env 环境:
```bash
pnpm build:test
```

View File

@ -0,0 +1,57 @@
{
"users": [
{
"id": 1,
"name": "韩寒"
},
{
"id": 2,
"name": "王奕博"
},
{
"id": 3,
"name": "马包裹"
},
{
"id": 4,
"name": "吴一帆"
}
],
"projects": [
{
"id": 1,
"name": "律所管理",
"personId": 1,
"organization": "南京法奔律师事务所",
"created": 1604989757139
},
{
"id": 2,
"name": "APP研发",
"personId": 2,
"organization": "研发组",
"created": 1604989757139
},
{
"id": 3,
"name": "产品管理",
"personId": 2,
"organization": "产品部门",
"created": 1546300800000
},
{
"id": 4,
"name": "总部管理",
"personId": 3,
"organization": "总部",
"created": 1604980000011
},
{
"id": 5,
"name": "销售管理",
"personId": 4,
"organization": "销售组",
"created": 1546900800000
}
]
}

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
};

View File

@ -0,0 +1,35 @@
/**
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
* https://github.com/anncwb/vite-plugin-compression
*/
import type { Plugin } from 'vite'
import compressPlugin from 'vite-plugin-compression'
export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false
): Plugin | Plugin[] {
const compressList = compress.split(',')
const plugins: Plugin[] = []
if (compressList.includes('gzip')) {
plugins.push(
compressPlugin({
ext: '.gz',
deleteOriginFile,
})
)
}
if (compressList.includes('brotli')) {
plugins.push(
compressPlugin({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
})
)
}
return plugins
}

View File

@ -0,0 +1,15 @@
import * as fs from 'fs'
// calc cdn path suffix
export const calcCdnPathSuffix = (mode = 'development') => {
const v = fs.readFileSync('./config/version.txt', 'utf-8')
const prefix = `desktop/${mode}`
let suffix = `${prefix}/latest`
if (v) {
suffix = `${prefix}/${v}`
}
return suffix
}

View File

@ -0,0 +1,26 @@
// build dir
export const buildDir = 'build'
// public file
export const publicDir = 'public'
// pages dir
export const pagesDir = 'src/pages'
// sub apps fir
export const subAppsDir = 'src/children'
// layout dir
export const layoutsDir = 'src/layouts'
// cdn switcher
export const useCdn = false
// OSS BASE
export const ossBase = 'https://cdn.xxx.com'
// OSS Bucket
export const ossBucket = 'static'
// chunk path
export const chunkPath = 'cdn/chunk'

View File

@ -0,0 +1,42 @@
import * as dotenv from 'dotenv'
export interface ViteEnv {
VITE_URL: string
VITE_BASE_URL: string
VITE_ZIP_NAME: string
VITE_BASE_API: string
VITE_BUILD_DROP_CONSOLE: string
VITE_CDN_BASE: string
}
// 通过dotenv配置 需要加载指定.env文件 这样process.env打印到得就是对应得文件了
// 这里的mode是我们启动时候的参数 npm run dev:prc 得到的mode就是prc
export function loadEnv(mode: string): ViteEnv {
const ret: any = {}
// 在使用之前我们先指定加载哪个环境变量
dotenv.config({
path: `.env.${mode}`, // .env.prc
})
for (const envName of Object.keys(process.env)) {
const realName = (process.env as any)[envName].replace(/\\n/g, '\n')
ret[envName] = realName
// inject VITE_XXX into process.env
process.env[envName] = realName
}
return ret
}
const regExps = (value: string, reg: string): string => {
return value.replace(new RegExp(reg, 'g'), '')
}
// proxy
export function createProxy(targetProxyUrl: string, baseUrl: string) {
return {
[`${baseUrl}`]: {
target: targetProxyUrl,
changeOrigin: true,
rewrite: (path: string) => regExps(path, `${baseUrl}`),
},
}
}

View File

@ -0,0 +1 @@
0.0.1

View File

@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:3333",
"chromeWebSecurity": false
}

View File

@ -0,0 +1,26 @@
context('Basic', () => {
beforeEach(() => {
cy.visit('/')
})
it('basic nav', () => {
cy.url().should('eq', 'http://localhost:3333/')
cy.contains('[Home Layout]').should('exist')
cy.get('#input')
.type('Vitesse{Enter}')
.url()
.should('eq', 'http://localhost:3333/hi/Vitesse')
cy.contains('[Default Layout]').should('exist')
cy.get('.btn').click().url().should('eq', 'http://localhost:3333/')
})
it('markdown', () => {
cy.get('[title="About"]').click().url().should('eq', 'http://localhost:3333/about')
cy.get('pre.language-js').should('exist')
})
})

25
examples/vue/index.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/pwa-192x192.png" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00aba9" />
<meta name="msapplication-TileColor" content="#00aba9" />
<meta name="theme-color" content="#ffffff" />
<script>
;(function () {
const prefersDark =
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
if (setting === 'dark' || (prefersDark && setting !== 'light'))
document.documentElement.classList.toggle('dark', true)
})()
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
## i18n
This directory is to serve your locale translation files. YAML under this folder would be
loaded automatically and register with their filenames as locale code.
Check out [`vue-i18n`](https://github.com/intlify/vue-i18n-next) for more details.
If you are using VS Code, [`i18n Ally`](https://github.com/lokalise/i18n-ally) is
recommended to make the i18n experience better.

View File

@ -0,0 +1,14 @@
button:
about: Über
back: Zurück
go: Los
home: Startseite
toggle_dark: Dunkelmodus umschalten
toggle_langs: Sprachen ändern
intro:
desc: Vite Startvorlage mit Vorlieben
dynamic-route: Demo einer dynamischen Route
hi: Hi, {name}!
aka: Auch bekannt als
whats-your-name: Wie heißt du?
not-found: Nicht gefunden

View File

@ -0,0 +1,14 @@
button:
about: About
back: Back
go: GO
home: Home
toggle_dark: Toggle dark mode
toggle_langs: Change languages
intro:
desc: Opinionated Vite Starter Template
dynamic-route: Demo of dynamic route
hi: Hi, {name}!
aka: Also known as
whats-your-name: What's your name?
not-found: Not found

View File

@ -0,0 +1,14 @@
button:
about: Acerca de
back: Atrás
go: Ir
home: Inicio
toggle_dark: Alternar modo oscuro
toggle_langs: Cambiar idiomas
intro:
desc: Plantilla de Inicio de Vite Dogmática
dynamic-route: Demo de ruta dinámica
hi: ¡Hola, {name}!
aka: También conocido como
whats-your-name: ¿Cómo te llamas?
not-found: No se ha encontrado

View File

@ -0,0 +1,14 @@
button:
about: À propos de
back: Retour
go: Essayer
home: Accueil
toggle_dark: Basculer en mode sombre
toggle_langs: Changer de langue
intro:
desc: Exemple d'application Vite
dynamic-route: Démo de route dynamique
hi: Salut, {name}!
aka: Aussi connu sous le nom de
whats-your-name: Comment t'appelles-tu ?
not-found: Page non trouvée

View File

@ -0,0 +1,14 @@
button:
about: Tentang
back: Kembali
go: Pergi
home: Beranda
toggle_dark: Ubah ke mode gelap
toggle_langs: Ubah bahasa
intro:
desc: Template awal vite
dynamic-route: Contoh rute dinamik
hi: Halo, {name}!
aka: Juga diketahui sebagai
whats-your-name: Siapa nama anda?
not-found: Tidak ditemukan

View File

@ -0,0 +1,13 @@
button:
about: Su di me
back: Indietro
go: Vai
home: Home
toggle_dark: Attiva/disattiva modalità scura
toggle_langs: Cambia lingua
intro:
desc: Modello per una Applicazione Vite
dynamic-route: Demo di rotta dinamica
hi: Ciao, {name}!
whats-your-name: Come ti chiami?
not-found: Non trovato

View File

@ -0,0 +1,13 @@
button:
about: これは?
back: 戻る
go: 進む
home: ホーム
toggle_dark: ダークモード切り替え
toggle_langs: 言語切り替え
intro:
desc: 固執された Vite スターターテンプレート
dynamic-route: 動的ルートのデモ
hi: こんにちは、{name}!
whats-your-name: 君の名は。
not-found: 見つかりませんでした

View File

@ -0,0 +1,13 @@
button:
about: 소개
back: 뒤로가기
go: 이동
home:
toggle_dark: 다크모드 토글
toggle_langs: 언어 변경
intro:
desc: Vite 애플리케이션 템플릿
dynamic-route: 다이나믹 라우트 데모
hi: 안녕, {name}!
whats-your-name: 이름이 뭐예요?
not-found: 찾을 수 없습니다

View File

@ -0,0 +1,14 @@
button:
about: O nas
back: Wróć
go: WEJDŹ
home: Strona główna
toggle_dark: Ustaw tryb nocny
toggle_langs: Zmień język
intro:
desc: Opiniowany szablon startowy Vite
dynamic-route: Demonstracja dynamicznego route
hi: Cześć, {name}!
aka: Znany też jako
whats-your-name: Jak masz na imię?
not-found: Nie znaleziono

View File

@ -0,0 +1,14 @@
button:
about: Sobre
back: Voltar
go: Ir
home: Início
toggle_dark: Alternar modo escuro
toggle_langs: Mudar de idioma
intro:
desc: Modelo Opinativo de Partida de Vite
dynamic-route: Demonstração de rota dinâmica
hi: Olá, {name}!
aka: Também conhecido como
whats-your-name: Qual é o seu nome?
not-found: Não encontrado

View File

@ -0,0 +1,13 @@
button:
about: О шаблоне
back: Назад
go: Перейти
home: Главная
toggle_dark: Включить темный режим
toggle_langs: Сменить язык
intro:
desc: Самостоятельный начальный шаблон Vite
dynamic-route: Демо динамического маршрута
hi: Привет, {name}!
whats-your-name: Как тебя зовут?
not-found: Не найден

View File

@ -0,0 +1,14 @@
button:
about: Hakkımda
back: Geri
go: İLERİ
home: Anasayfa
toggle_dark: Karanlık modu değiştir
toggle_langs: Dilleri değiştir
intro:
desc: Görüşlü Vite Başlangıç Şablonu
dynamic-route: Dinamik rota demosu
hi: Merhaba, {name}!
aka: Ayrıca şöyle bilinir
whats-your-name: Adınız nedir?
not-found: Bulunamadı

View File

@ -0,0 +1,13 @@
button:
about: Về
back: Quay lại
go: Đi
home: Khởi đầu
toggle_dark: Chuyển đổi chế độ tối
toggle_langs: Thay đổi ngôn ngữ
intro:
desc: Ý kiến cá nhân Vite Template để bắt đầu
dynamic-route: Bản giới thiệu về dynamic route
hi: Hi, {name}!
whats-your-name: Tên bạn là gì?
not-found: Không tìm thấy

View File

@ -0,0 +1,14 @@
button:
about: 关于
back: 返回
go: 确定
home: 首页
toggle_dark: 切换深色模式
toggle_langs: 切换语言
intro:
desc: 固执己见的 Vite 项目模板
dynamic-route: 动态路由演示
hi: 你好,{name}
aka: 也叫
whats-your-name: 输入你的名字
not-found: 未找到页面

18
examples/vue/netlify.toml Executable file
View File

@ -0,0 +1,18 @@
[build.environment]
# bypass npm auto install
NPM_FLAGS = "--version"
NODE_VERSION = "16"
[build]
publish = "dist"
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/manifest.webmanifest"
[headers.values]
Content-Type = "application/manifest+json"

3
examples/vue/optimize.ts Normal file
View File

@ -0,0 +1,3 @@
export const excludeDeps = ['vue-demi']
export const includeDeps = ['vue', 'vue-router', '@vueuse/core', '@vueuse/head']

View File

@ -1,67 +1,141 @@
{
"name": "demo-vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@aomao/engine": "^2.6.16",
"@aomao/plugin-alignment": "^2.6.16",
"@aomao/plugin-backcolor": "^2.6.16",
"@aomao/plugin-bold": "^2.6.16",
"@aomao/plugin-code": "^2.6.16",
"@aomao/plugin-codeblock-vue": "^2.6.16",
"@aomao/plugin-file": "^2.6.16",
"@aomao/plugin-math": "^2.6.16",
"@aomao/plugin-fontcolor": "^2.6.16",
"@aomao/plugin-fontsize": "^2.6.16",
"@aomao/plugin-heading": "^2.6.16",
"@aomao/plugin-hr": "^2.6.16",
"@aomao/plugin-image": "^2.6.16",
"@aomao/plugin-indent": "^2.6.16",
"@aomao/plugin-italic": "^2.6.16",
"@aomao/plugin-link-vue": "^2.6.16",
"@aomao/plugin-mark": "^2.6.16",
"@aomao/plugin-mention": "^2.6.16",
"@aomao/plugin-orderedlist": "^2.6.16",
"@aomao/plugin-paintformat": "^2.6.16",
"@aomao/plugin-quote": "^2.6.16",
"@aomao/plugin-redo": "^2.6.16",
"@aomao/plugin-removeformat": "^2.6.16",
"@aomao/plugin-selectall": "^2.6.16",
"@aomao/plugin-strikethrough": "^2.6.16",
"@aomao/plugin-sub": "^2.6.16",
"@aomao/plugin-sup": "^2.6.16",
"@aomao/plugin-table": "^2.6.16",
"@aomao/plugin-tasklist": "^2.6.16",
"@aomao/plugin-underline": "^2.6.16",
"@aomao/plugin-undo": "^2.6.16",
"@aomao/plugin-unorderedlist": "^2.6.16",
"@aomao/plugin-fontfamily": "^2.6.16",
"@aomao/plugin-status": "^2.6.16",
"@aomao/plugin-video": "^2.6.16",
"@aomao/plugin-line-height": "^2.6.16",
"@aomao/toolbar-vue": "^2.6.16",
"ant-design-vue": "^2.2.6",
"core-js": "^3.6.5",
"reconnecting-websocket": "^4.4.0",
"sharedb": "^1.9.2",
"vue": "^3.2.9",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.0-0"
},
"devDependencies": {
"@types/sharedb": "^1.0.14",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.2.9",
"less": "^3.0.4",
"less-loader": "6.0.0",
"typescript": "~4.1.5"
}
}
{
"name": "modern-vue",
"version": "0.0.2",
"private": true,
"scripts": {
"build:ssg": "vite-ssg build",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=6000 vite build --mode development",
"build:dev": "cross-env NODE_OPTIONS=--max-old-space-size=6000 vite build --mode development",
"build:test": "cross-env NODE_OPTIONS=--max-old-space-size=6000 SERVER_ENV=prod NODE_ENV=production vite build --mode test && cross-env MODE=test node ./scripts/upToCdnProd.js",
"build:prod": "cross-env NODE_OPTIONS=--max-old-space-size=6000 SERVER_ENV=prod NODE_ENV=production vite build --mode production && cross-env MODE=production node ./scripts/upToCdnProd.js",
"dev": "vite --port 3333 --open --mode development",
"lint": "eslint \"**/*.{vue,ts,js}\"",
"preview": "vite preview",
"preview-https": "serve dist",
"test": "vitest",
"test:e2e": "cypress open",
"test:unit": "vitest",
"typecheck": "vue-tsc --noEmit",
"format": "prettier --write",
"format:staged": "pretty-quick --staged --write",
"mock:demo": "json-server api/__mock__/db.json --watch --port 3003",
"mock": "json-server mock/db.json --watch --port 3003",
"commit": "git cz",
"git": "git add . && git cz && git push"
},
"dependencies": {
"@micro-zoe/micro-app": "^0.8.3",
"@vueuse/core": "^7.5.4",
"@vueuse/head": "^0.7.5",
"dotenv": "^14.3.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.9",
"prism-theme-vars": "^0.2.2",
"vite-plugin-compression": "^0.4.0",
"vite-plugin-vue-setup-extend": "^0.3.0",
"vue": "^3.2.29",
"vue-demi": "^0.12.1",
"vue-i18n": "^9.1.9",
"vue-router": "^4.0.12",
"@aomao/engine": "^2.6.16",
"@aomao/plugin-alignment": "^2.6.16",
"@aomao/plugin-backcolor": "^2.6.16",
"@aomao/plugin-bold": "^2.6.16",
"@aomao/plugin-code": "^2.6.16",
"@aomao/plugin-codeblock-vue": "^2.6.16",
"@aomao/plugin-file": "^2.6.16",
"@aomao/plugin-math": "^2.6.16",
"@aomao/plugin-fontcolor": "^2.6.16",
"@aomao/plugin-fontsize": "^2.6.16",
"@aomao/plugin-heading": "^2.6.16",
"@aomao/plugin-hr": "^2.6.16",
"@aomao/plugin-image": "^2.6.16",
"@aomao/plugin-indent": "^2.6.16",
"@aomao/plugin-italic": "^2.6.16",
"@aomao/plugin-link-vue": "^2.6.16",
"@aomao/plugin-mark": "^2.6.16",
"@aomao/plugin-mention": "^2.6.16",
"@aomao/plugin-orderedlist": "^2.6.16",
"@aomao/plugin-paintformat": "^2.6.16",
"@aomao/plugin-quote": "^2.6.16",
"@aomao/plugin-redo": "^2.6.16",
"@aomao/plugin-removeformat": "^2.6.16",
"@aomao/plugin-selectall": "^2.6.16",
"@aomao/plugin-strikethrough": "^2.6.16",
"@aomao/plugin-sub": "^2.6.16",
"@aomao/plugin-sup": "^2.6.16",
"@aomao/plugin-table": "^2.6.16",
"@aomao/plugin-tasklist": "^2.6.16",
"@aomao/plugin-underline": "^2.6.16",
"@aomao/plugin-undo": "^2.6.16",
"@aomao/plugin-unorderedlist": "^2.6.16",
"@aomao/plugin-fontfamily": "^2.6.16",
"@aomao/plugin-status": "^2.6.16",
"@aomao/plugin-video": "^2.6.16",
"@aomao/plugin-line-height": "^2.6.16",
"@aomao/toolbar-vue": "^2.6.16",
"ant-design-vue": "^2.2.6",
"core-js": "^3.6.5",
"reconnecting-websocket": "^4.4.0",
"sharedb": "^1.9.2",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@types/sharedb": "^1.0.14",
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"@iconify-json/carbon": "^1.0.14",
"@iconify-json/el": "^1.0.1",
"@iconify-json/mi": "^1.0.1",
"@intlify/vite-plugin-vue-i18n": "^3.2.1",
"@types/glob": "^7.2.0",
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/node": "^17.0.11",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vitejs/plugin-vue": "^2.1.0",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/test-utils": "^2.0.0-rc.18",
"commitlint-config-cz": "^0.13.3",
"critters": "^0.0.16",
"cross-env": "^7.0.3",
"cypress": "^9.3.1",
"cz-conventional-changelog": "^2.1.0",
"cz-customizable": "^6.3.0",
"esdk-obs-nodejs": "^3.21.6",
"eslint": "7",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^8.0.1",
"glob": "^7.2.0",
"https-localhost": "^4.7.0",
"husky": "^7.0.1",
"json-server": "^0.16.3",
"lint-staged": "^11.1.2",
"markdown-it-link-attributes": "^4.0.0",
"markdown-it-prism": "^2.2.2",
"pnpm": "^6.28.0",
"prettier": "^2.3.2",
"prettier-eslint-cli": "^5.0.1",
"pretty-quick": "^3.1.1",
"ts-node": "^10.4.0",
"typescript": "^4.5.5",
"unplugin-auto-import": "^0.5.11",
"unplugin-icons": "^0.13.0",
"unplugin-vue-components": "^0.17.14",
"vite": "^2.7.13",
"vite-plugin-inspect": "^0.3.13",
"vite-plugin-md": "^0.11.7",
"vite-plugin-pages": "^0.20.0",
"vite-plugin-pwa": "^0.11.13",
"vite-plugin-vue-layouts": "^0.5.0",
"vite-plugin-windicss": "^1.6.3",
"vite-ssg": "^0.17.7",
"vitest": "0.1.27",
"vue-tsc": "^0.31.1",
"workbox-window": "^6.4.2",
"less": "^3.0.4",
"less-loader": "6.0.0"
}
}

10055
examples/vue/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
/assets/*
cache-control: max-age=31536000
cache-control: immutable

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
path { fill: #222; }
@media (prefers-color-scheme: dark) {
path { fill: #ffffff; }
}
</style>
<path d="M27.562 26L17.17 8.928l2.366-3.888L17.828 4L16 7.005L14.17 4l-1.708 1.04l2.366 3.888L4.438 26H2v2h28v-2zM16 10.85L25.22 26H17v-8h-2v8H6.78z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,2 @@
User-agent: *
Allow: /

View File

@ -0,0 +1,41 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2916 6015 c-93 -57 -173 -108 -178 -113 -6 -6 7 -36 33 -78 23 -38
86 -141 139 -229 54 -88 135 -221 180 -295 46 -74 94 -155 108 -180 14 -25 29
-52 35 -60 7 -12 -9 -45 -62 -130 -39 -63 -85 -140 -103 -170 -18 -30 -117
-194 -222 -365 -104 -170 -199 -326 -210 -346 -12 -19 -61 -102 -111 -183 -49
-81 -101 -166 -115 -189 -14 -23 -39 -64 -55 -90 -17 -27 -77 -126 -134 -220
-57 -95 -127 -210 -156 -257 -194 -315 -325 -533 -325 -541 0 -5 -4 -9 -10 -9
-5 0 -10 -4 -10 -9 0 -5 -55 -98 -121 -207 -247 -404 -403 -660 -416 -684 -8
-14 -58 -97 -112 -185 l-98 -160 -189 -2 c-104 -1 -225 -2 -269 -2 l-80 -1 1
-210 c0 -116 4 -213 8 -218 11 -11 6107 -9 6114 2 8 13 8 406 0 419 -4 7 -88
10 -265 9 l-259 -2 -50 77 c-27 43 -54 87 -60 98 -6 11 -62 103 -124 205 -62
102 -120 197 -129 212 -9 16 -85 142 -170 280 -85 139 -160 262 -165 273 -6
11 -13 22 -16 25 -3 3 -30 46 -59 95 -30 50 -102 169 -161 265 -59 96 -240
393 -402 660 -163 267 -371 609 -463 760 -92 151 -194 318 -225 370 -31 52
-101 167 -155 255 l-97 160 27 50 c16 27 32 55 36 61 5 5 38 59 74 120 36 60
69 116 74 124 5 8 75 122 155 253 81 131 144 242 141 247 -4 7 -114 76 -183
115 -10 6 -52 32 -95 58 -42 27 -81 46 -87 42 -8 -5 -94 -140 -140 -219 -19
-33 -221 -365 -246 -404 -15 -22 -18 -18 -111 135 -52 87 -123 203 -157 258
-67 108 -67 110 -111 184 -16 28 -34 51 -40 50 -5 0 -86 -47 -179 -104z m739
-1642 c319 -526 519 -854 637 -1046 43 -70 78 -130 78 -133 0 -2 5 -10 10 -17
6 -7 69 -109 140 -227 72 -118 134 -222 139 -230 5 -8 55 -89 111 -180 56 -91
105 -172 110 -180 9 -14 52 -84 270 -445 54 -88 135 -221 180 -295 46 -74 91
-148 100 -165 9 -16 31 -53 48 -81 18 -28 32 -54 32 -57 0 -3 -403 -6 -895 -5
l-895 0 0 81 c-1 45 -1 439 -1 875 l0 792 -37 1 c-57 1 -344 1 -374 0 l-27 -1
0 -832 c0 -458 0 -852 0 -875 l-1 -42 -895 1 c-492 0 -895 3 -895 5 0 9 115
198 122 201 5 2 8 7 8 12 0 5 23 46 51 92 28 46 78 128 112 183 33 55 70 116
82 135 12 19 132 215 265 435 133 220 266 438 295 485 65 105 206 338 220 362
6 10 172 284 370 608 198 325 387 635 420 690 33 55 62 100 65 100 3 0 73
-111 155 -247z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,20 @@
const fs = require('fs')
const { upToHWOSS } = require('./upToHW')
// 得出cdn路径前缀
const calcCdnPathSuffix = (mode) => {
const v = fs.readFileSync('./config/version.txt', 'utf-8')
const prefix = `desktop/${mode}`
let suffix = `${prefix}/latest`
if (v) {
suffix = `${prefix}/${v}`
}
return suffix
}
const mode = process.env.MODE || 'development'
upToHWOSS(calcCdnPathSuffix(mode))

View File

@ -0,0 +1,73 @@
const buildDir = 'build'
const ossBucket = 'mobile-static'
function upToHWOSS(suffix) {
const glob = require('glob')
const path = require('path')
const ObsClient = require('esdk-obs-nodejs')
const isWindow = /^win/.test(process.platform)
let pre = path.resolve(__dirname, `../${buildDir}/`) + (isWindow ? '\\' : '')
const files = glob.sync(
`${path.join(
__dirname,
`../${buildDir}/**/*.?(js|json|ico|css|png|jpg|svg|woff|woff2|ttf|eot|gz)`
)}`
)
pre = pre.replace(/\\/g, '/')
// ObsClient fill your own
const obsClient = new ObsClient({
access_key_id: '',
secret_access_key: '',
server: '',
})
async function uploadFileCDN(files) {
files.map(async (file) => {
let key = getFileKey(pre, file)
key = suffix + '/' + key
try {
await uploadFIle(key, file)
console.log(`upload key: ${key}`)
} catch (err) {
console.log('error', err)
}
})
}
async function uploadFIle(key, localFile) {
return new Promise((resolve, reject) => {
obsClient.putObject(
{
Bucket: ossBucket,
Key: key,
SourceFile: localFile,
},
(err, result) => {
if (err) {
reject(err)
} else {
resolve(result)
}
}
)
})
}
function getFileKey(pre, file) {
if (file.indexOf(pre) > -1) {
const key = file.split(pre)[1]
return key.startsWith('/') ? key.substring(1) : key
}
return file
}
;(async () => {
console.time('uploading to oss...')
await uploadFileCDN(files)
console.timeEnd('upload end')
})()
}
module.exports = {
upToHWOSS,
}

View File

@ -1,29 +1,13 @@
<script setup lang="ts">
// https://github.com/vueuse/head
// you can use this to manipulate the document head in any components,
// they will be rendered correctly in the html results with vite-ssg
useHead({
title: 'Modern Vue',
meta: [{ name: 'description', content: 'Opinionated Vite Starter Template' }],
})
</script>
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
<router-view />
</template>
<style lang="less">
#app{
padding-top: 42px;
}
#nav {
padding: 10px;
position: fixed;
top: 0;
width: 100%;
background: #fff;
z-index: 999;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,8 @@
import type { App } from 'vue'
import Comp from './src/main.vue'
Comp.install = function (Vue: App) {
Vue.component(Comp.name, Comp)
}
export default Comp

View File

@ -0,0 +1,287 @@
import { PluginEntry, CardEntry, PluginOptions, NodeInterface } from '@aomao/engine'
//引入插件 begin
import Redo from '@aomao/plugin-redo'
import Undo from '@aomao/plugin-undo'
import Bold from '@aomao/plugin-bold'
import Code from '@aomao/plugin-code'
import Backcolor from '@aomao/plugin-backcolor'
import Fontcolor from '@aomao/plugin-fontcolor'
import Fontsize from '@aomao/plugin-fontsize'
import Italic from '@aomao/plugin-italic'
import Underline from '@aomao/plugin-underline'
import Hr, { HrComponent } from '@aomao/plugin-hr'
import Tasklist, { CheckboxComponent } from '@aomao/plugin-tasklist'
import Orderedlist from '@aomao/plugin-orderedlist'
import Unorderedlist from '@aomao/plugin-unorderedlist'
import Indent from '@aomao/plugin-indent'
import Heading from '@aomao/plugin-heading'
import Strikethrough from '@aomao/plugin-strikethrough'
import Sub from '@aomao/plugin-sub'
import Sup from '@aomao/plugin-sup'
import Alignment from '@aomao/plugin-alignment'
import Mark from '@aomao/plugin-mark'
import Quote from '@aomao/plugin-quote'
import PaintFormat from '@aomao/plugin-paintformat'
import RemoveFormat from '@aomao/plugin-removeformat'
import SelectAll from '@aomao/plugin-selectall'
import Link from '@aomao/plugin-link-vue'
import Codeblock, { CodeBlockComponent } from '@aomao/plugin-codeblock-vue'
import Image, { ImageComponent, ImageUploader } from '@aomao/plugin-image'
import Table, { TableComponent } from '@aomao/plugin-table'
import File, { FileComponent, FileUploader } from '@aomao/plugin-file'
import Video, { VideoComponent, VideoUploader } from '@aomao/plugin-video'
import Math, { MathComponent } from '@aomao/plugin-math'
import Fontfamily from '@aomao/plugin-fontfamily'
import Status, { StatusComponent } from '@aomao/plugin-status'
import LineHeight from '@aomao/plugin-line-height'
import Mention, { MentionComponent } from '@aomao/plugin-mention'
import {
ToolbarPlugin,
ToolbarComponent,
fontFamilyDefaultData,
} from '@aomao/toolbar-vue'
import Empty from 'ant-design-vue/es/empty'
import 'ant-design-vue/es/empty/style'
import { createApp } from 'vue'
import Loading from '../src/loading.vue'
import MentionPopover from '../src/mention.vue'
const DOMAIN = 'https://editor.aomao.com/api'
export const plugins: Array<PluginEntry> = [
Redo,
Undo,
Bold,
Code,
Backcolor,
Fontcolor,
Fontsize,
Italic,
Underline,
Hr,
Tasklist,
Orderedlist,
Unorderedlist,
Indent,
Heading,
Strikethrough,
Sub,
Sup,
Alignment,
Mark,
Quote,
PaintFormat,
RemoveFormat,
SelectAll,
Link,
Codeblock,
Image,
ImageUploader,
Table,
File,
FileUploader,
Video,
VideoUploader,
Math,
ToolbarPlugin,
Fontfamily,
Status,
LineHeight,
Mention,
]
export const cards: Array<CardEntry> = [
HrComponent,
CheckboxComponent,
CodeBlockComponent,
ImageComponent,
TableComponent,
FileComponent,
VideoComponent,
MathComponent,
ToolbarComponent,
StatusComponent,
MentionComponent,
]
export const pluginConfig: { [key: string]: PluginOptions } = {
[ToolbarPlugin.pluginName]: {
popup: {
items: [
['bold', 'strikethrough', 'fontcolor'],
{
icon: 'text',
items: ['italic', 'underline', 'backcolor', 'moremark'],
},
[
{
type: 'button',
name: 'image-uploader',
icon: 'image',
},
'link',
'tasklist',
'heading',
],
{
icon: 'more',
items: [
{
type: 'button',
name: 'video-uploader',
icon: 'video',
},
{
type: 'button',
name: 'file-uploader',
icon: 'attachment',
},
{
type: 'button',
name: 'table',
icon: 'table',
},
{
type: 'button',
name: 'math',
icon: 'math',
},
{
type: 'button',
name: 'codeblock',
icon: 'codeblock',
},
{
type: 'button',
name: 'orderedlist',
icon: 'ordered-list',
},
{
type: 'button',
name: 'unordered-list',
icon: 'unordered-list',
},
{
type: 'button',
name: 'hr',
icon: 'hr',
},
{
type: 'button',
name: 'quote',
icon: 'quote',
},
],
},
],
},
},
[Italic.pluginName]: {
// 默认为 _ 下划线,这里修改为单个 * 号
markdown: '*',
},
[Image.pluginName]: {
onBeforeRender: (status: string, url: string) => {
if (url.startsWith('data:image/')) return url
return url + `?token=12323`
},
},
[ImageUploader.pluginName]: {
file: {
action: `${DOMAIN}/upload/image`,
headers: { Authorization: 213434 },
},
remote: {
action: `${DOMAIN}/upload/image`,
},
isRemote: (src: string) => src.indexOf(DOMAIN) < 0,
},
[FileUploader.pluginName]: {
action: `${DOMAIN}/upload/file`,
},
[VideoUploader.pluginName]: {
action: `${DOMAIN}/upload/video`,
limitSize: 1024 * 1024 * 50,
},
[Video.pluginName]: {
onBeforeRender: (status: string, url: string) => {
return url + `?token=12323`
},
},
[Math.pluginName]: {
action: `https://g.aomao.com/latex`,
parse: (res: any) => {
if (res.success) return { result: true, data: res.svg }
return { result: false }
},
},
[Mention.pluginName]: {
action: `${DOMAIN}/user/search`,
onLoading: (root: NodeInterface) => {
const vm = createApp(Loading)
vm.mount(root.get<HTMLElement>()!)
},
onEmpty: (root: NodeInterface) => {
const vm = createApp(Empty)
vm.mount(root.get<HTMLElement>()!)
},
onClick: (root: NodeInterface, { key, name }: { key: string; name: string }) => {
console.log('mention click:', key, '-', name)
},
onMouseEnter: (layout: NodeInterface, { name }: { key: string; name: string }) => {
const vm = createApp(MentionPopover, {
name,
})
vm.mount(layout.get<HTMLElement>()!)
},
},
[Fontsize.pluginName]: {
//配置粘贴后需要过滤的字体大小
filter: (fontSize: string) => {
return (
[
'12px',
'13px',
'14px',
'15px',
'16px',
'19px',
'22px',
'24px',
'29px',
'32px',
'40px',
'48px',
].indexOf(fontSize) > -1
)
},
},
[Fontfamily.pluginName]: {
//配置粘贴后需要过滤的字体
filter: (fontfamily: string) => {
const item = fontFamilyDefaultData.find((item) =>
fontfamily
.split(',')
.some(
(name) =>
item.value.toLowerCase().indexOf(name.replace(/"/, '').toLowerCase()) > -1
)
)
return item ? item.value : false
},
},
[LineHeight.pluginName]: {
//配置粘贴后需要过滤的行高
filter: (lineHeight: string) => {
if (lineHeight === '14px') return '1'
if (lineHeight === '16px') return '1.15'
if (lineHeight === '21px') return '1.5'
if (lineHeight === '28px') return '2'
if (lineHeight === '35px') return '2.5'
if (lineHeight === '42px') return '3'
// 不满足条件就移除掉
return ['1', '1.15', '1.5', '2', '2.5', '3'].indexOf(lineHeight) > -1
},
},
}

View File

@ -0,0 +1,2 @@
export const demoContent =
'<h1 style="text-align:center;">民事起诉状</h1><br><h3>原告:<span style="color: #d9d9d9">原某某</span></h3><!--[--><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt;text-indent:2em;"><span><span style="color: #d9d9d9">性别</span></span> <span><span style="color: #d9d9d9">汉族</span></span> <span><span style="color: #d9d9d9">出生日期</span></span><span>出生</span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 地址: <span><span style="color: #d9d9d9">身份证上的住址</span></span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 公民身份号码: <span><span style="color: #d9d9d9">1234567890123</span></span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 联系电话: <span><span style="color: #d9d9d9">18877777777</span></span></p><!--]--><!----><!----><br><!--]--><!--[--><h3>被告:<span style="color: #d9d9d9">被某某</span></h3><!--[--><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"><span><span style="color: #d9d9d9">性别</span></span> <span><span style="color: #d9d9d9">汉族</span></span> <span><span style="color: #d9d9d9">出生日期</span></span><span>出生</span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 地址: <span><span style="color: #d9d9d9">身份证上的住址</span></span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 公民身份号码: <span><span style="color: #d9d9d9">1234567890123</span></span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"> 联系电话: <span><span style="color: #d9d9d9">18877777777</span></span></p><!--]--><!----><!----><br><!--]--><!--]--><br><h3>诉讼请求:</h3><!--[--><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"><span><span style="color: #d9d9d9">诉讼请求</span></span></p><!--]--><br><br><h3>事实与理由:</h3><!--[--><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em;"><span style="color: #d9d9d9">围绕诉求,将与诉求有关的事实于理由写明,注意要简明扼要,抓住要点。</span></p><!--]--><br><br><p style=";text-indent:2em; font-family:宋体">此 致</p><p style=";text-align:left; font-family:宋体">南京市雨花台区人民法院</p><br><br><br><br><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em; ;text-align:right;"> 具状人:<span style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;color:#d9d9d9;"></span></p><p style=";line-height:1.5; font-family:宋体 ;font-size:12pt; ;text-indent:2em; ;text-align:right;">2022年2月8日</p>'

View File

@ -0,0 +1,35 @@
<template>
<a-spin class="loading" :tip="text" :spinning="loading">
<slot></slot>
</a-spin>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ASpin from 'ant-design-vue/es/spin'
import 'ant-design-vue/es/spin/style'
export default defineComponent({
name: 'AmLoading',
components: {
ASpin,
},
props: {
text: String,
loading: Boolean,
},
})
</script>
<style css>
.loading {
padding: 20px;
display: block;
}
.ant-spin-nested-loading {
position: inherit;
}
.ant-spin-nested-loading .ant-spin-container {
position: inherit;
}
</style>

View File

@ -0,0 +1,248 @@
<template>
<AmLoading :loading="loading">
<AmToolbar v-if="engine" :engine="engine" :items="items" />
<div :class="['editor-wrapper', { 'editor-mobile': isMobile }]">
<div class="editor-container text-left">
<div class="editor-content">
<div ref="container"></div>
</div>
</div>
</div>
</AmLoading>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { message, Modal } from 'ant-design-vue'
import Engine, { $, EngineInterface, isMobile } from '@aomao/engine'
import AmToolbar, { GroupItemProps } from '@aomao/toolbar-vue'
import AmLoading from './loading.vue'
import { cards, plugins, pluginConfig } from './config'
import 'ant-design-vue/es/style'
import { demoContent } from './demoData'
// toolbar
const items = isMobile
? ref<GroupItemProps[]>([
['undo', 'redo'],
{
icon: 'text',
items: ['bold', 'italic', 'strikethrough', 'underline', 'moremark'],
},
[
{
type: 'button',
name: 'image-uploader',
icon: 'image',
},
'link',
'tasklist',
'heading',
],
{
icon: 'more',
items: [
{
type: 'button',
name: 'video-uploader',
icon: 'video',
},
{
type: 'button',
name: 'file-uploader',
icon: 'attachment',
},
{
type: 'button',
name: 'table',
icon: 'table',
},
{
type: 'button',
name: 'math',
icon: 'math',
},
{
type: 'button',
name: 'codeblock',
icon: 'codeblock',
},
{
type: 'button',
name: 'orderedlist',
icon: 'orderedlist',
},
{
type: 'button',
name: 'unorderedlist',
icon: 'unorderedlist',
},
{
type: 'button',
name: 'hr',
icon: 'hr',
},
],
},
])
: ref<GroupItemProps[]>([
['collapse'],
['undo', 'redo', 'paintformat', 'removeformat'],
['heading', 'fontfamily', 'fontsize'],
['bold', 'italic', 'strikethrough', 'underline', 'moremark'],
['fontcolor', 'backcolor'],
['alignment'],
['unorderedlist', 'orderedlist', 'tasklist', 'indent', 'line-height'],
['link', 'quote', 'hr'],
])
//
const container = ref<HTMLElement | null>(null)
//
const engine = ref<EngineInterface | null>(null)
//
const loading = ref(true)
onMounted(() => {
//
if (container.value) {
//
const engineInstance = new Engine(container.value, {
//
plugins,
//
cards,
//
config: pluginConfig,
})
// UI使 console.log
engineInstance.messageSuccess = (msg: string) => {
message.success(msg)
}
// UI使 console.error
engineInstance.messageError = (error: string) => {
message.error(error)
}
// UI
engineInstance.messageConfirm = (msg: string) => {
return new Promise<boolean>((resolve, reject) => {
Modal.confirm({
content: msg,
onOk: () => resolve(true),
onCancel: () => reject(),
})
})
}
//
engineInstance.on('card:maximize', () => {
$('.editor-toolbar').css('z-index', '9999').css('top', '55px')
})
engineInstance.on('card:minimize', () => {
$('.editor-toolbar').css('z-index', '').css('top', '')
})
// api
const value = demoContent
// 使 mongodb ot-server/client yarn start ot-server
//
engineInstance.setValue(value, () => {
loading.value = false
})
//
engineInstance.on('change', () => {
console.log('value', engineInstance.getJsonValue())
console.log('html:', engineInstance.getHtml())
})
engine.value = engineInstance
}
})
onUnmounted(() => {
if (engine.value) engine.value.destroy()
})
</script>
<style>
#app {
padding: 0;
}
#nav {
position: relative;
}
.editor-ot-users {
font-size: 12px;
background: #ffffff;
padding: 0px 0 8px 266px;
z-index: 999;
width: 100%;
}
.editor-ot-users-content {
display: flex;
flex-wrap: wrap;
}
.editor-ot-users .ant-avatar {
margin: 0 2px;
}
.editor-toolbar {
position: fixed;
width: 100%;
background: #ffffff;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.02);
z-index: 1000;
}
.editor-wrapper {
position: relative;
width: 100%;
min-width: 1440px;
}
.editor-wrapper.editor-mobile {
min-width: auto;
padding: 0 12px;
}
.editor-container {
background: #fafafa;
background-color: #fafafa;
padding: 62px 0 64px;
height: 100vh;
width: 100%;
margin: 0 auto;
overflow: auto;
position: relative;
}
.editor-mobile .editor-container {
padding: 0;
height: auto;
overflow: hidden;
}
.editor-content {
position: relative;
width: 812px;
margin: 0 auto;
background: #fff;
border: 1px solid #f0f0f0;
overflow: hidden;
min-height: 800px;
}
.editor-mobile .editor-content {
width: auto;
min-height: calc(100vh - 68px);
border: 0 none;
}
.editor-content .am-engine {
padding: 40px 60px 60px;
}
.editor-mobile .editor-content .am-engine {
padding: 18px 0 0 0;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div class="mention-container">
<p>This is name: {{ name }}</p>
<p>配置 mention 插件的 onMouseEnter 方法</p>
<p>此处使用 createApp().mount 自定义渲染</p>
<p>Use createApp().mount to customize rendering here</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'AmMention',
props: {
name: {
type: String,
default: '',
},
},
})
</script>
<style css>
.mention-container {
padding: 5px;
}
</style>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
const props = defineProps<{
initial: number
}>()
const { count, inc, dec } = useCounter(props.initial)
</script>
<template>
<div>
{{ count }}
<button class="inc" @click="inc()">+</button>
<button class="dec" @click="dec()">-</button>
</div>
</template>

View File

@ -0,0 +1,46 @@
<script setup lang="ts">
import { isDark, toggleDark } from '~/composables'
const { t, availableLocales, locale } = useI18n()
const toggleLocales = () => {
// change to some real logic
const locales = availableLocales
locale.value = locales[(locales.indexOf(locale.value) + 1) % locales.length]
}
</script>
<template>
<nav class="text-xl mt-6">
<router-link class="icon-btn mx-2" to="/" :title="t('button.home')">
<carbon-campsite />
</router-link>
<button
class="icon-btn mx-2 !outline-none"
:title="t('button.toggle_dark')"
@click="toggleDark()"
>
<carbon-moon v-if="isDark" />
<carbon-sun v-else />
</button>
<a class="icon-btn mx-2" :title="t('button.toggle_langs')" @click="toggleLocales">
<carbon-language />
</a>
<router-link class="icon-btn mx-2" to="/about" :title="t('button.about')">
<carbon-dicom-overlay />
</router-link>
<a
class="icon-btn mx-2"
rel="noreferrer"
href="https://github.com/antfu/vitesse"
target="_blank"
title="GitHub"
>
<carbon-logo-github />
</a>
</nav>
</template>

View File

@ -0,0 +1,12 @@
## Components
Components in this dir will be auto-registered and on-demand, powered by
[`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).
### Icons
You can use icons from almost any icon sets by the power of
[Iconify](https://iconify.design/).
It will only bundle the icons you use. Check out
[`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details.

View File

@ -1,303 +0,0 @@
import {
PluginEntry,
CardEntry,
PluginOptions,
NodeInterface,
} from '@aomao/engine';
//引入插件 begin
import Redo from '@aomao/plugin-redo';
import Undo from '@aomao/plugin-undo';
import Bold from '@aomao/plugin-bold';
import Code from '@aomao/plugin-code';
import Backcolor from '@aomao/plugin-backcolor';
import Fontcolor from '@aomao/plugin-fontcolor';
import Fontsize from '@aomao/plugin-fontsize';
import Italic from '@aomao/plugin-italic';
import Underline from '@aomao/plugin-underline';
import Hr, { HrComponent } from '@aomao/plugin-hr';
import Tasklist, { CheckboxComponent } from '@aomao/plugin-tasklist';
import Orderedlist from '@aomao/plugin-orderedlist';
import Unorderedlist from '@aomao/plugin-unorderedlist';
import Indent from '@aomao/plugin-indent';
import Heading from '@aomao/plugin-heading';
import Strikethrough from '@aomao/plugin-strikethrough';
import Sub from '@aomao/plugin-sub';
import Sup from '@aomao/plugin-sup';
import Alignment from '@aomao/plugin-alignment';
import Mark from '@aomao/plugin-mark';
import Quote from '@aomao/plugin-quote';
import PaintFormat from '@aomao/plugin-paintformat';
import RemoveFormat from '@aomao/plugin-removeformat';
import SelectAll from '@aomao/plugin-selectall';
import Link from '@aomao/plugin-link-vue';
import Codeblock, { CodeBlockComponent } from '@aomao/plugin-codeblock-vue';
import Image, { ImageComponent, ImageUploader } from '@aomao/plugin-image';
import Table, { TableComponent } from '@aomao/plugin-table';
import File, { FileComponent, FileUploader } from '@aomao/plugin-file';
import Video, { VideoComponent, VideoUploader } from '@aomao/plugin-video';
import Math, { MathComponent } from '@aomao/plugin-math';
import Fontfamily from '@aomao/plugin-fontfamily';
import Status, { StatusComponent } from '@aomao/plugin-status';
import LineHeight from '@aomao/plugin-line-height';
import Mention, { MentionComponent } from '@aomao/plugin-mention';
import {
ToolbarPlugin,
ToolbarComponent,
fontFamilyDefaultData,
} from '@aomao/toolbar-vue';
import Empty from 'ant-design-vue/es/empty';
import 'ant-design-vue/es/empty/style';
import { createApp } from 'vue';
import Loading from './loading.vue';
import MentionPopover from './mention.vue';
const DOMAIN = 'https://editor.aomao.com/api';
export const plugins: Array<PluginEntry> = [
Redo,
Undo,
Bold,
Code,
Backcolor,
Fontcolor,
Fontsize,
Italic,
Underline,
Hr,
Tasklist,
Orderedlist,
Unorderedlist,
Indent,
Heading,
Strikethrough,
Sub,
Sup,
Alignment,
Mark,
Quote,
PaintFormat,
RemoveFormat,
SelectAll,
Link,
Codeblock,
Image,
ImageUploader,
Table,
File,
FileUploader,
Video,
VideoUploader,
Math,
ToolbarPlugin,
Fontfamily,
Status,
LineHeight,
Mention,
];
export const cards: Array<CardEntry> = [
HrComponent,
CheckboxComponent,
CodeBlockComponent,
ImageComponent,
TableComponent,
FileComponent,
VideoComponent,
MathComponent,
ToolbarComponent,
StatusComponent,
MentionComponent,
];
export const pluginConfig: { [key: string]: PluginOptions } = {
[ToolbarPlugin.pluginName]: {
popup: {
items: [
['bold', 'strikethrough', 'fontcolor'],
{
icon: 'text',
items: ['italic', 'underline', 'backcolor', 'moremark'],
},
[
{
type: 'button',
name: 'image-uploader',
icon: 'image',
},
'link',
'tasklist',
'heading',
],
{
icon: 'more',
items: [
{
type: 'button',
name: 'video-uploader',
icon: 'video',
},
{
type: 'button',
name: 'file-uploader',
icon: 'attachment',
},
{
type: 'button',
name: 'table',
icon: 'table',
},
{
type: 'button',
name: 'math',
icon: 'math',
},
{
type: 'button',
name: 'codeblock',
icon: 'codeblock',
},
{
type: 'button',
name: 'orderedlist',
icon: 'ordered-list',
},
{
type: 'button',
name: 'unordered-list',
icon: 'unordered-list',
},
{
type: 'button',
name: 'hr',
icon: 'hr',
},
{
type: 'button',
name: 'quote',
icon: 'quote',
},
],
},
],
},
},
[Italic.pluginName]: {
// 默认为 _ 下划线,这里修改为单个 * 号
markdown: '*',
},
[Image.pluginName]: {
onBeforeRender: (status: string, url: string) => {
if (url.startsWith('data:image/')) return url;
return url + `?token=12323`;
},
},
[ImageUploader.pluginName]: {
file: {
action: `${DOMAIN}/upload/image`,
headers: { Authorization: 213434 },
},
remote: {
action: `${DOMAIN}/upload/image`,
},
isRemote: (src: string) => src.indexOf(DOMAIN) < 0,
},
[FileUploader.pluginName]: {
action: `${DOMAIN}/upload/file`,
},
[VideoUploader.pluginName]: {
action: `${DOMAIN}/upload/video`,
limitSize: 1024 * 1024 * 50,
},
[Video.pluginName]: {
onBeforeRender: (status: string, url: string) => {
return url + `?token=12323`;
},
},
[Math.pluginName]: {
action: `https://g.aomao.com/latex`,
parse: (res: any) => {
if (res.success) return { result: true, data: res.svg };
return { result: false };
},
},
[Mention.pluginName]: {
action: `${DOMAIN}/user/search`,
onLoading: (root: NodeInterface) => {
const vm = createApp(Loading);
vm.mount(root.get<HTMLElement>()!);
},
onEmpty: (root: NodeInterface) => {
const vm = createApp(Empty);
vm.mount(root.get<HTMLElement>()!);
},
onClick: (
root: NodeInterface,
{ key, name }: { key: string; name: string },
) => {
console.log('mention click:', key, '-', name);
},
onMouseEnter: (
layout: NodeInterface,
{ name }: { key: string; name: string },
) => {
const vm = createApp(MentionPopover, {
name,
});
vm.mount(layout.get<HTMLElement>()!);
},
},
[Fontsize.pluginName]: {
//配置粘贴后需要过滤的字体大小
filter: (fontSize: string) => {
return (
[
'12px',
'13px',
'14px',
'15px',
'16px',
'19px',
'22px',
'24px',
'29px',
'32px',
'40px',
'48px',
].indexOf(fontSize) > -1
);
},
},
[Fontfamily.pluginName]: {
//配置粘贴后需要过滤的字体
filter: (fontfamily: string) => {
const item = fontFamilyDefaultData.find((item) =>
fontfamily
.split(',')
.some(
(name) =>
item.value
.toLowerCase()
.indexOf(name.replace(/"/, '').toLowerCase()) >
-1,
),
);
return item ? item.value : false;
},
},
[LineHeight.pluginName]: {
//配置粘贴后需要过滤的行高
filter: (lineHeight: string) => {
if (lineHeight === '14px') return '1';
if (lineHeight === '16px') return '1.15';
if (lineHeight === '21px') return '1.5';
if (lineHeight === '28px') return '2';
if (lineHeight === '35px') return '2.5';
if (lineHeight === '42px') return '3';
// 不满足条件就移除掉
return (
['1', '1.15', '1.5', '2', '2.5', '3'].indexOf(lineHeight) > -1
);
},
},
};

View File

@ -1,315 +0,0 @@
<template>
<am-loading :loading="loading">
<div class="editor-ot-users">
<space class="editor-ot-users-content" size="small">
<span v-if="!isMobile" style="color: '#888888'">
当前在线<strong>{{members.length}}</strong>
</span>
<avatar
v-for="member in members"
:key="member['id']"
size="30"
:style="{backgroundColor:member['color']}"
>
{{member['name']}}
</avatar>
</space>
</div>
<am-toolbar v-if="engine" :engine="engine" :items="items" />
<div :class="['editor-wrapper',{'editor-mobile': isMobile}]">
<div class="editor-container">
<div class="editor-content">
<div ref="container"></div>
</div>
</div>
</div>
</am-loading>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { Avatar, message, Modal, Space } from 'ant-design-vue'
import Engine, { $, EngineInterface, isMobile } from "@aomao/engine"
import AmToolbar from '@aomao/toolbar-vue'
import AmLoading from './loading.vue'
import { cards, plugins, pluginConfig} from './config'
import OTClient from './ot-client'
import 'ant-design-vue/es/style'
export default defineComponent({
name:"engine-demo",
components:{
Avatar,
Space,
AmLoading,
AmToolbar
},
data(){
// toolbar
return {
items:isMobile ? [
['undo', 'redo'],
{
icon:"text",
items:[
'bold',
'italic',
'strikethrough',
'underline',
'moremark',
]
},
[
{
type: "button",
name: 'image-uploader',
icon: "image"
},
"link",
"tasklist",
"heading"
],
{
icon: "more",
items: [
{
type: "button",
name: 'video-uploader',
icon: "video"
},
{
type: "button",
name: 'file-uploader',
icon: "attachment"
},
{
type: "button",
name: 'table',
icon: "table"
},
{
type: "button",
name: 'math',
icon: "math"
},
{
type: "button",
name: 'codeblock',
icon: "codeblock"
},
{
type: "button",
name: "orderedlist",
icon: "orderedlist"
},
{
type: "button",
name: "unorderedlist",
icon: "unorderedlist"
},
{
type: "button",
name: "hr",
icon: "hr"
},
]
}
]:[['collapse'],
['undo', 'redo', 'paintformat', 'removeformat'],
['heading', 'fontfamily', 'fontsize'],
[
'bold',
'italic',
'strikethrough',
'underline',
'moremark',
],
['fontcolor', 'backcolor'],
['alignment'],
['unorderedlist', 'orderedlist', 'tasklist', 'indent', 'line-height'],
['link', 'quote', 'hr']]
}
},
setup(){
//
const container = ref<HTMLElement | null>(null)
//
const engine = ref<EngineInterface | null>(null)
//
const members = ref([])
//
const loading = ref(true)
onMounted(() => {
//
if(container.value){
//
const engineInstance = new Engine(container.value,{
//
plugins,
//
cards,
//
config: pluginConfig,
});
// UI使 console.log
engineInstance.messageSuccess = (msg: string) => {
message.success(msg);
};
// UI使 console.error
engineInstance.messageError = (error: string) => {
message.error(error);
};
// UI
engineInstance.messageConfirm = (msg: string) => {
return new Promise<boolean>((resolve, reject) => {
Modal.confirm({
content: msg,
onOk: () => resolve(true),
onCancel: () => reject(),
});
});
};
//
engineInstance.on('card:maximize', () => {
$('.editor-toolbar').css('z-index', '9999').css('top', '55px');
});
engineInstance.on('card:minimize', () => {
$('.editor-toolbar').css('z-index', '').css('top', '');
});
// api
const value = '<strong>Hello</strong>,<span style="color:red">am-editor</span>'
// 使 mongodb ot-server/client yarn start ot-server
let isOt = false
if (isOt) {
//
const ot = new OTClient(engineInstance);
// Token ot-server
const memberData = localStorage.getItem('member');
const currentMember = !!memberData ? JSON.parse(memberData) : null;
// docId使 value
ot.connect(`ws://127.0.0.1:8080${currentMember ? '?uid=' + currentMember.id : ''}`, 'demo', value);
ot.on('ready', member => {
//
if (member) localStorage.setItem('member', JSON.stringify(member));
loading.value = false
});
//退
ot.on('membersChange', currentMembers => {
members.value = currentMembers;
});
} else {
//
engineInstance.setValue(value, () => {
loading.value = false
})
}
//
engineInstance.on('change', () => {
console.log('value', engineInstance.getValue());
console.log('html:', engineInstance.getHtml());
});
engine.value = engineInstance
}
})
onUnmounted(() => {
if(engine.value) engine.value.destroy()
})
return {
loading,
isMobile,
container,
engine,
members
}
}
})
</script>
<style>
#app {
padding:0
}
#nav {
position: relative
}
.editor-ot-users {
font-size: 12px;
background: #ffffff;
padding: 0px 0 8px 266px;
z-index: 999;
width: 100%;
}
.editor-ot-users-content {
display: flex;
flex-wrap: wrap;
}
.editor-ot-users .ant-avatar {
margin: 0 2px;
}
.editor-toolbar {
position: fixed;
width: 100%;
background: #ffffff;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.02);
z-index: 1000;
}
.editor-wrapper {
position: relative;
width: 100%;
min-width: 1440px;
}
.editor-wrapper.editor-mobile {
min-width: auto;
padding: 0 12px;
}
.editor-container {
background: #fafafa;
background-color: #fafafa;
padding: 62px 0 64px;
height: calc(100vh - 68px);
width: 100%;
margin: 0 auto;
overflow: auto;
position: relative;
}
.editor-mobile .editor-container {
padding: 0;
height: auto;
overflow: hidden;
}
.editor-content {
position: relative;
width: 812px;
margin: 0 auto;
background: #fff;
border: 1px solid #f0f0f0;
overflow: hidden;
min-height: 800px;
}
.editor-mobile .editor-content {
width: auto;
min-height:calc(100vh - 68px);
border: 0 none;
}
.editor-content .am-engine {
padding: 40px 60px 60px;
}
.editor-mobile .editor-content .am-engine {
padding:18px 0 0 0;
}
</style>

View File

@ -1,35 +0,0 @@
<template>
<a-spin class="loading" :tip="text" :spinning="loading">
<slot></slot>
</a-spin>
</template>
<script lang="ts">
import { defineComponent } from "vue"
import ASpin from 'ant-design-vue/es/spin'
import 'ant-design-vue/es/spin/style';
export default defineComponent({
name:"am-loading",
components:{
ASpin
},
props:{
text: String,
loading: Boolean
},
})
</script>
<style css>
.loading {
padding: 20px;
display: block;
}
.ant-spin-nested-loading {
position: inherit;
}
.ant-spin-nested-loading .ant-spin-container {
position: inherit;
}
</style>

View File

@ -1,24 +0,0 @@
<template>
<div class="mention-container">
<p>This is name: {{name}}</p>
<p>配置 mention 插件的 onMouseEnter 方法</p>
<p>此处使用 createApp().mount 自定义渲染</p>
<p>Use createApp().mount to customize rendering here</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue"
export default defineComponent({
name:"am-mention",
props:{
name: String
},
})
</script>
<style css>
.mention-container {
padding: 5px;
}
</style>

View File

@ -1,406 +0,0 @@
import { EventEmitter } from 'events';
import { EngineInterface } from '@aomao/engine';
import ReconnectingWebSocket, { ErrorEvent } from 'reconnecting-websocket';
import { Doc } from 'sharedb';
import sharedb from 'sharedb/lib/client';
import { Socket } from 'sharedb/lib/sharedb';
export type Member = {
avatar: string;
name: string;
uuid: string;
color?: string;
};
export const STATUS = {
init: 'init',
loaded: 'loaded',
active: 'active',
exit: 'exit',
error: 'error',
};
export const EVENT = {
inactive: 'inactive',
error: 'error',
membersChange: 'membersChange',
statusChange: 'statusChange',
message: 'message',
};
export type ERROR = {
code: string;
level: string;
message: string;
error?: ErrorEvent;
};
export const ERROR_CODE = {
INIT_FAILED: 'INIT_FAILED',
SAVE_FAILED: 'SAVE_FAILED',
PUBLISH_FAILED: 'PUBLISH_FAILED',
DISCONNECTED: 'DISCONNECTED',
STATUS_CODE: {
TIMEOUT: 4001,
FORCE_DISCONNECTED: 4002,
},
CONNECTION_ERROR: 'CONNECTION_ERROR',
COLLAB_DOC_ERROR: 'COLLAB_DOC_ERROR',
};
export const ERROR_LEVEL = {
FATAL: 'FATAL',
WARNING: 'WARNING',
NOTICE: 'NOTICE',
};
/**
*
*/
class OTClient extends EventEmitter {
// 编辑器引擎
protected engine: EngineInterface;
// ws 连接实例
protected socket?: WebSocket;
// 当前协同的所有用户
protected members: Array<Member> = [];
// 当前用户
protected current?: Member;
// 当前状态
protected status?: string;
// 协作的文档对象
protected doc?: Doc;
// 当前 ws 是否关闭
protected isClosed: boolean = true;
// 心跳检测对象
protected heartbeat?: {
timeout: NodeJS.Timeout;
datetime: Date;
};
constructor(engine: EngineInterface) {
super();
this.engine = engine;
}
/**
*
* @param {number} millisecond 30000
* @return {void}
*/
checkHeartbeat(millisecond: number = 30000): void {
if (!this.socket) return;
if (this.heartbeat?.timeout) clearTimeout(this.heartbeat.timeout);
const timeout = setTimeout(() => {
const now = new Date();
if (
!this.isClosed &&
(!this.heartbeat ||
now.getTime() - this.heartbeat.datetime.getTime() >=
millisecond)
) {
this.sendMessage('heartbeat', { time: now.getTime() });
this.heartbeat = {
timeout,
datetime: now,
};
} else if (this.heartbeat) {
this.heartbeat.timeout = timeout;
}
this.checkHeartbeat(millisecond);
}, 1000);
}
/**
*
* @param url
* @param docID ID
* @param defautlValue
* @param collectionName
*/
connect(
url: string,
docID: string,
defautlValue?: string,
collectionName: string = 'yanmao',
) {
if (this.socket) this.socket.close();
// 实例化一个可以自动重连的 ws
const socket = new ReconnectingWebSocket(
async () => {
const token = await new Promise<string>((resolve) => {
// 这里可以异步获取一个Token如果有的话
resolve('');
});
// 组合ws链接
const uri = new URL(url);
uri.searchParams.set('id', docID);
uri.searchParams.set('token', token);
return uri.toString();
},
[],
{
maxReconnectionDelay: 30000,
minReconnectionDelay: 10000,
reconnectionDelayGrowFactor: 10000,
maxRetries: 10,
},
);
// ws 已链接
socket.addEventListener('open', () => {
this.socket = socket as WebSocket;
// 加载编辑器内部的协同服务
this.load(socket, docID, collectionName, defautlValue);
// 标记关闭状态为false
this.isClosed = false;
// 监听协同服务端自定义消息
this.socket.addEventListener('message', (event) => {
const { data, action } = JSON.parse(event.data);
// 当前所有的协作用户
if ('members' === action) {
this.addMembers(data);
this.engine.ot.setMembers(data);
return;
}
// 有新的协作者加入了
if ('join' === action) {
this.addMembers([data]);
this.engine.ot.addMember(data);
return;
}
// 有协作者离开了
if ('leave' === action) {
this.engine.ot.removeMember(data);
this.removeMember(data);
return;
}
// 协作服务端准备好了,可以实例化编辑器内部的协同服务了
if ('ready' === action) {
// 当前协作者用户
this.current = data as Member;
this.engine.ot.setCurrentMember(data);
this.emit('ready', this.engine.ot.getCurrentMember());
this.emit(EVENT.membersChange, this.normalizeMembers());
this.transmit(STATUS.active);
}
// 广播信息,一个协作用户发送给全部协作者的广播
if ('broadcast' === action) {
const { uuid, body, type } = data;
// 如果接收者和发送者不是同一人就触发一个message事件外部可以监听这个事件并作出响应
if (uuid !== this.current?.uuid) {
this.emit(EVENT.message, {
type,
body,
});
}
}
});
// 开始检测心跳
this.checkHeartbeat();
});
// 监听ws关闭事件
socket.addEventListener('close', () => {
// 如果不是主动退出的关闭,就显示错误信息
if (this.status !== STATUS.exit) {
this.onError({
code: ERROR_CODE.DISCONNECTED,
level: ERROR_LEVEL.FATAL,
message:
'网络连接异常,无法继续编辑!正在为您重新连接中...',
});
}
});
// 监听ws错误消息
socket.addEventListener('error', (error) => {
this.onError({
code: ERROR_CODE.CONNECTION_ERROR,
level: ERROR_LEVEL.FATAL,
message: '协作服务异常,无法继续编辑!正在为您重新连接中...',
error,
});
});
}
/**
*
* @param docId ID
* @param collectionName
* @param defaultValue docId的文档
*/
load(
socket: ReconnectingWebSocket,
docId: string,
collectionName: string,
defaultValue?: string,
) {
// 实例化一个协同客户端的连接
const connection = new sharedb.Connection(socket as Socket);
// 获取文档对象
const doc = connection.get(collectionName, docId);
this.doc = doc;
// 订阅
doc.subscribe((error) => {
console.log('subscribe');
if (error) {
console.log('collab doc subscribe error', error);
} else {
try {
// 实例化编辑器内部协同服务
this.engine.ot.initRemote(doc as any, defaultValue);
// 聚焦到编辑器
this.engine.focus();
} catch (err) {
console.log('am-engine init failed:', err);
}
}
});
doc.on('create', () => {
console.log('collab doc create');
});
doc.on('load', () => {
console.log('collab doc loaded');
this.sendMessage('ready');
});
doc.on('op', (op, type) => {
console.log('op', op, type ? 'local' : 'server');
});
doc.on('del', (t, n) => {
console.log('collab doc deleted', t, n);
});
doc.on('error', (error) => {
console.error(error);
});
}
/**
* 广
* @param type
* @param body
*/
broadcast(type: string, body: any = {}) {
this.sendMessage('broadcast', { type, body });
}
/**
*
* @param action
* @param data
*/
sendMessage(action: string, data?: any) {
this.socket?.send(
JSON.stringify({
action,
data: {
...data,
doc_id: this.doc?.id,
uuid: this.current?.uuid,
},
}),
);
}
addMembers(memberList: Array<Member>) {
memberList.forEach((member) => {
if (!this.members.find((m) => member.uuid === m.uuid)) {
this.members.push(member);
}
});
setTimeout(() => {
this.emit(EVENT.membersChange, this.normalizeMembers());
}, 1000);
}
removeMember(member: Member) {
this.members = this.members.filter((user) => {
return user.uuid !== member.uuid;
});
this.emit(EVENT.membersChange, this.normalizeMembers());
}
normalizeMembers() {
const members = [];
const colorMap: any = {};
const users = this.engine.ot.getMembers();
users.forEach((user) => {
colorMap[user.uuid] = user.color;
});
const memberMap: any = {};
for (let i = this.members.length; i > 0; i--) {
const member = this.members[i - 1];
if (!memberMap[member.uuid]) {
const cloneMember = { ...member };
cloneMember.color = colorMap[member.uuid];
memberMap[member.uuid] = member;
members.push(cloneMember);
}
}
return members;
}
transmit(status: string) {
const prevStatus = this.status;
this.status = status;
this.emit(EVENT.statusChange, {
form: prevStatus,
to: status,
});
}
onError(error: ERROR) {
this.emit(EVENT.error, error);
this.status = STATUS.error;
}
isActive() {
return this.status === STATUS.active;
}
exit() {
if (this.status !== STATUS.exit) {
this.transmit(STATUS.exit);
this.disconnect();
}
}
disconnect() {
if (this.socket) {
try {
this.socket.close(
ERROR_CODE.STATUS_CODE.FORCE_DISCONNECTED,
'FORCE_DISCONNECTED',
);
if (this.heartbeat?.timeout) {
clearTimeout(this.heartbeat!.timeout);
}
} catch (e) {
console.log(e);
}
}
}
bindEvents() {
window.addEventListener('beforeunload', () => this.exit());
window.addEventListener('visibilitychange', () => {
if ('hidden' === document.visibilityState) {
this.emit(EVENT.inactive);
}
});
window.addEventListener('pagehide', () => this.exit());
}
unbindEvents() {
window.removeEventListener('beforeunload', () => this.exit());
window.removeEventListener('visibilitychange', () => {
if ('hidden' === document.visibilityState) {
this.emit(EVENT.inactive);
}
});
window.removeEventListener('pagehide', () => this.exit());
}
}
export default OTClient;

View File

@ -0,0 +1,3 @@
// these APIs are auto-imported from @vueuse/core
export const isDark = useDark()
export const toggleDark = useToggle(isDark)

View File

@ -0,0 +1 @@
export * from './dark'

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
const router = useRouter()
const { t } = useI18n()
</script>
<template>
<main class="px-4 py-10 text-center text-teal-700 dark:text-gray-200">
<div>
<p class="text-4xl">
<carbon-warning class="inline-block" />
</p>
</div>
<router-view />
<div>
<button class="btn m-3 text-sm mt-8" @click="router.back()">
{{ t('button.back') }}
</button>
</div>
</main>
</template>

View File

@ -0,0 +1,14 @@
## Layouts
Vue components in this dir are used as layouts.
By default, `default.vue` will be used unless an alternative is specified in the route
meta.
With [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) and
[`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts), you
can specify the layout in the page's SFCs like this:
```html
<route lang="yaml"> meta: layout: home </route>
```

View File

@ -0,0 +1,5 @@
<template>
<main class="text-gray-700 dark:text-gray-200">
<router-view />
</main>
</template>

View File

@ -0,0 +1,7 @@
<template>
<main class="px-4 py-10 text-center text-gray-700 dark:text-gray-200">
<router-view />
<Footer />
<div class="mt-5 mx-auto text-center opacity-25 text-sm">[Home Layout]</div>
</main>
</template>

View File

@ -1,5 +1,68 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
// windicss layers
import 'virtual:windi-base.css'
import 'virtual:windi-components.css'
// your custom styles here
import './styles/main.css'
// windicss utilities should be the last style import
import 'virtual:windi-utilities.css'
// windicss devtools support (dev only)
import 'virtual:windi-devtools'
createApp(App).use(router).mount('#app');
import App from './App.vue'
// register vue composition api globally
import { ViteSSG } from 'vite-ssg'
import generatedRoutes from 'virtual:generated-pages'
import microApp from '@micro-zoe/micro-app'
import { setupLayouts } from 'virtual:generated-layouts'
const routes = setupLayouts(generatedRoutes)
// @ts-ignore
microApp.start({
lifeCycles: {
created() {
console.log('created 全局监听')
},
beforemount() {
console.log('beforemount 全局监听')
},
mounted() {
console.log('mounted 全局监听')
},
unmount() {
console.log('unmount 全局监听')
},
error() {
console.log('error 全局监听')
},
},
plugins: {
modules: {
react: [
{
loader(code: string, url: string) {
if (
process.env.NODE_ENV === 'development' &&
code.indexOf('sockjs-node') > -1
) {
console.log('react17插件', url)
code = code.replace('window.location.port', '3002')
}
return code
},
},
],
},
},
})
// https://github.com/antfu/vite-ssg
export const createApp = ViteSSG(
App,
{ routes, base: import.meta.env.BASE_URL },
(ctx) => {
// install all modules under `modules/`
Object.values(import.meta.globEager('./modules/*.ts')).forEach((i) =>
i.install?.(ctx)
)
}
)

View File

@ -0,0 +1,12 @@
## Modules
A custom user module system. Place a `.ts` file with the following template, it will be
installed automatically.
```ts
import { UserModule } from '~/types'
export const install: UserModule = ({ app, router, isClient }) => {
// do something
}
```

View File

@ -0,0 +1,23 @@
import { createI18n } from 'vue-i18n'
import type { UserModule } from '~/types'
// Import i18n resources
// https://vitejs.dev/guide/features.html#glob-import
//
// Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
const messages = Object.fromEntries(
Object.entries(import.meta.globEager('../../locales/*.y(a)?ml')).map(([key, value]) => {
const yaml = key.endsWith('.yaml')
return [key.slice(14, yaml ? -5 : -4), value.default]
})
)
export const install: UserModule = ({ app }) => {
const i18n = createI18n({
legacy: false,
locale: 'en',
messages,
})
app.use(i18n)
}

View File

@ -0,0 +1,15 @@
import { App } from 'vue'
import type { UserModule } from '~/types'
// inject into Vue
export const injectToVue = (app: App, name: string, target: unknown) => {
app.config.globalProperties[`$${name}`] = target
}
export const install: UserModule = ({ app }) => {
const inject = (name: string, target: unknown) => {
injectToVue(app, name, target)
}
// demo
inject('name', 'Modern Vue Stack')
}

View File

@ -0,0 +1,13 @@
import NProgress from 'nprogress'
import type { UserModule } from '~/types'
export const install: UserModule = ({ isClient, router }) => {
if (isClient) {
router.beforeEach(() => {
NProgress.start()
})
router.afterEach(() => {
NProgress.done()
})
}
}

View File

@ -0,0 +1,14 @@
import { createPinia } from 'pinia'
import type { UserModule } from '~/types'
// Setup Pinia
// https://pinia.esm.dev/
export const install: UserModule = ({ isClient, initialState, app }) => {
const pinia = createPinia()
app.use(pinia)
// Refer to
// https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization
// for other serialization strategies.
if (isClient) pinia.state.value = initialState.pinia || {}
else initialState.pinia = pinia.state.value
}

View File

@ -0,0 +1,11 @@
import type { UserModule } from '~/types'
// https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
export const install: UserModule = ({ isClient, router }) => {
if (!isClient) return
router.isReady().then(async () => {
const { registerSW } = await import('virtual:pwa-register')
registerSW({ immediate: true })
})
}

View File

@ -0,0 +1,21 @@
## File-based Routing
Routes will be auto-generated for Vue files in this dir with the same file structure.
Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more
details.
### Path Aliasing
`~/` is aliased to `./src/` folder.
For example, instead of having
```ts
import { isDark } from '../../../../composables'
```
now, you can use
```ts
import { isDark } from '~/composables'
```

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
const { t } = useI18n()
</script>
<template>
<div>
{{ t('not-found') }}
</div>
</template>
<route lang="yaml">
meta:
layout: 404
</route>

View File

@ -0,0 +1,25 @@
---
title: About
---
<div class="text-center">
<!-- You can use Vue components inside markdown -->
<carbon-dicom-overlay class="text-4xl -mb-6 m-auto" />
<h3>About</h3>
</div>
[Vitesse](https://github.com/antfu/vitesse) is an opinionated
[Vite](https://github.com/vitejs/vite) starter template made by
[@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**,
**components auto importing**, **markdown support**, I18n, PWA and uses **WindiCSS** for
UI.
```js
// syntax highlighting example
function vitesse() {
const foo = 'bar'
console.log(foo)
}
```
Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details.

View File

@ -0,0 +1,49 @@
<script setup lang="ts">
import { useUserStore } from '~/stores/user'
const props = defineProps<{ name: string }>()
const router = useRouter()
const user = useUserStore()
const { t } = useI18n()
watchEffect(() => {
user.setNewName(props.name)
})
</script>
<template>
<div>
<p class="text-4xl">
<carbon-pedestrian class="inline-block" />
</p>
<p>
{{ t('intro.hi', { name: props.name }) }}
</p>
<p class="text-sm opacity-50">
<em>{{ t('intro.dynamic-route') }}</em>
</p>
<template v-if="user.otherNames.length">
<p class="text-sm mt-4">
<span class="opacity-75">{{ t('intro.aka') }}:</span>
<ul>
<li v-for="otherName in user.otherNames" :key="otherName">
<router-link :to="`/hi/${otherName}`" replace>
{{ otherName }}
</router-link>
</li>
</ul>
</p>
</template>
<div>
<button
class="btn m-3 text-sm mt-6"
@click="router.back()"
>
{{ t('button.back') }}
</button>
</div>
</div>
</template>

View File

@ -0,0 +1,7 @@
<script setup lang="ts" name="IndexPage">
import AmEditor from '~/components/AmEditor/'
</script>
<template>
<AmEditor />
</template>

View File

@ -0,0 +1,11 @@
<template>
<div class="red">
<micro-app name="red" url="http://localhost:3002/" :data="data" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const data = ref({ from: '来自基座的初始化数据' })
</script>

View File

@ -1,26 +0,0 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '../views/Home.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ '../views/About.vue'),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;

View File

@ -1,6 +0,0 @@
/* eslint-disable */
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

16
examples/vue/src/shims.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
declare interface Window {
// extend the window
}
// with vite-plugin-md, markdowns can be treat as Vue components
declare module '*.md' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -0,0 +1,34 @@
import { acceptHMRUpdate, defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
/**
* Current named of the user.
*/
const savedName = ref('')
const previousNames = ref(new Set<string>())
const usedNames = computed(() => Array.from(previousNames.value))
const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value))
/**
* Changes the current name of the user and saves the one that was used
* before.
*
* @param name - new name to set
*/
function setNewName(name: string) {
if (savedName.value)
previousNames.value.add(savedName.value)
savedName.value = name
}
return {
setNewName,
otherNames,
savedName,
}
})
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))

View File

@ -0,0 +1,43 @@
@import './markdown.css';
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
html.dark {
background: #121212;
}
#nprogress {
pointer-events: none;
}
#nprogress .bar {
@apply bg-teal-600 opacity-75;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
.btn {
@apply px-4 py-1 rounded inline-block
bg-teal-600 text-white cursor-pointer
hover:bg-teal-700
disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50;
}
.icon-btn {
@apply inline-block cursor-pointer select-none
opacity-75 transition duration-200 ease-in-out
hover:opacity-100 hover:text-teal-600;
font-size: 0.9em;
}

View File

@ -0,0 +1,58 @@
/* https://github.com/antfu/prism-theme-vars */
@import 'prism-theme-vars/base.css';
.prose {
--prism-font-family: 'Input Mono', monospace;
}
.prose img {
width: 100%;
}
html:not(.dark) .prose {
--prism-foreground: #393a34;
--prism-background: #fbfbfb;
--prism-comment: #a0ada0;
--prism-string: #b56959;
--prism-literal: #2f8a89;
--prism-number: #296aa3;
--prism-keyword: #1c6b48;
--prism-function: #6c7834;
--prism-boolean: #1c6b48;
--prism-constant: #a65e2b;
--prism-deleted: #a14f55;
--prism-class: #2993a3;
--prism-builtin: #ab5959;
--prism-property: #b58451;
--prism-namespace: #b05a78;
--prism-punctuation: #8e8f8b;
--prism-decorator: #bd8f8f;
--prism-regex: #ab5e3f;
--prism-json-property: #698c96;
}
html.dark .prose {
--prism-foreground: #d4cfbf;
--prism-background: #151515;
--prism-comment: #758575;
--prism-string: #d48372;
--prism-literal: #429988;
--prism-keyword: #4d9375;
--prism-boolean: #1c6b48;
--prism-number: #6394bf;
--prism-variable: #c2b36e;
--prism-function: #a1b567;
--prism-deleted: #a14f55;
--prism-class: #54b1bf;
--prism-builtin: #e0a569;
--prism-property: #dd8e6e;
--prism-namespace: #db889a;
--prism-punctuation: #858585;
--prism-decorator: #bd8f8f;
--prism-regex: #ab5e3f;
--prism-json-property: #6b8b9e;
--prism-line-number: #888888;
--prism-line-number-gutter: #eeeeee;
--prism-line-highlight-background: #444444;
--prism-selection-background: #444444;
}

View File

@ -0,0 +1,3 @@
import type { ViteSSGContext } from 'vite-ssg'
export type UserModule = (ctx: ViteSSGContext) => void

View File

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -1,19 +0,0 @@
<template>
<div class="home">
<engine-demo />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Select } from 'ant-design-vue'
import EngineDemo from '@/components/demo.vue';
export default defineComponent({
components: {
"a-select":Select,
"a-select-option":Select.Option,
EngineDemo
}
})
</script>

View File

@ -0,0 +1,3 @@
// Vitest Snapshot v1
exports[`Counter.vue > should render 1`] = `"<div>10 <button class=\\"inc\\"> + </button><button class=\\"dec\\"> - </button></div>"`;

View File

@ -0,0 +1,5 @@
describe('tests', () => {
it('should works', () => {
expect(1 + 1).toEqual(2)
})
})

Some files were not shown because too many files have changed in this diff Show More