Browse Source

Initial commit: node_kefu_uniapp project

master
[yxf] 6 days ago
commit
f26cb78822
  1. 16
      .gitignore
  2. 8
      .hintrc
  3. 8
      .prettierrc
  4. 15
      .vscode/config.code-snippets
  5. 4
      .vscode/settings.json
  6. 119
      .vscode/template.code-snippets
  7. 31
      App.vue
  8. 33
      LICENSE
  9. 112
      README.md
  10. 3
      androidPrivacy.json
  11. 824
      build/cool/eps.d.ts
  12. 1
      build/cool/eps.json
  13. 61
      components/agree-btn.vue
  14. 231
      components/sms-btn.vue
  15. 17
      config/dev.ts
  16. 36
      config/index.ts
  17. 17
      config/prod.ts
  18. 18
      config/proxy.ts
  19. 91
      cool/bootstrap/eps.ts
  20. 15
      cool/bootstrap/index.ts
  21. 38
      cool/bootstrap/modules.ts
  22. 28
      cool/hooks/app.ts
  23. 11
      cool/hooks/comm.ts
  24. 23
      cool/hooks/hmr.ts
  25. 21
      cool/hooks/index.ts
  26. 180
      cool/hooks/pager.ts
  27. 289
      cool/hooks/wx.ts
  28. 9
      cool/index.ts
  29. 21
      cool/module/index.ts
  30. 325
      cool/router/index.ts
  31. 89
      cool/service/base.ts
  32. 9
      cool/service/index.ts
  33. 133
      cool/service/request.ts
  34. 22
      cool/service/sign.ts
  35. 67
      cool/store/dict.ts
  36. 9
      cool/store/index.ts
  37. 94
      cool/store/user.ts
  38. 45
      cool/types/index.d.ts
  39. 52
      cool/upload/comm.ts
  40. 141
      cool/upload/index.ts
  41. 603
      cool/utils/canvas.ts
  42. 168
      cool/utils/comm.ts
  43. 4
      cool/utils/index.ts
  44. 75
      cool/utils/storage.ts
  45. 72
      cool/utils/ui.ts
  46. BIN
      doc/logo.png
  47. 20
      hooks/index.ts
  48. 24
      index.html
  49. 132
      locale/en.json
  50. 132
      locale/es.json
  51. 1
      locale/fr.json
  52. 53
      locale/index.ts
  53. 132
      locale/zh-Hans.json
  54. 132
      locale/zh-Hant.json
  55. 16
      main.ts
  56. 184
      manifest.json
  57. 2873
      package-lock.json
  58. 39
      package.json
  59. 417
      pages.json
  60. 140
      pages/demo/basic/button.vue
  61. 210
      pages/demo/basic/icon.vue
  62. 83
      pages/demo/basic/image.vue
  63. 52
      pages/demo/basic/loading.vue
  64. 36
      pages/demo/basic/tag.vue
  65. 76
      pages/demo/basic/text.vue
  66. 62
      pages/demo/basic/toast.vue
  67. 92
      pages/demo/extend/action-sheet.vue
  68. 28
      pages/demo/extend/captcha.vue
  69. 103
      pages/demo/extend/confirm.vue
  70. 44
      pages/demo/extend/dialog.vue
  71. 112
      pages/demo/extend/filter-bar.vue
  72. 69
      pages/demo/extend/page.vue
  73. 74
      pages/demo/extend/service.vue
  74. 28
      pages/demo/extend/slider-verify.vue
  75. 150
      pages/demo/extend/tree.vue
  76. 111
      pages/demo/form/checkbox.vue
  77. 186
      pages/demo/form/form.vue
  78. 25
      pages/demo/form/input-number.vue
  79. 61
      pages/demo/form/input.vue
  80. 95
      pages/demo/form/radio.vue
  81. 19
      pages/demo/form/rate.vue
  82. 50
      pages/demo/form/select-city.vue
  83. 65
      pages/demo/form/select-date.vue
  84. 86
      pages/demo/form/select-popup.vue
  85. 92
      pages/demo/form/select.vue
  86. 21
      pages/demo/form/switch.vue
  87. 11
      pages/demo/form/textarea.vue
  88. 23
      pages/demo/form/upload.vue
  89. BIN
      pages/demo/static/avatar1.png
  90. BIN
      pages/demo/static/avatar2.png
  91. BIN
      pages/demo/static/avatar3.png
  92. BIN
      pages/demo/static/avatar4.png
  93. BIN
      pages/demo/static/bg1.png
  94. BIN
      pages/demo/static/bg2.png
  95. BIN
      pages/demo/static/bg3.png
  96. BIN
      pages/demo/static/bg4.png
  97. 58
      pages/demo/view/avatar.vue
  98. 61
      pages/demo/view/badge.vue
  99. 38
      pages/demo/view/banner.vue
  100. 15
      pages/demo/view/card.vue

16
.gitignore

@ -0,0 +1,16 @@
.DS_Store
node_modules/
unpackage/
/dist/
# Log files
npm-debug.log*
# Editor directories and files
.project
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*

8
.hintrc

@ -0,0 +1,8 @@
{
"extends": [
"development"
],
"hints": {
"typescript-config/is-valid": "off"
}
}

8
.prettierrc

@ -0,0 +1,8 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"jsxBracketSameLine": true,
"singleQuote": false,
"printWidth": 100
}

15
.vscode/config.code-snippets

@ -0,0 +1,15 @@
{
"module-config": {
"prefix": "module-config",
"scope": "typescript",
"body": [
"import type { ModuleConfig } from \"/@/cool\";",
"",
"export default (): ModuleConfig => {",
" return {};",
"};",
""
],
"description": "module config snippets"
}
}

4
.vscode/settings.json

@ -0,0 +1,4 @@
{
"editor.cursorSmoothCaretAnimation": "on",
"editor.formatOnSave": true,
}

119
.vscode/template.code-snippets

@ -0,0 +1,119 @@
{
"page": {
"prefix": "page",
"scope": "vue",
"body": [
"<template>",
" <cl-page>",
" <view class=\"page\">",
" $1",
" </view>",
" </cl-page>",
"</template>",
"",
"<script lang=\"ts\" setup>",
"import { useCool } from \"/@/cool\";",
"",
"const { service, router } = useCool();",
"</script>",
"",
"<style lang=\"scss\" scoped>",
".page {",
" ",
"}",
"</style>",
"",
],
"description": "page snippets",
},
"component": {
"prefix": "component",
"scope": "vue",
"body": [
"<template>",
" <view class=\"$1\">",
" ",
" </view>",
"</template>",
"",
"<script lang=\"ts\" setup>",
"import { useCool } from \"/@/cool\";",
"",
"const { service, router } = useCool();",
"</script>",
"",
"<style lang=\"scss\" scoped>",
".component$2 {",
" ",
"}",
"</style>",
"",
],
"description": "component snippets",
},
"popup": {
"prefix": "popup",
"scope": "vue",
"body": [
"<template>",
" <cl-popup v-model=\"visible\" direction=\"bottom\">",
" <view class=\"component$1\"> </view>",
" </cl-popup>",
"</template>",
"",
"<script lang=\"ts\" setup>",
"import { ref } from \"vue\";",
"import { useCool } from \"/@/cool\";",
"",
"const { service, router } = useCool();",
"",
"// 是否可见",
"const visible = ref(false);",
"",
"// 打开",
"function open() {",
" visible.value = true;",
"}",
"",
"// 关闭",
"function close() {",
" visible.value = false;",
"}",
"",
"defineExpose({",
" open,",
" close,",
"});",
"</script>",
"",
"<style lang=\"scss\" scoped>",
".component {",
"}",
"</style>",
"",
],
"description": "popup snippets",
},
"pager": {
"prefix": "pager",
"scope": "typescript",
"body": [
"const { service } = useCool();",
"const { onRefresh, list } = usePager();",
"",
"function refresh(params?: any) {",
" const { data, next } = onRefresh(params);",
" next(service.$1.page(data));",
"}",
"",
"onReady(() => {",
" refresh();",
"});",
"",
"defineExpose({",
" refresh,",
"});",
],
"description": "pager snippets",
},
}

31
App.vue

@ -0,0 +1,31 @@
<script setup lang="ts">
import { useStore } from "/@/cool";
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
onLaunch(() => {
console.log("App Launch");
const { dict, user } = useStore();
//
// dict.refresh();
if (user.token) {
//
// user.get();
}
});
onShow(() => {
console.log("App Show");
});
onHide(() => {
console.log("App Hide");
});
</script>
<style lang="scss">
@import "/$/cool-ui/index.scss";
@import "/@/static/css/index.scss";
</style>

33
LICENSE

@ -0,0 +1,33 @@
MIT License
Copyright (c) [2025] [厦门闪酷科技开发有限公司]
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.
---
MIT 许可证
版权所有 (c) [2025] [厦门闪酷科技开发有限公司]
特此免费授予获得本软件及相关文档文件(“软件”)副本的任何人无限制地处理本软件的权限,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件的副本,并允许软件提供给其的人员这样做,但须符合以下条件:
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
本软件按“原样”提供,不提供任何明示或暗示的担保,包括但不限于对适销性、特定用途适用性和非侵权的担保。在任何情况下,作者或版权持有人均不对因软件或软件使用或其他交易而产生的任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权诉讼或其他诉讼中。

112
README.md

@ -0,0 +1,112 @@
# COOL-UNI 项目脚手架
## 简介
[演示、文档地址](https://uni-docs.cool-js.com/)
## 更快
#### 启动快
基于 `vite`,快速的冷启动,不需要等待打包,即时的热模块更新,真正的按需编译。
#### 开发快
新增 `eps` 模式,自动扫描接口,代码智能提示。
<img src="https://uni-docs.cool-js.com/images/service-tip.gif" height="300px" />
#### 对接快
有什么功能是前端一个人做不了??大不了全干了
👉👉 [管理前端(vue3)开发文档、强大的 CRUD 组件](https://cool-js.com/admin/vue/introduce.html#%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93)
👉👉 [服务端(node、midway)开发文档、一键生成代码](https://cool-js.com/admin/node/introduce.html#%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93)
👉👉 [演示地址](https://show.cool-admin.com) 😁
<img src="https://vue.cool-admin.com/show/admin.jpg" width="500px" />
## 更强
内置请求、路由、文件上传、组件通信、缓存等方法及 ui 库和 hooks
```html
<script lang="ts" setup>
import { useCool } from "/@/cool";
import { useUi } from "/$/cool-ui";
const { service, router, storage, upload } = useCool();
const ui = useUi();
// 请求
service.test.page().then((res) => {
consoe.log(res);
});
// 跳转
router.push({
path: "/pages/goods/info",
query: {
id: 1,
},
});
// 全局事件
ui.showLoading();
ui.showToast();
// 储存
storage.set("token", "a123huis");
// 文件上传
uni.chooseImage({
count: 1,
sourceType: ["album", "camera"],
success(res) {
upload(res.tempFiles[0]).then((url) => {
console.log(url);
});
},
});
</script>
```
## 更全
#### 细腻的代码
- `service` 无感刷新,直接调用后端接口
```ts
const { service } = useCool();
```
- 提供 `entity` 描述,写 `any` 和不写的都哭了
```ts
const list = ref<Eps.UserInfoEntity[]>([]);
```
#### 活跃的社区
- 拥有自己的知识库系统
- [官方有问必答](https://cool-js.com/help/list.html)
#### 丰富的插件
- [Ai 智能模块](https://cool-js.com/plugin/detail.html?id=58)
- [客服聊天模块](https://cool-js.com/plugin/detail.html?id=56)
- [企业机器人](https://cool-js.com/plugin/detail.html?id=41)、[飞书推送](https://cool-js.com/plugin/detail.html?id=30)
- [各厂商的支付模块](https://cool-js.com/plugin/detail.html?id=33)
- [云存储](https://cool-js.com/plugin/detail.html?id=36)
- [PDF 打印](https://cool-js.com/plugin/detail.html?id=44)
- [更多](https://cool-js.com/plugin/list.html)

3
androidPrivacy.json

@ -0,0 +1,3 @@
{
"prompt" : "template"
}

824
build/cool/eps.d.ts

@ -0,0 +1,824 @@
declare namespace Eps {
interface CsChatEntity {
/**
*
*/
[key: string]: any;
}
interface UserInfoEntity {
/**
*
*/
[key: string]: any;
}
interface CsMsgEntity {
/**
*
*/
[key: string]: any;
}
interface CsSessionEntity {
/**
*
*/
[key: string]: any;
}
interface DemoGoodsEntity {
/**
*
*/
[key: string]: any;
}
interface UserAddressEntity {
/**
*
*/
[key: string]: any;
}
type json = any;
interface BaseComm {
/**
* uploadMode
*/
uploadMode(data?: any): Promise<any>;
/**
* upload
*/
upload(data?: any): Promise<any>;
/**
* param
*/
param(data?: any): Promise<any>;
/**
* eps
*/
eps(data?: any): Promise<any>;
/**
*
*/
permission: { uploadMode: string; upload: string; param: string; eps: string };
/**
*
*/
_permission: { uploadMode: boolean; upload: boolean; param: boolean; eps: boolean };
request: Service["request"];
}
interface CsChat {
/**
* send
*/
send(data?: any): Promise<any>;
/**
* page
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number; [key: string]: any };
list: CsChatEntity[];
[key: string]: any;
}>;
/**
* info
*/
info(data?: any): Promise<CsChatEntity>;
/**
*
*/
permission: { send: string; page: string; info: string };
/**
*
*/
_permission: { send: boolean; page: boolean; info: boolean };
request: Service["request"];
}
interface CsLogin {
/**
* account
*/
account(data?: any): Promise<any>;
/**
*
*/
permission: { account: string };
/**
*
*/
_permission: { account: boolean };
request: Service["request"];
}
interface CsMsg {
/**
* unreadCount
*/
unreadCount(data?: any): Promise<any>;
/**
* read
*/
read(data?: any): Promise<any>;
/**
* page
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number; [key: string]: any };
list: CsMsgEntity[];
[key: string]: any;
}>;
/**
*
*/
permission: { unreadCount: string; read: string; page: string };
/**
*
*/
_permission: { unreadCount: boolean; read: boolean; page: boolean };
request: Service["request"];
}
interface CsSession {
/**
* detail
*/
detail(data?: any): Promise<any>;
/**
* create
*/
create(data?: any): Promise<any>;
/**
*
*/
permission: { detail: string; create: string };
/**
*
*/
_permission: { detail: boolean; create: boolean };
request: Service["request"];
}
interface OpenDemoCache {
/**
* set
*/
set(data?: any): Promise<any>;
/**
* get
*/
get(data?: any): Promise<any>;
/**
*
*/
permission: { set: string; get: string };
/**
*
*/
_permission: { set: boolean; get: boolean };
request: Service["request"];
}
interface OpenDemoEvent {
/**
* global
*/
global(data?: any): Promise<any>;
/**
* comm
*/
comm(data?: any): Promise<any>;
/**
*
*/
permission: { global: string; comm: string };
/**
*
*/
_permission: { global: boolean; comm: boolean };
request: Service["request"];
}
interface OpenDemoGoods {
/**
* entityPage
*/
entityPage(data?: any): Promise<any>;
/**
* sqlPage
*/
sqlPage(data?: any): Promise<any>;
/**
* delete
*/
delete(data?: any): Promise<any>;
/**
* update
*/
update(data?: any): Promise<any>;
/**
* info
*/
info(data?: any): Promise<DemoGoodsEntity>;
/**
* list
*/
list(data?: any): Promise<DemoGoodsEntity[]>;
/**
* page
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number; [key: string]: any };
list: DemoGoodsEntity[];
[key: string]: any;
}>;
/**
* add
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
entityPage: string;
sqlPage: string;
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
/**
*
*/
_permission: {
entityPage: boolean;
sqlPage: boolean;
delete: boolean;
update: boolean;
info: boolean;
list: boolean;
page: boolean;
add: boolean;
};
request: Service["request"];
}
interface OpenDemoI18n {
/**
* en
*/
en(data?: any): Promise<any>;
/**
* tw
*/
tw(data?: any): Promise<any>;
/**
*
*/
permission: { en: string; tw: string };
/**
*
*/
_permission: { en: boolean; tw: boolean };
request: Service["request"];
}
interface OpenDemoPlugin {
/**
* invoke
*/
invoke(data?: any): Promise<any>;
/**
*
*/
permission: { invoke: string };
/**
*
*/
_permission: { invoke: boolean };
request: Service["request"];
}
interface OpenDemoQueue {
/**
* addGetter
*/
addGetter(data?: any): Promise<any>;
/**
* getter
*/
getter(data?: any): Promise<any>;
/**
* add
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: { addGetter: string; getter: string; add: string };
/**
*
*/
_permission: { addGetter: boolean; getter: boolean; add: boolean };
request: Service["request"];
}
interface OpenDemoRpc {
/**
* transaction
*/
transaction(data?: any): Promise<any>;
/**
* event
*/
event(data?: any): Promise<any>;
/**
* call
*/
call(data?: any): Promise<any>;
/**
*
*/
permission: { transaction: string; event: string; call: string };
/**
*
*/
_permission: { transaction: boolean; event: boolean; call: boolean };
request: Service["request"];
}
interface OpenDemoSse {
/**
* call
*/
call(data?: any): Promise<any>;
/**
*
*/
permission: { call: string };
/**
*
*/
_permission: { call: boolean };
request: Service["request"];
}
interface OpenDemoTenant {
/**
*
*/
permission: {};
/**
*
*/
_permission: {};
request: Service["request"];
}
interface OpenDemoTransaction {
/**
* delete
*/
delete(data?: any): Promise<any>;
/**
* update
*/
update(data?: any): Promise<any>;
/**
* info
*/
info(data?: any): Promise<DemoGoodsEntity>;
/**
* list
*/
list(data?: any): Promise<DemoGoodsEntity[]>;
/**
* page
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number; [key: string]: any };
list: DemoGoodsEntity[];
[key: string]: any;
}>;
/**
* add
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
/**
*
*/
_permission: {
delete: boolean;
update: boolean;
info: boolean;
list: boolean;
page: boolean;
add: boolean;
};
request: Service["request"];
}
interface DictInfo {
/**
* types
*/
types(data?: any): Promise<any>;
/**
* data
*/
data(data?: any): Promise<any>;
/**
*
*/
permission: { types: string; data: string };
/**
*
*/
_permission: { types: boolean; data: boolean };
request: Service["request"];
}
interface Swagger {
/**
* json
*/
json(data?: any): Promise<any>;
/**
*
*/
permission: { json: string };
/**
*
*/
_permission: { json: boolean };
request: Service["request"];
}
interface UserAddress {
/**
* default
*/
default(data?: any): Promise<any>;
/**
* delete
*/
delete(data?: any): Promise<any>;
/**
* update
*/
update(data?: any): Promise<any>;
/**
* info
*/
info(data?: any): Promise<UserAddressEntity>;
/**
* list
*/
list(data?: any): Promise<UserAddressEntity[]>;
/**
* page
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number; [key: string]: any };
list: UserAddressEntity[];
[key: string]: any;
}>;
/**
* add
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
default: string;
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
/**
*
*/
_permission: {
default: boolean;
delete: boolean;
update: boolean;
info: boolean;
list: boolean;
page: boolean;
add: boolean;
};
request: Service["request"];
}
interface UserComm {
/**
* wxMpConfig
*/
wxMpConfig(data?: any): Promise<any>;
/**
*
*/
permission: { wxMpConfig: string };
/**
*
*/
_permission: { wxMpConfig: boolean };
request: Service["request"];
}
interface UserInfo {
/**
* updatePassword
*/
updatePassword(data?: any): Promise<any>;
/**
* updatePerson
*/
updatePerson(data?: any): Promise<any>;
/**
* bindPhone
*/
bindPhone(data?: any): Promise<any>;
/**
* miniPhone
*/
miniPhone(data?: any): Promise<any>;
/**
* person
*/
person(data?: any): Promise<any>;
/**
* logoff
*/
logoff(data?: any): Promise<any>;
/**
*
*/
permission: {
updatePassword: string;
updatePerson: string;
bindPhone: string;
miniPhone: string;
person: string;
logoff: string;
};
/**
*
*/
_permission: {
updatePassword: boolean;
updatePerson: boolean;
bindPhone: boolean;
miniPhone: boolean;
person: boolean;
logoff: boolean;
};
request: Service["request"];
}
interface UserLogin {
/**
* refreshToken
*/
refreshToken(data?: any): Promise<any>;
/**
* miniPhone
*/
miniPhone(data?: any): Promise<any>;
/**
* uniPhone
*/
uniPhone(data?: any): Promise<any>;
/**
* password
*/
password(data?: any): Promise<any>;
/**
* captcha
*/
captcha(data?: any): Promise<any>;
/**
* smsCode
*/
smsCode(data?: any): Promise<any>;
/**
* wxApp
*/
wxApp(data?: any): Promise<any>;
/**
* phone
*/
phone(data?: any): Promise<any>;
/**
* mini
*/
mini(data?: any): Promise<any>;
/**
* mp
*/
mp(data?: any): Promise<any>;
/**
*
*/
permission: {
refreshToken: string;
miniPhone: string;
uniPhone: string;
password: string;
captcha: string;
smsCode: string;
wxApp: string;
phone: string;
mini: string;
mp: string;
};
/**
*
*/
_permission: {
refreshToken: boolean;
miniPhone: boolean;
uniPhone: boolean;
password: boolean;
captcha: boolean;
smsCode: boolean;
wxApp: boolean;
phone: boolean;
mini: boolean;
mp: boolean;
};
request: Service["request"];
}
type Service = {
/**
*
*/
request(options?: {
url: string;
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
data?: any;
params?: any;
headers?: {
authorization?: string;
[key: string]: any;
};
timeout?: number;
proxy?: boolean;
[key: string]: any;
}): Promise<any>;
base: { comm: BaseComm };
cs: { chat: CsChat; login: CsLogin; msg: CsMsg; session: CsSession };
open: {
demo: {
cache: OpenDemoCache;
event: OpenDemoEvent;
goods: OpenDemoGoods;
i18n: OpenDemoI18n;
plugin: OpenDemoPlugin;
queue: OpenDemoQueue;
rpc: OpenDemoRpc;
sse: OpenDemoSse;
tenant: OpenDemoTenant;
transaction: OpenDemoTransaction;
};
};
dict: { info: DictInfo };
swagger: Swagger;
user: { address: UserAddress; comm: UserComm; info: UserInfo; login: UserLogin };
};
type DictKey = "brand" | "occupation";
}

1
build/cool/eps.json

File diff suppressed because one or more lines are too long

61
components/agree-btn.vue

@ -0,0 +1,61 @@
<template>
<cl-checkbox :size="34" v-model="agree" round>
<view class="agree-btn">
{{ $t("已阅读并同意") }}
<text @tap.stop="toDoc('用户协议', 'userAgreement')">{{ $t("用户协议") }}</text>
{{ $t("和") }}
<text @tap.stop="toDoc('隐私政策', 'privacyPolicy')">{{ $t("隐私政策") }}</text>
</view>
</cl-checkbox>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useCool } from "/@/cool";
import { useUi } from "/$/cool-ui";
import { useI18n } from "vue-i18n";
const { router } = useCool();
const ui = useUi();
const { t } = useI18n();
const agree = ref(false);
function toDoc(title: string, key: string) {
router.push({
path: "/pages/user/doc",
query: {
title,
key,
},
});
}
function check() {
if (!agree.value) {
ui.showToast(t("请先勾选同意后再进行登录"));
}
return agree.value;
}
defineExpose({
check,
});
</script>
<style lang="scss" scoped>
.agree-btn {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
color: #999999;
letter-spacing: 1rpx;
text {
color: $cl-color-primary;
padding: 0 10rpx;
}
}
</style>

231
components/sms-btn.vue

@ -0,0 +1,231 @@
<template>
<view class="sms-btn">
<slot :disabled="isDisabled" :countdown="countdown" :btnText="btnText">
<cl-button
:border="false"
background-color="transparent"
color="#FE6B03"
:height="height"
:font-size="fontSize"
fill
:size="size"
:disabled="isDisabled"
@tap="open"
>
{{ btnText }}
</cl-button>
</slot>
<cl-popup
v-model="captcha.visible"
:ref="setRefs('popup')"
:padding="40"
border-radius="24rpx"
>
<cl-loading-mask :loading="captcha.loading">
<view class="sms-popup">
<view class="head">
<cl-text bold :size="28" :value="$t('获取短信验证码')"></cl-text>
<cl-icon :size="32" name="close" @tap="close"></cl-icon>
</view>
<view class="row">
<cl-input
v-model="form.code"
:placeholder="$t('验证码')"
:maxlength="4"
:height="70"
:clearable="false"
:focus="refs.popup?.isFocus"
:border="false"
background-color="#f7f7f7"
@confirm="send"
/>
<image :src="captcha.img" mode="aspectFit" @tap="getCaptcha" />
</view>
<cl-button
type="primary"
fill
:disabled="!form.code"
:loading="captcha.sending"
:height="70"
@tap="send"
>
{{ $t("发送短信") }}
</cl-button>
</view>
</cl-loading-mask>
</cl-popup>
</view>
</template>
<script lang="ts" setup>
import { computed, type PropType, reactive, ref } from "vue";
import { useCool } from "../cool";
import { useUi } from "/$/cool-ui";
import { useI18n } from "vue-i18n";
const props = defineProps({
phone: String,
type: String,
height: Number,
fontSize: Number,
size: String as PropType<"large" | "default" | "small">,
border: {
type: Boolean,
default: true,
},
plain: Boolean,
});
const emit = defineEmits(["success"]);
const { service, refs, setRefs } = useCool();
const ui = useUi();
const { t } = useI18n();
//
const captcha = reactive({
visible: false,
loading: false,
sending: false,
img: "",
});
//
const countdown = ref(0);
//
const isDisabled = computed(() => countdown.value > 0 || !props.phone);
//
const btnText = computed(() =>
countdown.value > 0 ? t("{n}s后重新获取", { n: countdown.value }) : t("获取验证码")
);
//
const form = reactive({
code: "",
captchaId: "",
});
//
function startCountdown() {
countdown.value = 60;
function fn() {
countdown.value--;
if (countdown.value < 1) {
clearInterval(timer);
}
}
const timer = setInterval(fn, 1000);
fn();
}
//
async function send() {
if (form.code) {
captcha.sending = true;
await service.user.login
.smsCode({
phone: props.phone,
...form,
})
.then(() => {
ui.showToast(t("短信已发送,请查收"));
startCountdown();
close();
emit("success");
})
.catch((err) => {
ui.showToast(err.message);
getCaptcha();
});
captcha.sending = false;
} else {
ui.showToast(t("请填写验证码"));
}
}
//
async function getCaptcha() {
clear();
captcha.loading = true;
await service.user.login
.captcha({ color: "#2c3142", phone: props.phone })
.then((res) => {
form.captchaId = res.captchaId;
captcha.img = res.data;
})
.catch((err) => {
ui.showToast(err.message);
});
captcha.loading = false;
}
//
function open() {
if (props.phone) {
if (/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(props.phone)) {
captcha.visible = true;
getCaptcha();
} else {
ui.showToast(t("请填写正确的手机号格式"));
}
}
}
//
function close() {
captcha.visible = false;
clear();
}
//
function clear() {
form.code = "";
form.captchaId = "";
}
defineExpose({
open,
send,
getCaptcha,
startCountdown,
});
</script>
<style lang="scss" scoped>
.sms-popup {
width: 400rpx;
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
}
.row {
display: flex;
align-items: center;
margin-bottom: 30rpx;
image {
height: 70rpx;
width: 200rpx;
flex-shrink: 0;
}
}
}
</style>

17
config/dev.ts

@ -0,0 +1,17 @@
import { host, value } from "./proxy";
export default {
// 根地址
host,
// 请求地址
get baseUrl() {
// #ifdef H5
return `/${value}`;
// #endif
// #ifndef H5
return this.host + "";
// #endif
},
};

36
config/index.ts

@ -0,0 +1,36 @@
import dev from "./dev";
import prod from "./prod";
// 是否开发模式
export const isDev = import.meta.env.MODE === "development";
// 代理环境
const proxy = isDev ? dev : prod;
// 配置
export const config = {
// 应用信息
app: {
// 应用名称
name: "COOL-UNI",
// 应用描述
desc: "uniapp快速开发脚手架",
// 页面配置
pages: {
login: "/pages/user/login",
},
// 微信配置
wx: {
debug: false,
},
},
// 忽略
ignore: {
token: [],
},
...proxy,
};
export * from "./proxy";

17
config/prod.ts

@ -0,0 +1,17 @@
import { proxy } from "./proxy";
export default {
// 根地址
host: proxy["/prod/"].target,
// 请求地址
get baseUrl() {
// #ifdef H5
return "/api";
// #endif
// #ifndef H5
return this.host + "/api";
// #endif
},
};

18
config/proxy.ts

@ -0,0 +1,18 @@
const proxy = {
"/dev/": {
target: "http://ksadmin.pyxxkj.com:8001",
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev/, ""),
},
"/prod/": {
target: "http://ksadmin.pyxxkj.com:8001",
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/prod/, "/api"),
},
};
const value = "dev";
const host = proxy[`/${value}/`]?.target;
export { proxy, host, value };

91
cool/bootstrap/eps.ts

@ -0,0 +1,91 @@
import { merge } from "lodash-es";
import { BaseService, service } from "../service";
import { path2Obj } from "../utils";
import { isDev } from "/@/config";
import { eps } from "virtual:eps";
// 读取本地所有 service
const files = import.meta.glob("/service/**/*", {
eager: true,
});
// 数据集合
const services: any[] = [];
// 取值
for (const i in files) {
try {
// @ts-ignore
services.push(new files[i].default());
} catch (e) {
console.error(`[service] ${i} error: `, e);
}
}
export function createEps() {
// 设置 request 方法
function set(d: any) {
if (d.namespace) {
const a: any = new BaseService(d.namespace);
for (const i in d) {
const { path, method = "get" } = d[i];
if (path) {
a.request = a.request;
a[i] = function (data?: any) {
return this.request({
url: path,
method,
[method.toLocaleLowerCase() == "post" ? "data" : "params"]: data,
});
};
}
}
for (const i in a) {
d[i] = a[i];
}
} else {
for (const i in d) {
set(d[i]);
}
}
}
// 遍历每一个方法
set(eps.service);
// 合并 eps
merge(service, eps.service);
// 合并[local]
merge(
service,
path2Obj(
services.map((e) => {
return {
path: (e.namespace || "").replace("app/", ""),
value: e,
};
})
)
);
// 提示
if (isDev) {
console.log("[cool-eps] updated");
}
}
// 监听 vite 触发事件
if (import.meta.hot) {
import.meta.hot.on("eps-update", ({ service }) => {
if (service) {
eps.service = service;
}
createEps();
});
}

15
cool/bootstrap/index.ts

@ -0,0 +1,15 @@
import { createPinia } from "pinia";
import { createEps } from "./eps";
import { createModules } from "./modules";
import { type App } from "vue";
export async function bootstrap(app: App) {
// 状态共享存储
app.use(createPinia());
// 创建 EPS
createEps();
// 创建 uni_modules
createModules();
}

38
cool/bootstrap/modules.ts

@ -0,0 +1,38 @@
import { keys, orderBy } from "lodash-es";
import { module } from "../module";
export async function createModules() {
// 加载 uni_modules 插件
const files: any = import.meta.glob("/uni_modules/cool-*/config.ts", {
eager: true,
});
const modules = orderBy(
keys(files).map((k) => {
const [, , name] = k.split("/");
return {
name,
value: files[k]?.default,
};
}),
"order",
"desc",
);
for (let i in modules) {
const { name, value } = modules[i];
const data = value ? value() : undefined;
// 添加模块
module.add({
name,
...data,
});
// 触发加载事件
if (data) {
await data.onLoad?.(data.options);
}
}
}

28
cool/hooks/app.ts

@ -0,0 +1,28 @@
import { reactive, ref } from "vue";
import { storage } from "../utils";
import { config } from "/@/config";
import { defineStore } from "pinia";
// 主题
export const useTheme = defineStore("theme", () => {
const name = ref(storage.get("theme") || "default");
function set(value: string) {
name.value = value;
storage.set("theme", value);
}
return {
name,
set,
};
});
export function useApp() {
const info = reactive(config.app);
return {
info,
theme: useTheme(),
};
}

11
cool/hooks/comm.ts

@ -0,0 +1,11 @@
import { reactive } from "vue";
export function useRefs() {
const refs = reactive<{ [key: string]: any }>({});
function setRefs(name: string) {
return (el: any) => {
refs[name] = el;
};
}
return { refs, setRefs };
}

23
cool/hooks/hmr.ts

@ -0,0 +1,23 @@
// 解决热更新后失效问题;
const data = import.meta.hot?.data.getData?.() || {};
if (import.meta.hot) {
import.meta.hot.data.getData = () => {
return data;
};
}
export const hmr = {
data,
setData(key: string, value: any) {
data[key] = value;
},
getData(key: string, defaultValue?: any) {
if (defaultValue !== undefined && !data[key]) {
this.setData(key, defaultValue);
}
return data[key];
}
};

21
cool/hooks/index.ts

@ -0,0 +1,21 @@
import { router } from "../router";
import { service } from "../service";
import { upload } from "../upload";
import { storage } from "../utils";
import { useRefs } from "./comm";
export function useCool() {
return {
router,
service,
upload,
storage,
...useRefs(),
};
}
export * from "./app";
export * from "./comm";
export * from "./hmr";
export * from "./pager";
export * from "./wx";

180
cool/hooks/pager.ts

@ -0,0 +1,180 @@
import { computed, getCurrentInstance, onUnmounted, reactive } from "vue";
import { onPullDownRefresh, onReachBottom, onUnload } from "@dcloudio/uni-app";
import { useUi } from "/$/cool-ui";
interface Res {
list: any[];
pagination: {
total: number;
page: number;
size: number;
[key: string]: any;
};
[key: string]: any;
}
export function usePager<T = any>(data: T[] = []) {
const { proxy }: any = getCurrentInstance();
const ui = useUi();
// 分页信息
const pager = reactive({
params: {},
pagination: {
page: 1,
size: 20,
total: 0,
},
list: data,
loading: false,
finished: false,
});
// 事件
const events: any = {};
// 列表
const list = computed(() => pager.list);
// 刷新
async function refresh(params?: any) {
if (pager.loading) {
return false;
}
if (proxy.refresh) {
await proxy.refresh(params);
} else if (proxy.$.exposed.refresh) {
await proxy.$.exposed.refresh(params);
} else {
console.log("use defineExpose({ refresh })");
}
}
// 数据
function onData(cb: (list: T[]) => void) {
events.onData = cb;
}
// 刷新
function onRefresh(params: any = {}, options?: { clear?: boolean; loading?: boolean }) {
const { clear, loading = true } = options || {};
// 是否清空
if (clear) {
if (params.page == 1) {
pager.list = [];
pager.finished = false;
}
}
// 合并请求参数
Object.assign(pager.params, params);
const data = {
...pager.pagination,
...pager.params,
total: undefined,
};
// 是否显示加载动画
if (data.page == 1 && loading) {
ui.showLoading();
}
pager.loading = true;
// 完成
function done() {
ui.hideLoading();
pager.loading = false;
}
return {
data,
done,
next: (req: Promise<Res>) => {
return new Promise((resolve, reject) => {
req.then((res: Res) => {
// 设置列表数据
if (data.page == 1) {
pager.list = res.list;
} else {
pager.list.push(...res.list);
}
// 追加事件
if (events.onData) {
events.onData(res.list);
}
// 是否加载完成
pager.finished = pager.list.length === res.pagination.total;
// 分页信息
pager.pagination = res.pagination;
done();
resolve(res);
}).catch((err) => {
done();
ui.showToast(err.message);
reject(err);
});
});
},
};
}
// 关闭
function close() {
isReg = false;
ui.hideLoading();
}
// 下拉刷新
async function onDown(end?: () => void) {
await refresh({ page: 1 });
end?.();
}
// 上拉加载
function onUp() {
if (!pager.finished) {
refresh({ page: pager.pagination.page + 1 });
}
}
// 是否注册,避免在组件中重复注入事件问题
let isReg = true;
// 上拉加载
onReachBottom(() => {
if (isReg) {
onUp();
}
});
// 下拉刷新
onPullDownRefresh(() => {
if (isReg) {
onDown(uni.stopPullDownRefresh);
}
});
// 组件销毁
onUnmounted(close);
// 离开页面
onUnload(close);
return {
pager,
list,
onData,
onRefresh,
onPullDownRefresh,
onReachBottom,
onDown,
onUp,
};
}

289
cool/hooks/wx.ts

@ -0,0 +1,289 @@
import { ref } from "vue";
import { onReady, onShow } from "@dcloudio/uni-app";
import { config } from "/@/config";
import { getUrlParam, storage } from "../utils";
import { service } from "../service";
import { useI18n } from "vue-i18n";
// #ifdef H5
import wx from "weixin-js-sdk";
// #endif
export function useWx() {
const { platform } = uni.getSystemInfoSync();
const { t } = useI18n();
// 授权码
const code = ref("");
// 获取授权码
async function getCode() {
return new Promise((resolve) => {
// #ifdef MP-WEIXIN
uni.login({
provider: "weixin",
success: (res) => {
code.value = res.code;
resolve(res.code);
},
});
// #endif
});
}
// 是否微信浏览器
function isWxBrowser() {
// #ifdef H5
const ua: any = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == "micromessenger") {
return true;
} else {
return false;
}
// #endif
// #ifndef H5
return false;
// #endif
}
// 是否安装了微信
function hasApp() {
// #ifdef APP
return plus.runtime.isApplicationExist({ pname: "com.tencent.mm", action: "weixin://" });
// #endif
// #ifndef APP
return true;
// #endif
}
// 下载微信
function downloadApp() {
// #ifdef APP
if (platform == "android") {
const Uri: any = plus.android.importClass("android.net.Uri");
const uri: any = Uri.parse("market://details?id=" + "com.tencent.mm");
const Intent: any = plus.android.importClass("android.content.Intent");
const intent: any = new Intent(Intent.ACTION_VIEW, uri);
const main: any = plus.android.runtimeMainActivity();
main.startActivity(intent);
} else {
plus.runtime.openURL(
"itms-apps://" + "itunes.apple.com/cn/app/wechat/id414478124?mt=8"
);
}
// #endif
}
// 微信公众号配置
const mpConfig = {
appId: "",
};
// 获取微信公众号配置
function getMpConfig() {
// #ifdef H5
if (isWxBrowser()) {
service.user.comm
.wxMpConfig({
url: `${location.origin}${location.pathname}`,
})
.then((res) => {
wx.config({
debug: config.app.wx.debug,
jsApiList: ["chooseWXPay"],
...res,
});
Object.assign(mpConfig, res);
});
}
// #endif
}
// 微信公众号授权
function mpAuth() {
const redirect_uri = encodeURIComponent(
`${location.origin}${location.pathname}#/pages/user/login`
);
const response_type = "code";
const scope = "snsapi_userinfo";
const state = "STATE";
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${mpConfig.appId}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}#wechat_redirect`;
location.href = url;
}
// 微信公众号登录
async function mpLogin() {
return new Promise((resolve) => {
const code = getUrlParam("code");
const mpCode = storage.get("mpCode");
let url = window.location.href;
url = url.replace(/(\?[^#]*)#/, "#");
window.history.replaceState({}, "", url);
if (code != mpCode) {
storage.set("mpCode", code);
resolve(code);
} else {
resolve(null);
}
});
}
// 微信公众号支付
async function mpPay(params: wx.IchooseWXPay & { timeStamp: number }): Promise<void> {
return new Promise((resolve, reject) => {
if (!isWxBrowser()) {
return reject({
message: t("请在微信浏览器中打开"),
});
}
wx.chooseWXPay({
...params,
timestamp: params.timeStamp,
success() {
resolve();
},
complete(e: { errMsg: string }) {
switch (e.errMsg) {
case "chooseWXPay:cancel":
reject({ message: t("已取消支付") });
break;
default:
reject({ message: t("支付失败") });
}
},
});
});
}
// 微信app登录
function appLogin(): Promise<string> {
let all: any;
let Service: any;
return new Promise((resolve, reject) => {
plus.oauth.getServices((Services: any) => {
all = Services;
Object.keys(all).some((key) => {
if (all[key].id == "weixin") {
Service = all[key];
}
});
Service.authorize(resolve, reject);
}, reject);
});
}
// 微信app支付
function appPay(orderInfo: {
appid: string;
noncestr: string;
package: string;
partnerid: string;
prepayid: string;
timestamp: string;
sign: string;
[key: string]: any;
}): Promise<void> {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: "wxpay",
orderInfo,
success() {
resolve();
},
fail() {
reject({ message: t("已取消支付") });
},
});
});
}
// 微信小程序登录
async function miniLogin(): Promise<{ code: string; [key: string]: any }> {
return new Promise((resolve, reject) => {
// 兼容 Mac
const k = platform === "mac" ? "getUserInfo" : "getUserProfile";
uni[k]({
lang: "zh_CN",
desc: t("授权信息仅用于用户登录"),
success({ iv, encryptedData, signature, rawData }) {
function next() {
resolve({
iv,
encryptedData,
signature,
rawData,
code: code.value,
});
}
// 检查登录状态是否过期
uni.checkSession({
success() {
next();
},
fail() {
getCode().then(next);
},
});
},
fail(err) {
console.error(err);
getCode();
reject({
message: t("登录授权失败"),
});
},
});
});
}
// 微信小程序支付
function miniPay(params: any): Promise<void> {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: "wxpay",
...params,
success() {
resolve();
},
fail() {
reject({ message: t("已取消支付") });
},
});
});
}
onShow(() => {
getCode();
});
onReady(() => {
getMpConfig();
});
return {
code,
getCode,
isWxBrowser,
hasApp,
downloadApp,
mpConfig,
mpAuth,
mpLogin,
mpPay,
miniLogin,
miniPay,
appLogin,
appPay,
};
}

9
cool/index.ts

@ -0,0 +1,9 @@
export * from "./hooks";
export * from "./router";
export * from "./store";
export * from "./upload";
export * from "./service";
export * from "./module";
export * from "../config";
export type * from "./types";
export { storage } from "./utils";

21
cool/module/index.ts

@ -0,0 +1,21 @@
import type { Module } from "../types";
import { hmr } from "../hooks";
// 模块列表
const list: Module[] = hmr.getData("modules", []);
// 模块对象
const module = {
list,
get(name: string): Module {
return this.list.find((e) => e.name == name)!;
},
config(name: string) {
return this.get(name).options || {};
},
add(data: Module) {
this.list.push(data);
},
};
export { module };

325
cool/router/index.ts

@ -0,0 +1,325 @@
import { last } from "lodash-es";
import { ctx } from "virtual:ctx";
import { storage } from "../utils";
import { config } from "../../config";
type PushOptions =
| string
| {
path: string;
mode?: "navigateTo" | "redirectTo" | "reLaunch" | "switchTab" | "preloadPage";
events?: {
[key: string]: (data: any) => void;
};
query?: {
[key: string]: any;
};
params?: {
[key: string]: any;
};
isGuard?: boolean;
[key: string]: any;
};
type Tabs = {
text?: string;
pagePath: string;
iconPath?: string;
selectedIconPath?: string;
[key: string]: any;
}[];
// 路由列表
const routes = [...ctx.pages];
// 子包
if (ctx.subPackages) {
ctx.subPackages.forEach((a: { pages: any[]; root: string }) => {
a.pages.forEach((b) => {
routes.push({
...b,
path: a.root + "/" + b.path,
});
});
});
}
// 钩子函数
const fn: { [key: string]: (...args: any[]) => any } = {};
// 路由
const router = {
// 底部导航
get tabs(): Tabs {
if (ctx.tabBar) {
return ctx.tabBar.list || [];
} else {
return [];
}
},
// 全局样式配置
globalStyle: ctx.globalStyle,
// 路由列表
routes,
// 跳转参数(地址栏)
get query() {
const info = this.info();
return {
...info?.query,
};
},
// 跳转参数(缓存)
get params() {
return storage.get("router-params") || {};
},
// 页面路径
get pages() {
return {
home: "/" + (ctx.tabBar ? this.tabs[0].pagePath : ctx.pages[0].path),
...config.app.pages,
};
},
// 当前页面信息
currentPage(): { [key: string]: any } {
return last(getCurrentPages())!;
},
// 当前路由路径
get path() {
return router.info()?.path;
},
// 当前路由信息
info() {
const page = last(getCurrentPages());
if (page) {
const { route, $page, $vm, $getAppWebview }: any = page;
const q: any = {};
try {
$page?.fullPath
.split("?")[1]
.split("&")
.forEach((e: string) => {
const [k, v] = e.split("=");
q[k] = decodeURIComponent(v);
});
} catch (e) {}
// 页面配置
const style = this.routes.find((e) => e.path == route)?.style;
let d = {
$vm,
$getAppWebview,
path: `/${route}`,
fullPath: $page?.fullPath,
query: q || {},
isTab: this.isTab(route),
style,
isCustomNavbar: style?.navigationStyle == "custom",
};
return d;
} else {
return null;
}
},
// 路由跳转
push(options: PushOptions) {
if (typeof options == "string") {
options = {
path: options,
mode: "navigateTo",
};
}
let {
path,
mode = "navigateTo",
animationType,
animationDuration,
events,
success,
fail,
complete,
query,
params,
isGuard = true,
} = options || {};
if (query) {
let arr = [];
for (let i in query) {
if (query[i] !== undefined) {
arr.push(`${i}=${query[i]}`);
}
}
path += "?" + arr.join("&");
}
if (params) {
storage.set("router-params", params);
}
let data = {
url: path,
animationType,
animationDuration,
events,
success,
fail,
complete,
};
if (this.isTab(path)) {
mode = "switchTab";
}
const next = () => {
switch (mode) {
case "navigateTo":
uni.navigateTo(data);
break;
case "redirectTo":
uni.redirectTo(data);
break;
case "reLaunch":
uni.reLaunch(data);
break;
case "switchTab":
uni.switchTab(data);
break;
case "preloadPage":
uni.preloadPage(data);
break;
}
};
if (fn.beforeEach && isGuard) {
fn.beforeEach({ path: options.path, query }, next, (options: PushOptions) => {
this.push(options);
});
} else {
next();
}
},
// 后退
back(options?: UniApp.NavigateBackOptions) {
if (this.isFirstPage()) {
this.home();
} else {
uni.navigateBack(options || {});
}
},
// 执行当前页面的某个方法
callMethod(name: string, data?: any) {
const { $vm } = this.info()!;
if ($vm) {
if ($vm.$.exposed?.[name]) {
return $vm.$.exposed[name](data);
}
}
},
// 页面栈长度是否只有1
isFirstPage() {
return getCurrentPages().length == 1;
},
// 是否当前页
isCurrentPage(path: string) {
return this.info()?.path == path;
},
// 回到首页
home() {
this.push(this.pages.home);
},
// 跳转 Tab 页
switchTab(name: string) {
let item = this.tabs.find((e) => e.pagePath.includes(name));
if (item) {
this.push({
path: `/${item.pagePath}`,
mode: "switchTab",
});
} else {
console.error("Not found tab", name);
}
},
// 是否是 Tab 页
isTab(path: string) {
return !!this.tabs.find((e) => path == `/${e.pagePath}`);
},
// 去登陆
login(options?: { reLaunch: boolean }) {
const { reLaunch = false } = options || {};
this.push({
path: this.pages.login,
mode: reLaunch ? "reLaunch" : "navigateTo",
isGuard: false,
});
},
// 登录成功后操作
nextLogin(type?: string) {
const pages = getCurrentPages();
const index = pages.findIndex((e) => this.pages.login.includes(e.route!));
if (index <= 0) {
this.home();
} else {
router.back({
delta: pages.length - index,
});
}
// 登录方式
storage.set("loginType", type);
// 登录回调
if (fn.afterLogin) {
fn.afterLogin();
}
// 事件
uni.$emit("afterLogin", { type });
},
// 跳转前钩子
beforeEach(callback: (to: any, next: () => void) => void) {
fn.beforeEach = callback;
},
// 登录后回调
afterLogin(callback: () => void) {
fn.afterLogin = callback;
},
};
export { router };

89
cool/service/base.ts

@ -0,0 +1,89 @@
import { config } from "../../config";
import request from "./request";
export class BaseService {
namespace?: string;
constructor(namespace?: string) {
if (namespace) {
this.namespace = namespace;
}
}
// 发送请求
async request(options: any = {}) {
let url = options.url;
if (url && url.indexOf("http") < 0) {
if (this.namespace) {
url = this.namespace + url;
}
if (options.proxy !== false) {
url = config.baseUrl + "/" + url;
}
}
// 处理参数
options.data =
options.method?.toLocaleUpperCase() == "POST" ? options.data : options.params;
return request({
...options,
url,
});
}
// 获取列表
async list(data: any) {
return this.request({
url: "/list",
method: "POST",
data,
});
}
// 分页查询
async page(data: any) {
return this.request({
url: "/page",
method: "POST",
data,
});
}
// 获取信息
async info(params: any) {
return this.request({
url: "/info",
params,
});
}
// 更新数据
async update(data: any) {
return this.request({
url: "/update",
method: "POST",
data,
});
}
// 删除数据
async delete(data: any) {
return this.request({
url: "/delete",
method: "POST",
data,
});
}
// 添加数据
async add(data: any) {
return this.request({
url: "/add",
method: "POST",
data,
});
}
}

9
cool/service/index.ts

@ -0,0 +1,9 @@
import { BaseService } from "./base";
// service 数据集合
// @ts-ignore
export const service: Eps.Service = {
request: new BaseService().request,
};
export * from "./base";

133
cool/service/request.ts

@ -0,0 +1,133 @@
import { useStore } from "../store";
import { router } from "../router";
import { isDev, config } from "../../config";
import { storage } from "../utils";
import { getLocale, t } from "/@/locale";
// 请求队列
let requests: any[] = [];
// Token 是否刷新中
let isRefreshing = false;
export default function request(options: any) {
const { user } = useStore();
// 标识
let Authorization = user.token || "";
// 忽略标识
config.ignore.token.forEach((e) => {
if (options.url.includes(e)) {
Authorization = "";
}
});
if (isDev) {
console.log(`[${options.method || "GET"}] ${options.url}`);
}
return new Promise(async (resolve, reject) => {
// 继续请求
function next() {
uni.request({
...options,
header: {
Authorization,
language: getLocale(),
...options.header,
},
success(res: any) {
const { code, data, message } = res.data as {
code: number;
message: string;
data: any;
};
// 无权限
if (res.statusCode === 401) {
if (router.info()?.path == router.pages.login) {
return reject({ message });
} else {
user.logout();
}
}
// 服务异常
if (res.statusCode === 502) {
return reject({
message: t("服务异常"),
});
}
// 未找到
if (res.statusCode === 404) {
return reject({
message: `[404] ${options.url}`,
});
}
// 成功
if (res.statusCode === 200) {
switch (code) {
case 1000:
resolve(data);
break;
default:
reject({ message, code });
}
} else {
reject({ message: t("服务异常") });
}
},
fail(err: any) {
reject({ message: err.errMsg });
},
});
}
// 刷新token处理
if (!options.url.includes("refreshToken")) {
if (Authorization) {
// 判断 token 是否过期
if (storage.isExpired("token")) {
// 判断 refreshToken 是否过期
if (storage.isExpired("refreshToken")) {
// 退出登录
return user.logout();
}
// 是否在刷新中
if (!isRefreshing) {
isRefreshing = true;
user.refreshToken()
.then((token) => {
requests.forEach((cb) => cb(token));
requests = [];
isRefreshing = false;
})
.catch((err) => {
user.logout();
reject(err);
});
}
return new Promise((resolve) => {
// 继续请求
requests.push((token: string) => {
// 重新设置 token
Authorization = token;
next();
resolve();
});
});
}
}
}
next();
});
}

22
cool/service/sign.ts

@ -0,0 +1,22 @@
import md5 from "md5";
function useSign(params: any) {
const timestamp = new Date().getTime();
let arr = [`timestamp=${timestamp}`];
for (const i in params) {
arr.push(`${i}=${decodeURIComponent(params[i])}`);
}
arr.sort();
const sign = md5(arr.join("&"));
return {
timestamp,
sign,
};
}
export { useSign };

67
cool/store/dict.ts

@ -0,0 +1,67 @@
import { defineStore } from "pinia";
import { computed, reactive, toRaw } from "vue";
import { deepTree, isEmpty } from "../utils";
import { service } from "../service";
import { isDev } from "/@/config";
import { isString } from "lodash-es";
import type { Dict } from "../types";
const useDictStore = defineStore("dict", () => {
// 对象数据
const data = reactive<Dict.Data>({});
// 获取数据列表
function get(name: Dict.Key) {
return computed(() => data[name]).value || [];
}
// 获取名称
function getLabel(name: Dict.Key | any[], value: any): string {
const arr: any[] = String(value)?.split(",") || [];
return arr
.map((e) => {
return (isString(name) ? get(name) : name).find((a) => a.value == e)?.label;
})
.filter(Boolean)
.join(",");
}
// 刷新
async function refresh(types?: Dict.Key[]) {
return service.dict.info
.data({
types,
})
.then((res: Dict.Data) => {
const d: any = {};
for (const [i, arr] of Object.entries(res)) {
arr.forEach((e) => {
e.label = e.name;
e.value = isEmpty(e.value) ? e.id : e.value;
});
d[i] = deepTree(arr, "desc");
}
Object.assign(data, d);
if (isDev) {
console.log("字典数据:");
console.log(toRaw(data));
}
return data;
});
}
return {
data,
get,
getLabel,
refresh,
};
});
export { useDictStore };

9
cool/store/index.ts

@ -0,0 +1,9 @@
import { useUserStore } from "./user";
import { useDictStore } from "./dict";
export function useStore() {
return {
user: useUserStore(),
dict: useDictStore(),
};
}

94
cool/store/user.ts

@ -0,0 +1,94 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { deepMerge, storage } from "../utils";
import { router } from "../router";
import { service } from "../service";
import type { User } from "../types";
// 本地缓存
const data = storage.info();
const useUserStore = defineStore("user", function () {
// 标识
const token = ref(data.token || "");
// 设置标识
function setToken(data: User.Token) {
token.value = data.token;
// 访问
storage.set("token", data.token, data.expire - 5);
// 刷新
storage.set("refreshToken", data.refreshToken, data.refreshExpire - 5);
}
// 刷新标识
async function refreshToken() {
return service.user.login
.refreshToken({
refreshToken: storage.get("refreshToken"),
})
.then((res) => {
setToken(res);
return res.token;
});
}
// 用户信息
const info = ref<User.Info | undefined>(data.userInfo);
// 设置用户信息
function set(value: User.Info) {
info.value = value;
storage.set("userInfo", value);
}
// 更新用户信息
async function update(data: User.Info & { [key: string]: any }) {
set(deepMerge(info.value, data));
return service.user.info.updatePerson(data);
}
// 清除用户
function clear() {
storage.remove("userInfo");
storage.remove("token");
storage.remove("refreshToken");
token.value = "";
info.value = undefined;
}
// 退出
function logout() {
clear();
router.login({ reLaunch: true });
}
// 获取用户信息
async function get() {
return service.user.info
.person()
.then((res) => {
if (res) {
set(res);
}
return res;
})
.catch(() => {
logout();
});
}
return {
token,
setToken,
refreshToken,
info,
get,
set,
update,
logout,
};
});
export { useUserStore };

45
cool/types/index.d.ts

@ -0,0 +1,45 @@
export declare interface ModuleConfig {
name?: string;
description?: string;
order?: number;
demo?: { label: string; path: string };
options?: {
[key: string]: any;
};
onLoad?(options?: any): any;
}
export declare interface Module extends ModuleConfig {
name: string;
options: {
[key: string]: any;
};
[key: string]: any;
}
export namespace User {
interface Token {
token: string;
expire: number;
refreshToken: string;
refreshExpire: number;
}
interface Info extends Eps.UserInfoEntity {}
}
export namespace Dict {
type Key = Eps.DictKey | (string & {});
interface Item {
id: string;
label: string;
value: any;
children?: Item[];
[key: string]: any;
}
interface Data {
[key: string]: Item[];
}
}

52
cool/upload/comm.ts

@ -0,0 +1,52 @@
import { isArray, has, isObject } from "lodash-es";
type Size = number | number[] | { h?: number; w?: number; m?: string };
function parse(rules: string[], { url, size }: { url: string; size: Size }) {
if (!url) {
return "";
}
if (url.startsWith("blob:") || url.includes("file://")) {
return url;
}
let h = 0;
let w = 0;
if (isArray(size)) {
h = size[0];
w = size[1];
} else if (isObject(size) && has(size, "h")) {
h = size.h!;
w = size.w!;
if (size.m) {
rules.push(`m_${size.m}`);
}
} else {
h = w = Number(size);
}
url += url.includes("?") ? "&" : "?";
if (h) {
rules.push(`h_${h}`);
}
if (w) {
rules.push(`w_${w}`);
}
return `${url}${rules.join(",")}`;
}
function videoPoster(url: string, size: Size) {
return parse(["x-oss-process=video/snapshot,t_1000,f_jpg,m_fast"], { url, size });
}
function resizeImage(url: string, size: Size) {
return parse(["x-oss-process=image/resize"], { url, size });
}
export { videoPoster, resizeImage };

141
cool/upload/index.ts

@ -0,0 +1,141 @@
import dayjs from "dayjs";
import { config } from "../../config";
import { service } from "../service";
import { basename, pathJoin, uuid } from "../utils";
import { useStore } from "../store";
import { videoPoster, resizeImage } from "./comm";
declare interface UploadCallback {
onProgressUpdate?(options: UniApp.OnProgressUpdateResult): void;
onTask?(task: UniApp.UploadTask): void;
}
export async function upload(file: any, cb?: UploadCallback): Promise<string> {
const { onProgressUpdate, onTask } = cb || {};
// 获取上传模式
const { mode, type } = await service.base.comm.uploadMode();
// 用户缓存
const { user } = useStore();
// 本地上传
const isLocal = mode == "local";
// 文件名
const fileName = uuid() + "_" + (file.name || basename(file.path));
// Key
const key = isLocal ? fileName : pathJoin("app", dayjs().format("YYYY-MM-DD"), fileName);
// 多种上传请求
return new Promise((resolve, reject) => {
// 上传文件
function next({ host, preview, data }: { host: string; preview?: string; data?: any }) {
// 签名数据
const fd = {
...data,
key,
};
// 上传
const task = uni.uploadFile({
url: host,
filePath: file.path,
name: "file",
header: isLocal
? {
Authorization: user.token,
}
: {},
formData: fd,
success(res) {
if (isLocal) {
const { code, data, message } = JSON.parse(res.data);
if (code == 1000) {
resolve(data);
} else {
reject(message);
}
} else {
resolve(pathJoin(preview || host, fd.key));
}
},
fail(err) {
reject(err);
},
});
if (onTask) {
onTask(task);
}
if (onProgressUpdate) {
task.onProgressUpdate(onProgressUpdate);
}
}
if (isLocal) {
next({
host: config.baseUrl + "/app/base/comm/upload",
});
} else {
service.base.comm
.upload(
type == "aws"
? {
key,
}
: {}
)
.then((res) => {
switch (type) {
// 腾讯
case "cos":
next({
host: res.url,
data: res.credentials,
});
break;
// 阿里
case "oss":
next({
host: res.host,
data: {
OSSAccessKeyId: res.OSSAccessKeyId,
policy: res.policy,
signature: res.signature,
},
});
break;
// 七牛
case "qiniu":
next({
host: res.uploadUrl,
preview: res.publicDomain,
data: {
token: res.token,
},
});
break;
// aws
case "aws":
next({
host: res.url,
data: res.fields,
});
break;
}
})
.catch(reject);
}
});
}
export function useUpload() {
return {
upload,
videoPoster,
resizeImage,
};
}

603
cool/utils/canvas.ts

@ -0,0 +1,603 @@
import { getCurrentInstance } from "vue";
import { isEmpty, isString, cloneDeep, isObject } from "lodash-es";
// 渲染参数
declare interface RenderOptions {
x: number;
y: number;
height?: number;
width?: number;
[key: string]: any;
}
// 文本渲染参数
declare interface TextRenderOptions extends RenderOptions {
text: string;
color?: string;
fontSize?: number;
textAlign?: "left" | "right" | "center";
overflow?: "ellipsis";
lineClamp?: number;
letterSpace?: number;
lineHeight?: number;
}
// 图片渲染参数
declare interface ImageRenderOptions extends RenderOptions {
url: string;
mode?: "aspectFill" | "aspectFit";
radius?: number;
}
// 块渲染参数
declare interface DivRenderOptions extends RenderOptions {
radius?: number;
backgroundColor?: string;
border?: {
width: number;
color: string;
};
}
// 导出图片参数
declare interface CreateImageOptins {
x?: number;
y?: number;
width?: number;
height?: number;
destWidth?: number;
destHeight?: number;
fileType?: "jpg" | "png";
quality?: number;
}
class Canvas {
ctx: any;
canvasId: any;
scope: any;
renderQuene: any;
imageQueue: any;
constructor(canvasId: string) {
// 绘图上下文
this.ctx = null;
// canvas id
this.canvasId = canvasId;
// 当前页面作用域
const { proxy }: any = getCurrentInstance();
this.scope = proxy;
// 渲染队列
this.renderQuene = [];
// 图片队列
this.imageQueue = [];
// 创建画布
this.create();
}
// 创建画布
create() {
this.ctx = uni.createCanvasContext(this.canvasId, this.scope);
return this;
}
// 块
div(options: DivRenderOptions) {
let render = () => {
this.divRender(options);
};
this.renderQuene.push(render);
return this;
}
// 文本
text(options: TextRenderOptions) {
let render = () => {
this.textRender(options);
};
this.renderQuene.push(render);
return this;
}
// 图片
image(options: ImageRenderOptions) {
let render = () => {
this.imageRender(options);
};
this.imageQueue.push(options);
this.renderQuene.push(render);
return this;
}
// 绘画
draw(save = false) {
return new Promise((resolve) => {
let next = () => {
this.render();
this.ctx.draw(save, () => {
resolve(true);
});
};
if (!isEmpty(this.imageQueue)) {
this.preLoadImage().then(next);
} else {
next();
}
});
}
// 生成图片
createImage(options?: CreateImageOptins): Promise<string> {
return new Promise((resolve, reject) => {
let data = {
canvasId: this.canvasId,
...options,
success: (res: any) => {
// #ifdef MP-ALIPAY
resolve(res.apFilePath);
// #endif
// #ifndef MP-ALIPAY
resolve(res.tempFilePath);
// #endif
},
fail: reject,
};
// #ifdef MP-ALIPAY
this.ctx.toTempFilePath(data);
// #endif
// #ifndef MP-ALIPAY
uni.canvasToTempFilePath(data, this.scope);
// #endif
});
}
// 保存图片
saveImage(options?: CreateImageOptins) {
uni.showLoading({
title: "图片下载中...",
});
this.createImage(options).then((path: any) => {
return new Promise((resolve) => {
uni.hideLoading();
uni.saveImageToPhotosAlbum({
filePath: path,
success: () => {
uni.showToast({
title: "保存图片成功",
});
resolve(path);
},
fail: (err) => {
// #ifdef MP-ALIPAY
uni.showToast({
title: "保存图片成功",
});
// #endif
// #ifndef MP-ALIPAY
uni.showToast({
title: "保存图片失败",
icon: "none",
});
// #endif
},
});
});
});
}
// 预览图片
previewImage(options?: CreateImageOptins) {
this.createImage(options).then((url: string | any) => {
uni.previewImage({
urls: [url],
});
});
}
// 下载图片
downLoadImage(item: any) {
return new Promise((resolve, reject) => {
if (!item.url) {
return reject("url 不能为空");
}
// 处理base64
// #ifdef MP
if (item.url.indexOf("data:image") >= 0) {
let extName = item.url.match(/data\:\S+\/(\S+);/);
if (extName) {
extName = extName[1];
}
const fs = uni.getFileSystemManager();
const fileName = Date.now() + "." + extName;
// @ts-ignore
const filePath = wx.env.USER_DATA_PATH + "/" + fileName;
return fs.writeFile({
filePath,
data: item.url.replace(/^data:\S+\/\S+;base64,/, ""),
encoding: "base64",
success: () => {
item.url = filePath;
resolve(filePath);
},
});
}
// #endif
// 是否网络图片
const isHttp = item.url.includes("http");
uni.getImageInfo({
src: item.url,
success: (result) => {
item.sheight = result.height;
item.swidth = result.width;
if (isHttp) {
item.url = result.path;
}
resolve(item.url);
},
fail: (err) => {
console.log(err, item.url);
reject(err);
},
});
return 1;
});
}
// 预加载图片
async preLoadImage() {
await Promise.all(this.imageQueue.map(this.downLoadImage));
}
// 设置背景颜色
setBackground(options: any) {
if (!options) return null;
let backgroundColor;
if (!isString(options)) {
backgroundColor = options;
}
if (isString(options.backgroundColor)) {
backgroundColor = options.backgroundColor;
}
if (isObject(options.backgroundColor)) {
let { startX, startY, endX, endY, gradient } = options.backgroundColor;
const rgb = this.ctx.createLinearGradient(startX, startY, endX, endY);
for (let i = 0, l = gradient.length; i < l; i++) {
rgb.addColorStop(gradient[i].step, gradient[i].color);
}
backgroundColor = rgb;
}
this.ctx.setFillStyle(backgroundColor);
return this;
}
// 设置边框
setBorder(options: any) {
if (!options.border) return this;
let { x, y, width: w, height: h, border, radius: r } = options;
if (border.width) {
this.ctx.setLineWidth(border.width);
}
if (border.color) {
this.ctx.setStrokeStyle(border.color);
}
// 偏移距离
let p = border.width / 2;
// 是否有圆角
if (r) {
this.drawRadiusRoute(x - p, y - p, w + 2 * p, h + 2 * p, r + p);
this.ctx.stroke();
} else {
this.ctx.strokeRect(x - p, y - p, w + 2 * p, h + 2 * p);
}
return this;
}
// 设置缩放,旋转
setTransform(options: any) {
if (options.scale) {
}
if (options.rotate) {
}
}
// 带有圆角的路径绘制
drawRadiusRoute(x: number, y: number, w: number, h: number, r: number) {
this.ctx.beginPath();
this.ctx.moveTo(x + r, y, y);
this.ctx.lineTo(x + w - r, y);
this.ctx.arc(x + w - r, y + r, r, 1.5 * Math.PI, 0);
this.ctx.lineTo(x + w, y + h - r);
this.ctx.arc(x + w - r, y + h - r, r, 0, 0.5 * Math.PI);
this.ctx.lineTo(x + r, y + h);
this.ctx.arc(x + r, y + h - r, r, 0.5 * Math.PI, Math.PI);
this.ctx.lineTo(x, y + r);
this.ctx.arc(x + r, y + r, r, Math.PI, 1.5 * Math.PI);
this.ctx.closePath();
}
// 裁剪图片
cropImage(
mode: "aspectFill" | "aspectFit",
width: number,
height: number,
sWidth: number,
sHeight: number,
x: number,
y: number
) {
let cx, cy, cw, ch, sx, sy, sw, sh;
switch (mode) {
case "aspectFill":
if (width <= height) {
let p = width / sWidth;
cw = width;
ch = sHeight * p;
cx = 0;
cy = (height - ch) / 2;
} else {
let p = height / sHeight;
cw = sWidth * p;
ch = height;
cx = (width - cw) / 2;
cy = 0;
}
break;
case "aspectFit":
if (width <= height) {
let p = height / sHeight;
sw = width / p;
sh = sHeight;
sx = x + (sWidth - sw) / 2;
sy = y;
} else {
let p = width / sWidth;
sw = sWidth;
sh = height / p;
sx = x;
sy = y + (sHeight - sh) / 2;
}
break;
}
return { cx, cy, cw, ch, sx, sy, sw, sh };
}
// 获取文本内容
getTextRows({
text,
fontSize = 14,
width = 100,
lineClamp = 1,
overflow,
letterSpace = 0,
}: any) {
let arr: any[] = [[]];
let a = 0;
for (let i = 0; i < text.length; i++) {
let b = this.getFontPx(text[i], { fontSize, letterSpace });
if (a + b > width) {
a = b;
arr.push(text[i]);
} else {
// 最后一行且设置超出省略号
if (
overflow == "ellipsis" &&
arr.length == lineClamp &&
a + 3 * this.getFontPx(".", { fontSize, letterSpace }) > width - 5
) {
arr[arr.length - 1] += "...";
break;
} else {
a += b;
arr[arr.length - 1] += text[i];
}
}
}
return arr;
}
// 获取单个字体像素大小
getFontPx(text: string, { fontSize = 14, letterSpace }: any) {
if (!text) {
return fontSize / 2 + fontSize / 14 + letterSpace;
}
let ch = text.charCodeAt(0);
if ((ch >= 0x0001 && ch <= 0x007e) || (0xff60 <= ch && ch <= 0xff9f)) {
return fontSize / 2 + fontSize / 14 + letterSpace;
} else {
return fontSize + letterSpace;
}
}
// 渲染块
divRender(options: DivRenderOptions) {
this.ctx.save();
this.setBackground(options);
this.setBorder(options);
this.setTransform(options);
// 区分是否有圆角采用不同模式渲染
if (options.radius) {
let { x, y } = options;
let w = options.width || 0;
let h = options.height || 0;
let r = options.radius || 0;
// 画路径
this.drawRadiusRoute(x, y, w, h, r);
// 填充
this.ctx.fill();
} else {
this.ctx.fillRect(options.x, options.y, options.width, options.height);
}
this.ctx.restore();
}
// 渲染文本
textRender(options: TextRenderOptions) {
let {
fontSize = 14,
textAlign,
width,
color = "#000000",
x,
y,
letterSpace,
lineHeight = 14,
} = options || {};
this.ctx.save();
// 设置字体大小
this.ctx.setFontSize(fontSize);
// 设置字体颜色
this.ctx.setFillStyle(color);
// 获取文本内容
let rows = this.getTextRows(options);
// 获取文本行高
let lh = lineHeight - fontSize;
// 左偏移
let offsetLeft = 0;
// 字体对齐
if (textAlign && width) {
this.ctx.textAlign = textAlign;
switch (textAlign) {
case "left":
break;
case "center":
offsetLeft = width / 2;
break;
case "right":
offsetLeft = width;
break;
}
}
// 逐行写入
for (let i = 0; i < rows.length; i++) {
let d = offsetLeft;
if (letterSpace) {
for (let j = 0; j < rows[i].length; j++) {
// 写入文字
this.ctx.fillText(rows[i][j], x + d, (i + 1) * fontSize + y + lh * i);
// 设置偏移
d += this.getFontPx(rows[i][j], options);
}
} else {
// 写入文字
this.ctx.fillText(rows[i], x + offsetLeft, (i + 1) * fontSize + y + lh * i);
}
}
this.ctx.restore();
}
// 渲染图片
imageRender(options: ImageRenderOptions) {
this.ctx.save();
if (options.radius) {
// 画路径
this.drawRadiusRoute(
options.x,
options.y,
options.width || options.swidth,
options.height || options.sHeight,
options.radius
);
// 填充
this.ctx.fill();
// 裁剪
this.ctx.clip();
}
let temp = cloneDeep(this.imageQueue[0]);
if (options.mode) {
let { cx, cy, cw, ch, sx, sy, sw, sh } = this.cropImage(
options.mode,
temp.swidth,
temp.sheight,
temp.width,
temp.height,
temp.x,
temp.y
);
switch (options.mode) {
case "aspectFit":
this.ctx.drawImage(temp.url, sx, sy, sw, sh);
break;
case "aspectFill":
this.ctx.drawImage(
temp.url,
cx,
cy,
cw,
ch,
temp.x,
temp.y,
temp.width,
temp.height
);
break;
}
} else {
this.ctx.drawImage(
temp.url,
temp.x,
temp.y,
temp.width || temp.swidth,
temp.height || temp.sheight
);
}
this.imageQueue.shift();
this.ctx.restore();
}
// 渲染全部
render() {
this.renderQuene.forEach((ele: any) => {
ele();
});
}
}
export { Canvas };

168
cool/utils/comm.ts

@ -0,0 +1,168 @@
import { orderBy } from "lodash-es";
export const { platform } = uni.getSystemInfoSync();
// 是否安卓
export const isAndroid = platform == "android";
// 是否苹果
export const isIos = platform == "ios";
// 是否小数
export function isDecimal(value: any): boolean {
return String(value).length - String(value).indexOf(".") + 1 > 0;
}
// 首字母大写
export function firstUpperCase(value: string): string {
return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
return $1.toUpperCase() + $2;
});
}
// 深度合并
export function deepMerge(a: any, b: any) {
let k;
for (k in b) {
a[k] =
a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
}
return a;
}
// 获取地址栏参数
export function getUrlParam(name: string): string | null {
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
const r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURIComponent(r[2]);
return null;
}
// 列表转树形
export function deepTree(list: any[], sort?: "desc" | "asc"): any[] {
const newList: any[] = [];
const map: any = {};
orderBy(list, "orderNum", sort)
.map((e) => {
map[e.id] = e;
return e;
})
.forEach((e) => {
const parent = map[e.parentId];
if (parent) {
(parent.children || (parent.children = [])).push(e);
} else {
newList.push(e);
}
});
return newList;
}
// 路径转对象
export function path2Obj(list: any[]) {
const data: any = {};
list.forEach(({ path, value }) => {
const arr: string[] = path.split("/");
const parents = arr.slice(0, arr.length - 1);
const name = basename(path).replace(".ts", "");
let curr = data;
parents.forEach((k) => {
if (!curr[k]) {
curr[k] = {};
}
curr = curr[k];
});
curr[name] = value;
});
return data;
}
// 路径拼接
export function pathJoin(...parts: string[]): string {
if (parts.length === 0) {
return "";
}
const firstPart = parts[0];
let isAbsolute = false;
// 检查第一个部分是否以 "http" 开头,以确定路径类型(绝对还是相对)
if (firstPart.startsWith("http")) {
isAbsolute = true;
}
// 标准化路径,去除任何开头或结尾的斜杠
const normalizedParts = parts.map((part) => part.replace(/(^\/+|\/+$)/g, ""));
if (isAbsolute) {
// 如果是绝对路径,使用斜杠连接部分
return normalizedParts.join("/");
} else {
// 如果是相对路径,使用平台特定的分隔符连接部分
return normalizedParts.join("/");
}
}
// 文件名
export function filename(path: string): string {
return basename(path.substring(0, path.lastIndexOf(".")));
}
// 路径名称
export function basename(path: string): string {
let index = path.lastIndexOf("/");
index = index > -1 ? index : path.lastIndexOf("\\");
if (index < 0) {
return path;
}
return path.substring(index + 1);
}
// 文件扩展名
export function extname(path: string): string {
return path.substring((path || "").lastIndexOf(".") + 1);
}
// 横杠转驼峰
export function toCamel(str: string): string {
return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
return $1 + $2.toUpperCase();
});
}
// uuid
export function uuid(): string {
const s: any[] = [];
const hexDigits = "0123456789abcdef";
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4";
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = "-";
return s.join("");
}
// 延迟
export function sleep(duration: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, duration);
});
}
// 是否为空
export function isEmpty(val: any) {
return val === "" || val === null || val === undefined;
}

4
cool/utils/index.ts

@ -0,0 +1,4 @@
export * from "./comm";
export * from "./ui";
export * from "./canvas";
export * from "./storage";

75
cool/utils/storage.ts

@ -0,0 +1,75 @@
export const storage = {
// 后缀标识
suffix: "_deadtime",
/**
*
* @param {*} key
*/
get(key: string): any {
return uni.getStorageSync(key);
},
/**
*
*/
info() {
const { keys } = uni.getStorageInfoSync();
const d: any = {};
keys.forEach((e: string) => {
d[e] = uni.getStorageSync(e);
});
return d;
},
/**
*
* @param {*} key
* @param {*} value
* @param {*} expires
*/
set(key: string, value: any, expires?: number): void {
uni.setStorageSync(key, value);
if (expires) {
uni.setStorageSync(
`${key}${this.suffix}`,
Date.parse(String(new Date())) + expires * 1000
);
}
},
/**
*
* @param {*} key
*/
isExpired(key: string): boolean {
return uni.getStorageSync(`${key}${this.suffix}`) - Date.parse(String(new Date())) <= 0;
},
/**
*
* @param {*} key
*/
remove(key: string) {
return uni.removeStorageSync(key);
},
/**
*
*/
clear() {
uni.clearStorageSync();
},
/**
*
*/
once(key: string) {
const value = this.get(key);
this.remove(key);
return value;
},
};

72
cool/utils/ui.ts

@ -0,0 +1,72 @@
import { isArray, isEmpty, isNumber } from "lodash-es";
import { computed, getCurrentInstance, nextTick, ref } from "vue";
// 获取父组件
export function getParent(name: string, k1: string[], k2?: string[]) {
const { proxy }: any = getCurrentInstance();
const d = ref();
let n = 10;
const next = () => {
let parent = proxy.$parent;
while (parent) {
if (parent.$options.name !== name) {
parent = parent.$parent;
} else {
if (isArray(k2)) {
nextTick(() => {
const child: any = {};
(k2 || []).map((key: string) => {
if (proxy[key]) {
child[key] = proxy[key];
}
});
if (!parent.__children) {
parent.__children = [];
}
if (!isEmpty(child)) {
parent.__children.push(child);
}
});
}
return (k1 || []).reduce((res: any, key: string) => {
res[key] = parent[key];
return res;
}, {});
}
}
return parent || d.value;
};
return computed(() => next());
}
// 获取元素位置信息
export async function getRect(selector: string): Promise<any> {
return new Promise((resolve) => {
uni.createSelectorQuery()
.select(selector)
.boundingClientRect((res) => {
resolve(res);
})
.exec();
});
}
// 解析rpx
export function parseRpx(val: any): string {
return isArray(val) ? val.map(parseRpx).join(" ") : isNumber(val) ? `${val}rpx` : val;
}
// px 转 rpx
export function px2Rpx(px: number) {
return px / (uni.upx2px(100) / 100);
}

BIN
doc/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

20
hooks/index.ts

@ -0,0 +1,20 @@
import { defineStore } from "pinia";
import { ref } from "vue";
// 方式一,创建缓存方式
export const useTest = defineStore("test", () => {
const data = ref();
return {
data,
};
});
// 方式2
export function useTest2() {
const data = ref();
return {
data,
};
}

24
index.html

@ -0,0 +1,24 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<script>
var coverSupport =
"CSS" in window &&
typeof CSS.supports === "function" &&
(CSS.supports("top: env(a)") || CSS.supports("top: constant(a)"));
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ", viewport-fit=cover" : "") +
'" />',
);
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.ts"></script>
</body>
</html>

132
locale/en.json

@ -0,0 +1,132 @@
{
"已阅读并同意": "Read and Agree",
"和": "and",
"请先勾选同意后再进行登录": "Please check the agreement before logging in",
"page.设置": "Settings",
"page.编辑": "Edit",
"page.关于我们": "About Us",
"演示": "Demo",
"官网": "Official Website",
"系统": "System",
"我的": "Mine",
"清空": "Clear",
"确认": "Confirm",
"暂无数据": "No data",
"取消": "Cancel",
"账号": "Account",
"头像": "Avatar",
"昵称": "Nickname",
"手机号": "Mobile number",
"关于": "About",
"用户协议": "User agreement",
"隐私政策": "Privacy policy",
"切换账号": "Switch account",
"退出登录": "Log out",
"头像更新成功": "Avatar updated successfully",
"手机号登录": "Log in with mobile number",
"请填写手机号码": "Please fill in the mobile number",
"微信一键登录": "One-click login with WeChat",
"其他登录方式": "Other login methods",
"获取你的头像、昵称": "Get your avatar and nickname",
"用于向用户提供有辨识度的界面": "Used to provide a recognizable interface for users",
"请选择所在地区": "Please select your region",
"搜索": "Search",
"顶部": "Top",
"正在刷新": "Refreshing",
"下拉刷新": "Pull down to refresh",
"释放刷新": "Release to refresh",
"选择支付方式": "Select payment method",
"取消支付": "Cancel payment",
"微信支付": "WeChat Pay",
"支付宝支付": "Alipay",
"加载中": "Loading",
"提示": "Tip",
"上拉加载更多": "Pull up to load more",
"没有更多了": "No more",
"搜索关键字": "Search keyword",
"请填写": "Please fill in",
"上一步": "Previous step",
"下一步": "Next step",
"跳过": "Skip",
"完成": "Finish",
"上传/拍摄": "Upload/Take Photo",
"图片地址错误": "Image Address Error",
"拖动验证": "Drag to Verify",
"请向右拖动滑块完成拼图": "Drag the slider to complete the puzzle",
"请拖动滑块旋转至正确位置": "Drag the slider to rotate to the correct position",
"分享至": "Share to",
"QQ好友": "QQ Friends",
"朋友圈": "Moments",
"微信好友": "WeChat Friends",
"确定": "OK",
"请选择": "Please Select",
"选择": "Select",
"选择日期": "Select Date",
"请选择时间": "Please Select Time",
"年": "Year",
"月": "Month",
"日": "Day",
"时": "Hour",
"分": "Minute",
"秒": "Second",
"未登录": "Not logged in",
"写签名会更容易获得别人的关注哦~": "Writing a signature makes it easier to gain others' attention~",
"总点击": "Total clicks",
"赞": "Like",
"关注": "Follow",
"粉丝": "Fans",
"接单模式": "Order receiving mode",
"已关闭": "Closed",
"消息通知": "Message notification",
"已开启": "Enabled",
"我的订单": "My orders",
"我的账单": "My bills",
"观看历史": "Viewing history",
"数据看板": "Data dashboard",
"邀请好友": "Invite friends",
"设置": "Settings",
"待支付": "Pending payment",
"未发货": "Not shipped",
"已发货": "Shipped",
"售后 / 退款": "After-sales / Refund",
"订单模块不存在,请在插件市场中下载": "Order module does not exist. Please download it in the plugin market.",
"财务模块不存在,请在插件市场中下载": "Finance module does not exist. Please download it in the plugin market.",
"消息模块不存在,请在插件市场中下载": "Message module does not exist. Please download it in the plugin market.",
"uniapp快速开发脚手架": "Uniapp rapid development scaffolding",
"选择语言": "Select Language",
"插件 / 模块": "Plugin/Module",
"服务异常": "Service exception",
"请在微信浏览器中打开": "Please open in WeChat browser",
"已取消支付": "Payment has been cancelled",
"支付失败": "Payment failed",
"授权信息仅用于用户登录": "Authorization information is only used for user login",
"登录授权失败": "Login authorization failed",
"获取短信验证码": "Get SMS verification code",
"验证码": "Verification code",
"发送短信": "Send SMS",
"{n}s后重新获取": "Retry in {n}s",
"获取验证码": "Get verification code",
"短信已发送,请查收": "SMS has been sent. Please check.",
"请填写验证码": "Please fill in the verification code",
"请填写正确的手机号格式": "Please fill in the correct mobile phone number format",
"请填写昵称、限16个字符或汉字": "Please fill in your nickname, limited to 16 characters or Chinese characters",
"保存": "Save",
"通过手机登录": "Log in via mobile phone",
"通过微信登录": "Log in via WeChat",
"温馨提示": "Warm reminder",
"您还未安装微信~": "You haven't installed WeChat yet~",
"去下载": "Go to download",
"手机号一键登录": "One-key login with mobile number",
"当前环境不支持一键登录,请切换至验证码登录": "One-key login is not supported in the current environment. Please switch to verification code login",
"一键登录": "One-key login",
"我已阅读并同意": "I have read and agreed",
"并使用本机号码登录": "And log in with this mobile number",
"请上传头像": "Please upload an avatar",
"请输入昵称": "Please enter your nickname",
"请填写昵称": "Please fill in your nickname",
"用户信息保存成功": "User information saved successfully",
"输入验证码": "Enter verification code",
"已发送至": "Has been sent to",
"登录失效,请重试~": "Login expired, please try again~",
"联系我们": "Contact us"
}

132
locale/es.json

@ -0,0 +1,132 @@
{
"已阅读并同意": "Leído y aceptado",
"和": "y",
"请先勾选同意后再进行登录": "Por favor, marque la casilla de verificación de aceptación antes de iniciar sesión",
"page.设置": "Configuración",
"page.编辑": "Edición",
"page.关于我们": "Sobre nosotros",
"演示": "Demostración",
"官网": "Sitio web oficial",
"系统": "Sistema",
"我的": "Mío",
"请选择所在地区": "Por favor, selecciona la región",
"搜索": "Buscar",
"顶部": "Superior",
"正在刷新": "Refrescando",
"下拉刷新": "Deslizar hacia abajo para actualizar",
"释放刷新": "Soltar para actualizar",
"选择支付方式": "Seleccionar método de pago",
"取消支付": "Cancelar pago",
"微信支付": "Pago con WeChat",
"支付宝支付": "Pago con Alipay",
"加载中": "Cargando",
"提示": "Advertencia",
"上拉加载更多": "Deslizar hacia arriba para cargar más",
"没有更多了": "No hay más",
"搜索关键字": "Palabra clave de búsqueda",
"请填写": "Por favor, complete",
"上一步": "Paso anterior",
"下一步": "Paso siguiente",
"跳过": "Saltar",
"完成": "Completar",
"未登录": "No registrado",
"写签名会更容易获得别人的关注哦~": "Escribir una firma te ayudará a llamar más la atención de los demás.",
"总点击": "Total de clics",
"赞": "Me gusta",
"关注": "Seguir",
"粉丝": "Seguidores",
"接单模式": "Modo de recibir pedidos",
"已关闭": "Cerrado",
"消息通知": "Notificaciones de mensajes",
"已开启": "Encendido",
"我的订单": "Mis pedidos",
"我的账单": "Mi factura",
"观看历史": "Historial de reproducción",
"数据看板": "Tablero de datos",
"邀请好友": "Invitar amigos",
"设置": "Configuración",
"待支付": "Por pagar",
"未发货": "No enviado",
"已发货": "Enviado",
"售后 / 退款": "Servicio después de la venta / Reembolso",
"清空": "Limpiar",
"确认": "Confirmar",
"暂无数据": "Sin datos",
"取消": "Cancelar",
"账号": "Cuenta",
"头像": "Avatar",
"昵称": "Nombre de usuario",
"手机号": "Número de teléfono",
"关于": "Acerca de",
"用户协议": "Términos de usuario",
"隐私政策": "Política de privacidad",
"切换账号": "Cambiar de cuenta",
"退出登录": "Cerrar sesión",
"头像更新成功": "Actualización de avatar exitosa",
"手机号登录": "Inicio de sesión con número de teléfono",
"请填写手机号码": "Por favor ingrese su número de teléfono",
"微信一键登录": "Inicio de sesión con WeChat de un solo clic",
"其他登录方式": "Otros métodos de inicio de sesión",
"获取你的头像、昵称": "Obtener su avatar y nombre de usuario",
"用于向用户提供有辨识度的界面": "Para proporcionar una interfaz identificable al usuario",
"上传/拍摄": "Subir/Tomar",
"图片地址错误": "Dirección de imagen incorrecta",
"拖动验证": "Arrastrar para validar",
"请向右拖动滑块完成拼图": "Arrastre el deslizador hacia la derecha para completar el rompecabezas",
"请拖动滑块旋转至正确位置": "Arrastre el deslizador y girelo hasta la posición correcta",
"分享至": "Compartir a",
"QQ好友": "Amigos de QQ",
"朋友圈": "Momento",
"微信好友": "Amigos de WeChat",
"确定": "Aceptar",
"请选择": "Seleccionar",
"选择": "Selección",
"选择日期": "Seleccionar fecha",
"请选择时间": "Seleccionar hora",
"年": "Año",
"月": "Mes",
"日": "Día",
"时": "Hora",
"分": "Minuto",
"秒": "Segundo",
"请填写昵称、限16个字符或汉字": "Por favor complete el nombre de usuario, máximo 16 caracteres o chinos",
"保存": "Guardar",
"通过手机登录": "Iniciar sesión con el teléfono móvil",
"通过微信登录": "Iniciar sesión con WeChat",
"温馨提示": "Advertencia",
"您还未安装微信~": "Aún no has instalado WeChat~",
"去下载": "Ir a descargar",
"手机号一键登录": "Iniciar sesión con un solo toque del número de teléfono",
"当前环境不支持一键登录,请切换至验证码登录": "La autenticación unificada no está disponible en el entorno actual. Cambie a la autenticación con código de verificación",
"一键登录": "Autenticación unificada",
"我已阅读并同意": "He leído y aceptado",
"并使用本机号码登录": "Y usar el número de este teléfono para iniciar sesión",
"请上传头像": "Por favor cargue una foto de perfil",
"请输入昵称": "Por favor ingrese el nombre de usuario",
"请填写昵称": "Por favor complete el nombre de usuario",
"用户信息保存成功": "La información del usuario se ha guardado correctamente",
"输入验证码": "Ingrese el código de verificación",
"已发送至": "Se ha enviado a",
"登录失效,请重试~": "La sesión ha caducado. Inténtelo de nuevo~",
"联系我们": "Contáctenos",
"订单模块不存在,请在插件市场中下载": "El módulo de pedidos no existe. Descárguelo en el mercado de complementos.",
"财务模块不存在,请在插件市场中下载": "El módulo financiero no existe. Descárguelo en el mercado de complementos.",
"消息模块不存在,请在插件市场中下载": "El módulo de mensajes no existe. Descárguelo en el mercado de complementos.",
"uniapp快速开发脚手架": "Entorno de desarrollo rápido de uniapp",
"选择语言": "Seleccionar idioma",
"插件 / 模块": "Complementos / Módulos",
"服务异常": "Error en el servicio",
"请在微信浏览器中打开": "Abra en el navegador de WeChat",
"已取消支付": "Pago cancelado",
"支付失败": "Fallo en el pago",
"授权信息仅用于用户登录": "La información de autorización solo se utiliza para el inicio de sesión de usuario",
"登录授权失败": "Fallo en la autorización de inicio de sesión",
"获取短信验证码": "Obtener código de verificación por SMS",
"验证码": "Código de verificación",
"发送短信": "Enviar SMS",
"{n}s后重新获取": "Obtener de nuevo en {n} s",
"获取验证码": "Obtener código de verificación",
"短信已发送,请查收": "El SMS se ha enviado. Compruebe su bandeja de entrada.",
"请填写验证码": "Por favor, rellene el código de verificación",
"请填写正确的手机号格式": "Por favor, introduzca un número de teléfono válido"
}

1
locale/fr.json

@ -0,0 +1 @@
{}

53
locale/index.ts

@ -0,0 +1,53 @@
import { createI18n } from "vue-i18n";
import zhHans from "./zh-Hans.json";
import zhHant from "./zh-Hant.json";
import en from "./en.json";
import es from "./es.json";
import fr from "./fr.json";
const i18n = createI18n({
locale: uni.getLocale(),
// 配置后,使用命令 cool-i18n create 翻译,会自动更新 locale 目录
messages: {
"zh-Hans": zhHans,
"zh-Hant": zhHant,
en,
es,
},
});
const localeMap: { [key: string]: string } = {
"zh-Hans": "zh-cn",
"zh-Hant": "zh-tw",
};
function t(name: string, data?: any) {
let d = i18n.global.t(name, data);
if (data) {
for (const i in data) {
d = d.replace(`{${i}}`, data[i]);
}
}
return d;
}
function setLocale(locale: string) {
uni.setLocale(locale);
i18n.global.locale = locale;
}
function getLocale(): string {
const locale = uni.getLocale();
for (const i in localeMap) {
if (i == locale) {
return localeMap[i];
}
}
return locale;
}
export { i18n, t, setLocale, getLocale };

132
locale/zh-Hans.json

@ -0,0 +1,132 @@
{
"上传/拍摄": "上传/拍摄",
"图片地址错误": "图片地址错误",
"拖动验证": "拖动验证",
"请向右拖动滑块完成拼图": "请向右拖动滑块完成拼图",
"请拖动滑块旋转至正确位置": "请拖动滑块旋转至正确位置",
"分享至": "分享至",
"QQ好友": "QQ好友",
"朋友圈": "朋友圈",
"微信好友": "微信好友",
"确定": "确定",
"请选择": "请选择",
"选择": "选择",
"选择日期": "选择日期",
"请选择时间": "请选择时间",
"年": "年",
"月": "月",
"日": "日",
"时": "时",
"分": "分",
"秒": "秒",
"请选择所在地区": "请选择所在地区",
"搜索": "搜索",
"顶部": "顶部",
"正在刷新": "正在刷新",
"下拉刷新": "下拉刷新",
"释放刷新": "释放刷新",
"选择支付方式": "选择支付方式",
"取消支付": "取消支付",
"微信支付": "微信支付",
"支付宝支付": "支付宝支付",
"加载中": "加载中",
"提示": "提示",
"上拉加载更多": "上拉加载更多",
"没有更多了": "没有更多了",
"搜索关键字": "搜索关键字",
"请填写": "请填写",
"上一步": "上一步",
"下一步": "下一步",
"跳过": "跳过",
"完成": "完成",
"清空": "清空",
"确认": "确认",
"暂无数据": "暂无数据",
"取消": "取消",
"账号": "账号",
"头像": "头像",
"昵称": "昵称",
"手机号": "手机号",
"关于": "关于",
"用户协议": "用户协议",
"隐私政策": "隐私政策",
"切换账号": "切换账号",
"退出登录": "退出登录",
"头像更新成功": "头像更新成功",
"手机号登录": "手机号登录",
"请填写手机号码": "请填写手机号码",
"微信一键登录": "微信一键登录",
"其他登录方式": "其他登录方式",
"获取你的头像、昵称": "获取你的头像、昵称",
"用于向用户提供有辨识度的界面": "用于向用户提供有辨识度的界面",
"请填写昵称、限16个字符或汉字": "请填写昵称、限16个字符或汉字",
"保存": "保存",
"通过手机登录": "通过手机登录",
"通过微信登录": "通过微信登录",
"温馨提示": "温馨提示",
"您还未安装微信~": "您还未安装微信~",
"去下载": "去下载",
"手机号一键登录": "手机号一键登录",
"当前环境不支持一键登录,请切换至验证码登录": "当前环境不支持一键登录,请切换至验证码登录",
"一键登录": "一键登录",
"我已阅读并同意": "我已阅读并同意",
"并使用本机号码登录": "并使用本机号码登录",
"请上传头像": "请上传头像",
"请输入昵称": "请输入昵称",
"请填写昵称": "请填写昵称",
"用户信息保存成功": "用户信息保存成功",
"输入验证码": "输入验证码",
"已发送至": "已发送至",
"登录失效,请重试~": "登录失效,请重试~",
"联系我们": "联系我们",
"未登录": "未登录",
"写签名会更容易获得别人的关注哦~": "写签名会更容易获得别人的关注哦~",
"总点击": "总点击",
"赞": "赞",
"关注": "关注",
"粉丝": "粉丝",
"接单模式": "接单模式",
"已关闭": "已关闭",
"消息通知": "消息通知",
"已开启": "已开启",
"我的订单": "我的订单",
"我的账单": "我的账单",
"观看历史": "观看历史",
"数据看板": "数据看板",
"邀请好友": "邀请好友",
"设置": "设置",
"待支付": "待支付",
"未发货": "未发货",
"已发货": "已发货",
"售后 / 退款": "售后 / 退款",
"订单模块不存在,请在插件市场中下载": "订单模块不存在,请在插件市场中下载",
"财务模块不存在,请在插件市场中下载": "财务模块不存在,请在插件市场中下载",
"消息模块不存在,请在插件市场中下载": "消息模块不存在,请在插件市场中下载",
"uniapp快速开发脚手架": "uniapp快速开发脚手架",
"选择语言": "选择语言",
"插件 / 模块": "插件 / 模块",
"服务异常": "服务异常",
"请在微信浏览器中打开": "请在微信浏览器中打开",
"已取消支付": "已取消支付",
"支付失败": "支付失败",
"授权信息仅用于用户登录": "授权信息仅用于用户登录",
"登录授权失败": "登录授权失败",
"获取短信验证码": "获取短信验证码",
"验证码": "验证码",
"发送短信": "发送短信",
"{n}s后重新获取": "{n}s后重新获取",
"获取验证码": "获取验证码",
"短信已发送,请查收": "短信已发送,请查收",
"请填写验证码": "请填写验证码",
"请填写正确的手机号格式": "请填写正确的手机号格式",
"已阅读并同意": "已阅读并同意",
"和": "和",
"请先勾选同意后再进行登录": "请先勾选同意后再进行登录",
"page.设置": "设置",
"page.编辑": "编辑",
"page.关于我们": "关于我们",
"演示": "演示",
"官网": "官网",
"系统": "系统",
"我的": "我的"
}

132
locale/zh-Hant.json

@ -0,0 +1,132 @@
{
"已阅读并同意": "已閱讀並同意",
"和": "和",
"请先勾选同意后再进行登录": "請先勾選同意後再進行登錄",
"page.设置": "設置",
"page.编辑": "編輯",
"page.关于我们": "關於我們",
"演示": "演示",
"官网": "官網",
"系统": "系統",
"我的": "我的",
"请选择所在地区": "請選擇所在地區",
"搜索": "搜尋",
"顶部": "頂部",
"正在刷新": "正在刷新",
"下拉刷新": "下拉刷新",
"释放刷新": "釋放刷新",
"选择支付方式": "選擇支付方式",
"取消支付": "取消支付",
"微信支付": "微信支付",
"支付宝支付": "支付寶支付",
"加载中": "加載中",
"提示": "提示",
"上拉加载更多": "上拉加載更多",
"没有更多了": "沒有更多了",
"搜索关键字": "搜索關鍵字",
"请填写": "請填寫",
"上一步": "上一步",
"下一步": "下一步",
"跳过": "跳過",
"完成": "完成",
"上传/拍摄": "上傳/拍攝",
"图片地址错误": "圖片地址錯誤",
"拖动验证": "拖動驗證",
"请向右拖动滑块完成拼图": "請向右拖動滑塊完成拼圖",
"请拖动滑块旋转至正确位置": "請拖動滑塊旋轉至正確位置",
"分享至": "分享至",
"QQ好友": "QQ好友",
"朋友圈": "朋友圈",
"微信好友": "微信好友",
"确定": "確定",
"请选择": "請選擇",
"选择": "選擇",
"选择日期": "選擇日期",
"请选择时间": "請選擇時間",
"年": "年",
"月": "月",
"日": "日",
"时": "時",
"分": "分",
"秒": "秒",
"未登录": "未登錄",
"写签名会更容易获得别人的关注哦~": "寫簽名會更容易獲得別人的關注哦~",
"总点击": "總點擊",
"赞": "讚",
"关注": "關注",
"粉丝": "粉絲",
"接单模式": "接單模式",
"已关闭": "已關閉",
"消息通知": "消息通知",
"已开启": "已開啟",
"我的订单": "我的訂單",
"我的账单": "我的賬單",
"观看历史": "觀看歷史",
"数据看板": "數據看板",
"邀请好友": "邀請好友",
"设置": "設置",
"待支付": "待支付",
"未发货": "未發貨",
"已发货": "已發貨",
"售后 / 退款": "售後 / 退款",
"清空": "清空",
"确认": "確認",
"暂无数据": "暂无數據",
"取消": "取消",
"账号": "帳號",
"头像": "頭像",
"昵称": "暱稱",
"手机号": "手機號",
"关于": "關於",
"用户协议": "用戶協議",
"隐私政策": "隱私政策",
"切换账号": "切換帳號",
"退出登录": "退出登錄",
"头像更新成功": "頭像更新成功",
"手机号登录": "手機號登錄",
"请填写手机号码": "請填寫手機號碼",
"微信一键登录": "微信一鍵登錄",
"其他登录方式": "其他登錄方式",
"获取你的头像、昵称": "獲取你的頭像、暱稱",
"用于向用户提供有辨识度的界面": "用於向用戶提供有辨识度的界面",
"请填写昵称、限16个字符或汉字": "請填寫暱稱、限16個字元或漢字",
"保存": "儲存",
"通过手机登录": "透過手機登入",
"通过微信登录": "透過微信登入",
"温馨提示": "溫馨提示",
"您还未安装微信~": "您還未安裝微信~",
"去下载": "去下載",
"手机号一键登录": "手機號一鍵登入",
"当前环境不支持一键登录,请切换至验证码登录": "當前環境不支援一鍵登入,請切換至驗證碼登入",
"一键登录": "一鍵登入",
"我已阅读并同意": "我已閱讀並同意",
"并使用本机号码登录": "並使用本機號碼登入",
"请上传头像": "請上傳頭像",
"请输入昵称": "請輸入暱稱",
"请填写昵称": "請填寫暱稱",
"用户信息保存成功": "用戶資訊儲存成功",
"输入验证码": "輸入驗證碼",
"已发送至": "已發送至",
"登录失效,请重试~": "登入失效,請重試~",
"联系我们": "聯繫我們",
"订单模块不存在,请在插件市场中下载": "訂單模組不存在,請在插件市場中下載",
"财务模块不存在,请在插件市场中下载": "財務模組不存在,請在插件市場中下載",
"消息模块不存在,请在插件市场中下载": "消息模組不存在,請在插件市場中下載",
"uniapp快速开发脚手架": "uniapp快速開發腳手架",
"选择语言": "選擇語言",
"插件 / 模块": "插件 / 模組",
"服务异常": "服務異常",
"请在微信浏览器中打开": "請在微信瀏覽器中打開",
"已取消支付": "已取消支付",
"支付失败": "支付失敗",
"授权信息仅用于用户登录": "授權信息僅用於用戶登錄",
"登录授权失败": "登錄授權失敗",
"获取短信验证码": "獲取短信驗證碼",
"验证码": "驗證碼",
"发送短信": "發送短信",
"{n}s后重新获取": "{n}s後重新獲取",
"获取验证码": "獲取驗證碼",
"短信已发送,请查收": "短信已發送,請查收",
"请填写验证码": "請填寫驗證碼",
"请填写正确的手机号格式": "請填寫正確的手機號格式"
}

16
main.ts

@ -0,0 +1,16 @@
import { createSSRApp } from "vue";
import { bootstrap } from "/@/cool/bootstrap";
import App from "./App.vue";
import { i18n } from "./locale";
import "./router";
export function createApp() {
const app = createSSRApp(App);
app.use(i18n);
bootstrap(app);
return {
app,
};
}

184
manifest.json

@ -0,0 +1,184 @@
{
"name" : "cool-uni",
"appid" : "__UNI__46FB202",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : 100,
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {
"VideoPlayer" : {},
"Share" : {},
"Payment" : {},
"OAuth" : {},
"Geolocation" : {},
"Camera" : {},
"Push" : {}
},
"distribute" : {
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CAPTURE_VIDEO_OUTPUT\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.STATUS_BAR\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
},
"ios" : {
"capabilities" : {
"entitlements" : {
"com.apple.developer.associated-domains" : []
}
},
"idfa" : true,
"privacyDescription" : {
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验"
},
"dSYMs" : false
},
"sdkConfigs" : {
"payment" : {
"alipay" : {
"__platform__" : [ "ios", "android" ]
},
"weixin" : {
"__platform__" : [ "ios", "android" ],
"appid" : "wx348f72db1512fa2e",
"UniversalLinks" : ""
}
},
"ad" : {},
"share" : {
"weixin" : {
"appid" : "wx348f72db1512fa2e",
"UniversalLinks" : ""
}
},
"oauth" : {
"weixin" : {
"appid" : "wx348f72db1512fa2e",
"appsecret" : "test",
"UniversalLinks" : ""
},
"apple" : {}
},
"geolocation" : {
"system" : {
"__platform__" : [ "ios", "android" ]
}
},
"push" : {
"unipush" : {
"offline" : false,
"icons" : {
"small" : {
"ldpi" : ""
}
}
}
},
"maps" : {}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
},
"splashscreen" : {
"useOriginalMsgbox" : true
}
},
"safearea" : {
"bottom" : {
"offset" : "none"
}
},
"uniStatistics" : {
"enable" : true
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wxdebc4de0b5584ca4",
"setting" : {
"urlCheck" : true,
"es6" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"h5" : {
"router" : {
"base" : "./",
"mode" : "hash"
},
"devServer" : {
"https" : false
}
},
"locale" : "zh-Hans"
}

2873
package-lock.json

File diff suppressed because it is too large

39
package.json

@ -0,0 +1,39 @@
{
"name": "cool-uni",
"version": "8.0.0",
"license": "MIT",
"scripts": {
"dev": "vite --port 9900",
"dev:h5": "vite --port 9900",
"build": "vite build",
"build:h5": "vite build",
"build:mp-weixin": "vite build --mode mp-weixin"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3081220230817001",
"@hyoga/uni-socket.io": "3.0.4",
"dayjs": "^1.11.13",
"js-pinyin": "^0.2.5",
"lodash-es": "^4.17.21",
"md5": "^2.3.0",
"pinia": "^2.1.7",
"vue": "^3.5.13",
"vue-i18n": "9.1.9",
"weixin-js-sdk": "^1.6.5"
},
"engines": {
"node": ">= 16"
},
"devDependencies": {
"@cool-vue/vite-plugin": "^8.0.3",
"@dcloudio/types": "^3.4.14",
"@dcloudio/uni-app-vite": "^3.0.0-3081220230817001",
"@dcloudio/vite-plugin-uni": "^3.0.0",
"@types/lodash-es": "^4.17.12",
"@types/md5": "^2.3.2",
"@types/node": "^20.11.26",
"@vue/tsconfig": "^0.5.1",
"typescript": "~5.5.4",
"vite": "^5.4.14"
}
}

417
pages.json

@ -0,0 +1,417 @@
{
"pages": [
{
"path": "pages/user/login", //
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/index/home",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/index/my",
"style": {
"navigationStyle": "custom"
}
}
],
"subPackages": [
{
"root": "uni_modules/cool-cs/pages",
"pages": [
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天"
}
}
],
"isTemp": true
},
{
"root": "pages/user",
"pages": [
{
"path": "doc",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "set",
"style": {
"navigationBarTitleText": "%page.设置%"
}
},
{
"path": "edit",
"style": {
"navigationBarTitleText": "%page.编辑%"
}
},
// {
// "path": "login",
// "style": {
// "navigationStyle": "custom"
// }
// },
{
"path": "captcha",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "about",
"style": {
"navigationBarTitleText": "%page.关于我们%"
}
}
]
}
// {
// "root": "pages/demo",
// "pages": [
// {
// "path": "basic/button",
// "style": {
// "navigationBarTitleText": "Button 按钮"
// }
// },
// {
// "path": "basic/text",
// "style": {
// "navigationBarTitleText": "Text 文本"
// }
// },
// {
// "path": "basic/image",
// "style": {
// "navigationBarTitleText": "Image 图片"
// }
// },
// {
// "path": "basic/icon",
// "style": {
// "navigationBarTitleText": "Icon 图标"
// }
// },
// {
// "path": "basic/tag",
// "style": {
// "navigationBarTitleText": "Tag 标签"
// }
// },
// {
// "path": "basic/toast",
// "style": {
// "navigationBarTitleText": "Toast 提示"
// }
// },
// {
// "path": "basic/loading",
// "style": {
// "navigationBarTitleText": "Loading 加载"
// }
// },
// {
// "path": "view/flex",
// "style": {
// "navigationBarTitleText": "Flex 弹性"
// }
// },
// {
// "path": "view/grid",
// "style": {
// "navigationBarTitleText": "Grid 宫格"
// }
// },
// {
// "path": "view/divider",
// "style": {
// "navigationBarTitleText": "Divider 分割符"
// }
// },
// {
// "path": "view/avatar",
// "style": {
// "navigationBarTitleText": "Avatar 头像"
// }
// },
// {
// "path": "view/badge",
// "style": {
// "navigationBarTitleText": "Badge 角标"
// }
// },
// {
// "path": "view/loadmore",
// "style": {
// "navigationBarTitleText": "Loadmore 加载更多"
// }
// },
// {
// "path": "view/noticebar",
// "style": {
// "navigationBarTitleText": "Noticebar 通知栏"
// }
// },
// {
// "path": "view/countdown",
// "style": {
// "navigationBarTitleText": "Countdown 倒计时"
// }
// },
// {
// "path": "view/popup",
// "style": {
// "navigationBarTitleText": "Popup 弹出框"
// }
// },
// {
// "path": "view/progress",
// "style": {
// "navigationBarTitleText": "Progress 进度条"
// }
// },
// {
// "path": "view/search",
// "style": {
// "navigationBarTitleText": "Search 搜索框"
// }
// },
// {
// "path": "view/slider",
// "style": {
// "navigationBarTitleText": "Slider 滑块"
// }
// },
// {
// "path": "view/tabs",
// "style": {
// "navigationBarTitleText": "Tabs 选项卡"
// }
// },
// {
// "path": "view/timeline",
// "style": {
// "navigationBarTitleText": "Timeline 时间线"
// }
// },
// {
// "path": "view/topbar",
// "style": {
// "navigationBarTitleText": "Topbar 顶部导航栏",
// "navigationStyle": "custom"
// }
// },
// {
// "path": "view/waterfall",
// "style": {
// "navigationBarTitleText": "Waterfall 瀑布流",
// "enablePullDownRefresh": true
// }
// },
// {
// "path": "view/banner",
// "style": {
// "navigationBarTitleText": "Banner 轮博图"
// }
// },
// {
// "path": "view/card",
// "style": {
// "navigationBarTitleText": "Card 卡片"
// }
// },
// {
// "path": "view/list",
// "style": {
// "navigationBarTitleText": "List 列表"
// }
// },
// {
// "path": "view/list-index",
// "style": {
// "navigationBarTitleText": "ListIndex 索引列表"
// }
// },
// {
// "path": "view/scroller",
// "style": {
// "navigationBarTitleText": "Scroller 滚动条"
// }
// },
// {
// "path": "view/skeleton",
// "style": {
// "navigationBarTitleText": "Skeleton 骨架图"
// }
// },
// {
// "path": "form/input",
// "style": {
// "navigationBarTitleText": "Input 输入框"
// }
// },
// {
// "path": "form/input-number",
// "style": {
// "navigationBarTitleText": "InputNumber 计数器"
// }
// },
// {
// "path": "form/textarea",
// "style": {
// "navigationBarTitleText": "Textarea 文本域"
// }
// },
// {
// "path": "form/checkbox",
// "style": {
// "navigationBarTitleText": "Checkbox 多选框"
// }
// },
// {
// "path": "form/radio",
// "style": {
// "navigationBarTitleText": "Radio 单选框"
// }
// },
// {
// "path": "form/form",
// "style": {
// "navigationBarTitleText": "Form 表单"
// }
// },
// {
// "path": "form/select",
// "style": {
// "navigationBarTitleText": "Select 下拉框"
// }
// },
// {
// "path": "form/select-popup",
// "style": {
// "navigationBarTitleText": "SelectPopup 下拉框弹窗"
// }
// },
// {
// "path": "form/select-date",
// "style": {
// "navigationBarTitleText": "SelectDate 时间选择器"
// }
// },
// {
// "path": "form/select-city",
// "style": {
// "navigationBarTitleText": "SelectCity 城市选择器"
// }
// },
// {
// "path": "form/rate",
// "style": {
// "navigationBarTitleText": "Rate 评分"
// }
// },
// {
// "path": "form/switch",
// "style": {
// "navigationBarTitleText": "Switch 开关"
// }
// },
// {
// "path": "form/upload",
// "style": {
// "navigationBarTitleText": "Upload 文件上传"
// }
// },
// {
// "path": "extend/action-sheet",
// "style": {
// "navigationBarTitleText": "ActionSheet 操作菜单"
// }
// },
// {
// "path": "extend/captcha",
// "style": {
// "navigationBarTitleText": "Captcha 验证码"
// }
// },
// {
// "path": "extend/confirm",
// "style": {
// "navigationBarTitleText": "Confirm 确认框"
// }
// },
// {
// "path": "extend/dialog",
// "style": {
// "navigationBarTitleText": "Dialog 对话框"
// }
// },
// {
// "path": "extend/filter-bar",
// "style": {
// "navigationBarTitleText": "FilterBar 过滤栏"
// }
// },
// {
// "path": "extend/page",
// "style": {
// "navigationBarTitleText": "Page 页面"
// }
// },
// {
// "path": "extend/tree",
// "style": {
// "navigationBarTitleText": "Tree 树形"
// }
// },
// {
// "path": "extend/service",
// "style": {
// "navigationBarTitleText": "Service 服务"
// }
// },
// {
// "path": "extend/slider-verify",
// "style": {
// "navigationBarTitleText": "SliderVerify 图片滑动验证"
// }
// }
// ]
// }
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "cool-uni",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f6f7fa"
},
"tabBar": {
"backgroundColor": "#ffffff",
"borderStyle": "white",
"height": 0,
"list": [
{
"pagePath": "pages/index/home",
"iconPath": "static/icon/tabbar/home.png",
"selectedIconPath": "static/icon/tabbar/home2.png",
"text": "%演示%",
"visible": false
},
{
"pagePath": "pages/index/my",
"iconPath": "static/icon/tabbar/my.png",
"selectedIconPath": "static/icon/tabbar/my2.png",
"text": "%我的%",
"visible": false
}
]
}
}

140
pages/demo/basic/button.vue

@ -0,0 +1,140 @@
<template>
<cl-page :padding="20">
<view class="page-demo-button">
<cl-card label="基础用法">
<cl-row :gutter="20">
<cl-col :span="8">
<cl-button fill @tap="onTap">默认</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="primary" fill>主要</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="success" fill>成功</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="error" fill>失败</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="warning" fill>警告</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="info" fill>信息</cl-button>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="朴素">
<cl-row :gutter="20">
<cl-col :span="8">
<cl-button type="primary" plain fill>主要</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="success" plain fill>成功</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="error" plain fill>失败</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="warning" plain fill>警告</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="info" plain fill>信息</cl-button>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="不同尺寸">
<cl-button size="small"></cl-button>
<cl-button size="default">默认</cl-button>
<cl-button size="large"></cl-button>
<cl-button :height="90" :width="200" :font-size="34">90*200</cl-button>
</cl-card>
<cl-card label="图标">
<cl-button icon="cl-icon-search">搜索</cl-button>
<cl-button icon="cl-icon-chart-bar">统计</cl-button>
</cl-card>
<cl-card label="圆角">
<cl-button type="primary" round>默认</cl-button>
<cl-button round>默认</cl-button>
</cl-card>
<cl-card label="加载中">
<cl-row :gutter="20">
<cl-col :span="8">
<cl-button loading fill @tap="onTap">默认</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="primary" fill loading>主要</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="success" fill loading>成功</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="error" fill loading>失败</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="warning" fill loading>警告</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="info" fill loading>信息</cl-button>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="禁用">
<cl-row :gutter="20">
<cl-col :span="8">
<cl-button disabled fill @tap="onTap">默认</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="primary" fill disabled>主要</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="success" fill disabled>成功</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="error" fill disabled>失败</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="warning" fill disabled>警告</cl-button>
</cl-col>
<cl-col :span="8">
<cl-button type="info" fill disabled>信息</cl-button>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="没有边框">
<cl-button :border="false">默认</cl-button>
</cl-card>
<cl-card label="自定义颜色">
<cl-button :border="false" color="#ffffff" background-color="#008000"
>默认</cl-button
>
<cl-button color="#008000" plain>朴素</cl-button>
</cl-card>
</view>
</cl-page>
</template>
<script lang="ts" setup>
import { useUi } from "/$/cool-ui";
const ui = useUi();
function onTap() {
ui.showToast("点我干嘛");
}
</script>
<style lang="scss">
.page-demo-button {
.cl-col {
padding-bottom: 20rpx;
}
}
</style>

210
pages/demo/basic/icon.vue

@ -0,0 +1,210 @@
<template>
<cl-page :padding="20">
<cl-card label="尺寸">
<cl-icon name="like"></cl-icon>
<cl-icon name="like" :size="40"></cl-icon>
<cl-icon name="like" :size="50"></cl-icon>
</cl-card>
<cl-card label="颜色">
<cl-icon name="like" color="primary"></cl-icon>
<cl-icon name="like" color="error"></cl-icon>
<cl-icon name="like" color="success"></cl-icon>
</cl-card>
<cl-card label="方向">
<cl-grid :column="4">
<cl-grid-item v-for="item in arrow" :key="item">
<view class="item" @tap="copy(item)">
<cl-icon :name="item" :size="44"></cl-icon>
<cl-text :value="item" align="center" :margin="[15, 10, 0, 10]" />
</view>
</cl-grid-item>
</cl-grid>
</cl-card>
<cl-card label="操作">
<cl-grid :column="4">
<cl-grid-item v-for="item in op" :key="item">
<view class="item" @tap="copy(item)">
<cl-icon :name="item" :size="44"></cl-icon>
<cl-text :value="item" align="center" :margin="[15, 10, 0, 10]" />
</view>
</cl-grid-item>
</cl-grid>
</cl-card>
<cl-card label="展示">
<cl-grid :column="4">
<cl-grid-item v-for="item in dis" :key="item">
<view class="item" @tap="copy(item)">
<cl-icon :name="item" :size="44"></cl-icon>
<cl-text :value="item" align="center" :margin="[15, 10, 0, 10]" />
</view>
</cl-grid-item>
</cl-grid>
</cl-card>
<cl-card label="圆形">
<cl-grid :column="4">
<cl-grid-item v-for="item in circular" :key="item">
<view class="item" @tap="copy(item)">
<cl-icon :name="item" :size="44"></cl-icon>
<cl-text :value="item" align="center" :margin="[15, 10, 0, 10]" />
</view>
</cl-grid-item>
</cl-grid>
</cl-card>
<cl-card label="其他">
<cl-grid :column="4">
<cl-grid-item v-for="item in list" :key="item">
<view class="item" @tap="copy(item)">
<cl-icon :name="item" :size="44"></cl-icon>
<cl-text :value="item" align="center" :margin="[15, 10, 0, 10]" />
</view>
</cl-grid-item>
</cl-grid>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const arrow = ref([
"top",
"back-top",
"upgrade",
"",
"back",
"enter",
"",
"",
"arrow-double-left",
"arrow-double-right",
"",
"",
"arrow-bottom",
"arrow-top",
"arrow-left",
"arrow-right",
"caret-bottom",
"caret-top",
]);
const op = ref([
"plus",
"minus",
"plus-border",
"minus-border",
"enlarge",
"shrink",
"",
"",
"check",
"close",
"success-fill",
"delete-fill",
"toast-success",
"toast-error",
"",
"",
"cloud-download",
"cloud-upload",
"delete",
"stop",
]);
const dis = ref([
"meh",
"meh-fill",
"cry",
"cry-fill",
"smile",
"smile-fill",
"like",
"like-fill",
"location",
"location-fill",
"notification",
"notification-fill",
"eye-open",
"eye-close",
"favor-fill",
"good-fill",
]);
const circular = ref([
"chart-pie",
"play",
"more",
"keyboard-9",
"keyboard-26",
"time",
"refresh",
"check-border",
"close-border",
"help-border",
"warning-border",
"toast-waiting",
"clock-fill",
"help-fill",
"prompt-fill",
]);
const list = ref([
"exit",
"qrcode",
"msg",
"app",
"payment",
"face-auth",
"folder",
"bill",
"phone",
"share",
"link",
"rise",
"decline",
"active",
"cropper",
"fullscreen",
"chart-bar",
"set",
"map",
"calendar",
"customer-service",
"edit",
"scan",
"pay",
"image",
"search",
"toast-warning",
"history-fill",
"amount-fill",
"wallet-fill",
"doc-fill",
"camera-fill",
"mail-fill",
"bank-card-fill",
"relay-fill",
"comment-fill",
]);
function copy(name: string) {
uni.setClipboardData({
data: name,
});
}
</script>
<style lang="scss" scoped>
.item {
display: flex;
flex-direction: column;
align-items: center;
height: 150rpx;
padding-top: 20rpx;
}
</style>

83
pages/demo/basic/image.vue

@ -0,0 +1,83 @@
<template>
<cl-page :padding="20">
<cl-card label="裁剪模式">
<cl-row :gutter="20">
<cl-col :span="6">
<cl-image src="/pages/demo/static/bg1.png" :size="140" mode="scaleToFill" />
<cl-text value="scaleToFill"></cl-text>
</cl-col>
<cl-col :span="6">
<cl-image src="/pages/demo/static/bg1.png" :size="140" mode="aspectFit" />
<cl-text value="aspectFit"></cl-text>
</cl-col>
<cl-col :span="6">
<cl-image src="/pages/demo/static/bg1.png" :size="140" mode="aspectFill" />
<cl-text value="aspectFill"></cl-text>
</cl-col>
<cl-col :span="6">
<cl-image src="/pages/demo/static/bg1.png" :size="140" mode="widthFix" />
<cl-text value="widthFix"></cl-text>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="圆角">
<cl-row type="flex">
<cl-col :span="6">
<cl-image src="/pages/demo/static/avatar1.png" :size="140" round />
</cl-col>
<cl-col :span="6">
<cl-image src="/pages/demo/static/avatar1.png" :size="140" :radius="16" />
</cl-col>
</cl-row>
</cl-card>
<cl-card label="自定义大小">
<cl-row type="flex">
<cl-image
src="/pages/demo/static/avatar1.png"
:size="[150, 280]"
mode="aspectFill"
/>
</cl-row>
</cl-card>
<cl-card label="点击预览">
<cl-row type="flex">
<cl-image
src="/pages/demo/static/bg1.png"
:size="140"
:preview-list="previewList"
/>
</cl-row>
</cl-card>
<cl-card label="插槽:地址为空">
<cl-row type="flex">
<cl-image src="" :size="150" />
<cl-image src="" :size="150" :margin="[0, 0, 0, 20]">
<template #placeholder>
<cl-text color="red" value="自定义"></cl-text>
</template>
</cl-image>
</cl-row>
</cl-card>
<cl-card label="插槽:加载错误">
<cl-image src="https://xxxx.png" :size="150" />
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import Bg from "/pages/demo/static/bg1.png";
import Avatar from "/pages/demo/static/avatar1.png";
const previewList = ref([Bg, Avatar]);
</script>

52
pages/demo/basic/loading.vue

@ -0,0 +1,52 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-row>
<cl-col :span="4">
<cl-loading></cl-loading>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="不同大小">
<cl-row>
<cl-col :span="4">
<cl-loading></cl-loading>
</cl-col>
<cl-col :span="4">
<cl-loading :size="60"></cl-loading>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="加载区域">
<cl-loading-mask loading>
<view class="space">
<cl-form>
<cl-form-item label="昵称">
<cl-input />
</cl-form-item>
<cl-form-item label="性别">
<cl-select />
</cl-form-item>
</cl-form>
</view>
</cl-loading-mask>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.space {
font-size: 28rpx;
background-color: #f6f7fa;
position: relative;
padding: 20rpx;
box-sizing: border-box;
border-radius: 16rpx;
}
</style>

36
pages/demo/basic/tag.vue

@ -0,0 +1,36 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-tag>默认</cl-tag>
</cl-card>
<cl-card label="圆角">
<cl-tag round>默认</cl-tag>
</cl-card>
<cl-card label="尺寸">
<cl-tag size="large" :margin="[0, 20, 0, 0]">大尺寸</cl-tag>
<cl-tag size="default" :margin="[0, 20, 0, 0]">默认尺寸</cl-tag>
<cl-tag size="small">小尺寸</cl-tag>
</cl-card>
<cl-card label="标签颜色">
<cl-tag type="primary" :margin="[0, 20, 0, 0]">主要</cl-tag>
<cl-tag type="success" :margin="[0, 20, 0, 0]">成功</cl-tag>
<cl-tag type="error" :margin="[0, 20, 0, 0]">失败</cl-tag>
<cl-tag type="warning" :margin="[0, 20, 0, 0]">警告</cl-tag>
<cl-tag type="info" :margin="[0, 20, 0, 0]">信息</cl-tag>
</cl-card>
<cl-card label="自定义颜色">
<cl-tag color="#626aef">标签</cl-tag>
</cl-card>
<cl-card label="其他">
<cl-tag plain :margin="[0, 20, 0, 0]">镂空</cl-tag>
<cl-tag closable>可移除标签</cl-tag>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup></script>

76
pages/demo/basic/text.vue

@ -0,0 +1,76 @@
<template>
<cl-page :padding="20">
<view class="page-demo-text">
<cl-card label="基础用法">
<cl-text value="云想衣裳花想容,春风拂槛露华浓。" />
</cl-card>
<cl-card label="不同颜色">
<cl-row>
<cl-col :span="8">
<cl-text value="主色" color="primary" />
</cl-col>
<cl-col :span="8">
<cl-text value="成功" color="success" />
</cl-col>
<cl-col :span="8">
<cl-text value="错误" color="error" />
</cl-col>
<cl-col :span="8">
<cl-text value="警告" color="warning" />
</cl-col>
<cl-col :span="8">
<cl-text value="信息" color="info" />
</cl-col>
<cl-col :span="8">
<cl-text value="自定义颜色" color="#628bca" />
</cl-col>
</cl-row>
</cl-card>
<cl-card label="金额">
<cl-text type="price" :size="40" value="19450" />
</cl-card>
<cl-card label="手机号">
<cl-text type="phone" value="17605043035" />
</cl-card>
<cl-card label="超出省略">
<cl-text
:ellipsis="2"
:line-height="1.4"
value="锦瑟无端五十弦,一弦一柱思华年。庄生晓梦迷蝴蝶,望帝春心托杜鹃。沧海月明珠有泪,蓝田日暖玉生烟。此情可待成追忆,只是当时已惘然。"
/>
</cl-card>
<cl-card label="图标">
<cl-row>
<cl-col :span="12">
<cl-text prefix-icon="cl-icon-search" value="请填写名称" />
</cl-col>
<cl-col :span="12">
<cl-text suffix-icon="cl-icon-time" value="2022-02-06" />
</cl-col>
</cl-row>
</cl-card>
<cl-card label="自定义">
<cl-text
color="green"
:size="30"
:margin="[20, 0, 20, 0]"
value="颜色(green)、大小(30)、间距(20)"
/>
</cl-card>
</view>
</cl-page>
</template>
<script lang="ts" setup></script>

62
pages/demo/basic/toast.vue

@ -0,0 +1,62 @@
<template>
<cl-page :padding="20">
<cl-toast ref="Toast"></cl-toast>
<cl-card label="基础用法">
<cl-button @tap="open()">默认</cl-button>
</cl-card>
<cl-card label="不同位置">
<cl-button @tap="open()">默认</cl-button>
<cl-button @tap="open({ position: 'top' })">顶部</cl-button>
<cl-button @tap="open({ position: 'center' })">中间</cl-button>
<cl-button @tap="open({ position: 'bottom' })">底部</cl-button>
</cl-card>
<cl-card label="不同类型">
<cl-button @tap="open({ type: 'success' })">成功</cl-button>
<cl-button @tap="open({ type: 'error' })">失败</cl-button>
<cl-button @tap="open({ type: 'warning' })">警告</cl-button>
<cl-button @tap="open({ type: 'info' })">信息</cl-button>
</cl-card>
<cl-card label="其他">
<cl-button @tap="open({ icon: 'cl-icon-good-fill', message: '带图标' })"
>带图标</cl-button
>
<cl-button
@tap="
open({
message: '带图片',
image: {
url: '/static/logo.png',
style: {
height: '200rpx',
width: '200rpx',
borderRadius: '20rpx',
},
},
})
"
>带图片</cl-button
>
</cl-card>
<cl-card label="不同类型">
<cl-button @tap="open({ clear: true })">只显示一个</cl-button>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const Toast = ref<ClToast.Ref>();
function open(data?: any) {
Toast.value?.open({
message: "请输入收货人姓名",
...data,
});
}
</script>

92
pages/demo/extend/action-sheet.vue

@ -0,0 +1,92 @@
<template>
<cl-page :padding="20">
<cl-action-sheet ref="ActionSheet" />
<cl-card label="基础用法">
<cl-button @tap="open">打开</cl-button>
</cl-card>
<cl-card label="添加图标">
<cl-button @tap="open2">打开</cl-button>
</cl-card>
<cl-card label="禁用">
<cl-button @tap="open3">打开</cl-button>
</cl-card>
<cl-card label="关闭回调">
<cl-button @tap="open4">打开</cl-button>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useUi } from "/$/cool-ui";
const ui = useUi();
const ActionSheet = ref<ClActionSheet.Ref>();
function open() {
ActionSheet.value?.open({
list: [
{
label: "删除好友",
},
],
});
}
function open2() {
ActionSheet.value?.open({
list: [
{
label: "微信支付",
icon: "cl-icon-payment",
},
],
});
}
function open3() {
ActionSheet.value?.open({
title: "删除好友会同时删除所有聊天记录",
list: [
{
label: "删除好友",
color: "red",
},
],
});
}
function open4() {
ActionSheet.value?.open({
closeOnClickModal: false,
list: [
{
label: "删除好友",
color: "red",
},
],
beforeClose(index, done) {
if (index == 0) {
ui.showConfirm({
title: "提示",
message: "是否删除该联系人",
callback(action) {
if (action == "confirm") {
ui.showToast("删除成功");
}
done();
},
});
} else {
done();
}
},
});
}
</script>

28
pages/demo/extend/captcha.vue

@ -0,0 +1,28 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-captcha focus></cl-captcha>
</cl-card>
<cl-card label="其他数量">
<cl-captcha :len="6" :gutter="10" :height="100"></cl-captcha>
</cl-card>
<cl-card label="自动完成">
<cl-captcha v-model="val" @done="onDone"></cl-captcha>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useUi } from "/$/cool-ui";
const ui = useUi();
const val = ref("");
function onDone() {
ui.showToast(val.value);
}
</script>

103
pages/demo/extend/confirm.vue

@ -0,0 +1,103 @@
<template>
<cl-page :padding="20">
<cl-confirm ref="Confirm"> </cl-confirm>
<cl-confirm ref="Confirm2">
<cl-input />
</cl-confirm>
<cl-confirm ref="Confirm3"> </cl-confirm>
<cl-confirm ref="Confirm4"> </cl-confirm>
<cl-card label="基础用法">
<cl-button @tap="open">打开</cl-button>
</cl-card>
<cl-card label="自定义内容">
<cl-button @tap="open2">打开</cl-button>
</cl-card>
<cl-card label="关闭回掉">
<cl-button @tap="open3">打开</cl-button>
</cl-card>
<cl-card label="不同类型">
<cl-button @tap="open4('success')">成功</cl-button>
<cl-button @tap="open4('warning')">警告</cl-button>
<cl-button @tap="open4('error')">错误</cl-button>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useUi } from "/$/cool-ui";
const ui = useUi();
const Confirm = ref<ClConfirm.Ref>();
const Confirm2 = ref<ClConfirm.Ref>();
const Confirm3 = ref<ClConfirm.Ref>();
const Confirm4 = ref<ClConfirm.Ref>();
function open() {
Confirm.value?.open({
title: "提示",
message: "你有一个待取信件",
callback(action) {
console.log(action);
switch (action) {
case "confirm":
ui.showToast("领取成功");
break;
case "cancel":
ui.showToast("已取消");
break;
case "close":
ui.showToast("已关闭");
break;
}
},
});
}
function open2() {
Confirm2.value?.open({
title: "提示",
});
}
function open3() {
Confirm3.value?.open({
title: "提示",
message: "你有一个待取信件",
beforeClose(action, { done, showLoading, hideLoading }) {
console.log(action);
if (action == "confirm") {
showLoading();
setTimeout(() => {
done();
ui.showToast("领取成功");
}, 1500);
} else {
done();
}
},
});
}
function open4(type: "success" | "warning" | "error") {
Confirm4.value?.open({
title: "提示",
message: "这是一条消息",
type,
showCancelButton: false,
});
}
</script>

44
pages/demo/extend/dialog.vue

@ -0,0 +1,44 @@
<template>
<cl-page :padding="20">
<cl-dialog title="标题" v-model="visible">
<text>云想衣裳花想容春风拂槛露华浓若非群玉山头见会向瑶台月下逢</text>
</cl-dialog>
<cl-dialog :ref="setRefs('d1')">
<text>云想衣裳花想容春风拂槛露华浓若非群玉山头见会向瑶台月下逢</text>
</cl-dialog>
<cl-dialog :ref="setRefs('d2')" title="标题">
<text>云想衣裳花想容春风拂槛露华浓若非群玉山头见会向瑶台月下逢</text>
<template #footer>
<cl-button @tap="refs.d2?.close()">好诗</cl-button>
</template>
</cl-dialog>
<cl-card label="基础用法">
<cl-button @tap="open">打开</cl-button>
</cl-card>
<cl-card label="无标题">
<cl-button @tap="refs.d1?.open()">打开</cl-button>
</cl-card>
<cl-card label="带按钮">
<cl-button @tap="refs.d2?.open()">打开</cl-button>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useCool } from "/@/cool";
const { refs, setRefs } = useCool();
const visible = ref(false);
function open() {
visible.value = true;
}
</script>

112
pages/demo/extend/filter-bar.vue

@ -0,0 +1,112 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-filter-bar v-model="filter" :num="4">
<cl-filter-item label="排序" prop="order" type="order"></cl-filter-item>
<cl-filter-item label="开关" prop="switch" type="switch"></cl-filter-item>
</cl-filter-bar>
</cl-card>
<cl-card label="展开列表">
<cl-filter-bar v-model="filter">
<cl-filter-item
label="多选"
prop="d1"
type="dropdown"
multiple
:options="[
{
label: 'A',
value: 1,
},
{
label: 'B',
value: 2,
},
]"
></cl-filter-item>
<cl-filter-item
label="单选"
prop="d2"
type="dropdown"
:options="[
{
label: 'A',
value: 1,
},
{
label: 'B',
value: 2,
},
]"
></cl-filter-item>
</cl-filter-bar>
</cl-card>
<cl-card label="展开宫格">
<cl-filter-bar v-model="filter">
<cl-filter-item
label="多选"
prop="d1"
type="dropdown"
multiple
:options="[
{
label: 'A',
value: 1,
},
{
label: 'B',
value: 2,
},
]"
theme="grid"
></cl-filter-item>
<cl-filter-item
label="单选"
prop="d2"
type="dropdown"
:options="[
{
label: 'A',
value: 1,
},
{
label: 'B',
value: 2,
},
]"
theme="grid"
></cl-filter-item>
</cl-filter-bar>
</cl-card>
<cl-card label="展开自定义">
<cl-filter-bar v-model="filter">
<cl-filter-item label="输入框" prop="input" type="dropdown">
<template #dropdown>
<view class="cs" @tap.stop>
<cl-input />
<cl-button :margin="[0, 0, 0, 20]">搜索</cl-button>
</view>
</template>
</cl-filter-item>
</cl-filter-bar>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const filter = ref({
d1: [],
});
</script>
<style lang="scss" scoped>
.cs {
display: flex;
padding: 20rpx;
}
</style>

69
pages/demo/extend/page.vue

@ -0,0 +1,69 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-text
value="由于 uniapp 限制,cl-page 组件可作为全局组件使用"
block
color="warning"
:margin="[0, 0, 30, 0]"
/>
<cl-row>
<cl-button @tap="openToast">提示框</cl-button>
<cl-button @tap="openConfirm">确认框</cl-button>
<cl-button @tap="openLoading">加载框</cl-button>
</cl-row>
</cl-card>
<cl-card label="主题示例">
<cl-text
value="开发时,请根据设计图自行调整"
block
color="warning"
:margin="[0, 0, 30, 0]"
/>
<cl-row>
<cl-button @tap="setTheme('default')">默认</cl-button>
<cl-button @tap="setTheme('grey')">灰色</cl-button>
</cl-row>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { useUi } from "/$/cool-ui";
import { useApp, useCool } from "/@/cool";
const { router } = useCool();
const ui = useUi();
const app = useApp();
function setTheme(name: string) {
app.theme.set(name);
router.push("/pages/demo/form/form");
}
function openToast() {
ui.showToast("cool-cli");
}
function openConfirm() {
ui.showConfirm({
title: "提示",
type: "warning",
message: "是否要删除该联系人?",
});
}
function openLoading() {
ui.showLoading({
text: "2秒后关闭",
border: false,
});
setTimeout(() => {
ui.hideLoading();
}, 2000);
}
</script>

74
pages/demo/extend/service.vue

@ -0,0 +1,74 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-service :service="get()" :mask="{ text: '获取数据中' }">
<template #default="{ data }">
<view class="count">
<view class="item">
<cl-icon name="favor-fill" :size="40" :margin="[0, 0, 20, 0]"></cl-icon>
<cl-text>{{ data?.star || 0 }}</cl-text>
</view>
<view class="item">
<cl-icon name="chart-pie" :size="40" :margin="[0, 0, 20, 0]"></cl-icon>
<cl-text>{{ data?.count || 0 }}</cl-text>
</view>
<view class="item">
<cl-icon
name="wallet-fill"
:size="40"
:margin="[0, 0, 20, 0]"
></cl-icon>
<cl-text>{{ data?.wallet || 0 }}</cl-text>
</view>
</view>
</template>
</cl-service>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
function get() {
// 使
// return service.xxx.count()
// service
return new Promise((resolve) => {
setTimeout(() => {
resolve({
star: Math.ceil(Math.random() * 1000),
count: Math.ceil(Math.random() * 1000),
wallet: Math.ceil(Math.random() * 10000),
});
}, 1500);
});
}
</script>
<style lang="scss" scoped>
.count {
display: flex;
padding: 20rpx 0;
.item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text {
&:first-child {
font-size: 42rpx;
}
&:last-child {
font-size: 30rpx;
margin-top: 20rpx;
}
}
}
}
</style>

28
pages/demo/extend/slider-verify.vue

@ -0,0 +1,28 @@
<template>
<cl-page :padding="20">
<view class="page">
<cl-card label="拖动验证">
<cl-button @tap="refs.sliderVerify?.open()">打开</cl-button>
<cl-slider-verify title="拖动验证" :image="Bg1" :ref="setRefs('sliderVerify')" />
</cl-card>
<cl-card label="旋转验证">
<cl-button @tap="refs.sliderVerify2?.open()">打开</cl-button>
<cl-slider-verify
title="旋转验证"
type="rotate"
:image="Avatar4"
:ref="setRefs('sliderVerify2')"
/>
</cl-card>
</view>
</cl-page>
</template>
<script setup lang="ts">
import { useCool } from "/@/cool";
import Bg1 from "../static/bg1.png";
import Avatar4 from "../static/avatar4.png";
const { refs, setRefs } = useCool();
</script>

150
pages/demo/extend/tree.vue

@ -0,0 +1,150 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-row :margin="[0, 0, 10, 0]">
<cl-tag>{{ refs.tree?.label }}</cl-tag>
</cl-row>
<cl-tag type="error">{{ value }}</cl-tag>
<view class="bor">
<cl-tree :ref="setRefs('tree')" v-model="value" :data="data"> </cl-tree>
</view>
</cl-card>
<cl-card label="多选">
<cl-row :margin="[0, 0, 10, 0]">
<cl-tag>{{ refs.treeMultiple?.label }}</cl-tag>
</cl-row>
<cl-tag type="error">{{ value2 }}</cl-tag>
<view class="bor">
<cl-tree multiple :ref="setRefs('treeMultiple')" v-model="value2" :data="data" />
</view>
</cl-card>
<cl-card label="弹出框选择">
<cl-list-item :arrow-icon="false" label="节点选择" @tap="refs.treeSelect?.open">
<cl-text :value="refs.treeSelect?.label || '请选择'" :ellipsis="1" />
</cl-list-item>
<cl-tree-select
:ref="setRefs('treeSelect')"
v-model="value3"
:data="data"
:show-picker="false"
/>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useCool } from "/@/cool";
const { refs, setRefs } = useCool();
const value = ref(3);
const value2 = ref([12, 3]);
const value3 = ref();
const data = ref([
{
label: "Level one 1",
value: 1,
children: [
{
label: "Level two 1-1",
value: 2,
children: [
{
label: "Level three 1-1-1",
value: 3,
},
],
},
],
},
{
label: "Level one 2",
value: 4,
children: [
{
label: "Level two 2-1",
value: 5,
children: [
{
label: "Level three 2-1-1",
value: 6,
children: [
{
label: "Level four 2-1-1-1",
value: 14,
children: [
{
label: "Level five 2-1-1-1-1",
value: 16,
},
],
},
{
label: "Level four 2-1-1-2",
value: 15,
children: [
{
label: "Level five 2-1-1-2-1",
value: 17,
},
],
},
],
},
],
},
{
label: "Level two 2-2",
value: 7,
},
],
},
{
label: "Level one 3",
value: 8,
children: [
{
label: "Level two 3-1",
value: 9,
children: [
{
label: "Level three 3-1-1",
value: 10,
},
],
},
{
label: "Level two 3-2",
value: 11,
children: [
{
label: "Level three 3-2-1",
value: 12,
},
],
},
],
},
{
label: "Level one 4",
value: 13,
},
]);
</script>
<style lang="scss" scoped>
.bor {
border: $cl-border-width solid #eee;
border-radius: 16rpx;
margin-top: 20rpx;
padding: 10rpx;
}
</style>

111
pages/demo/form/checkbox.vue

@ -0,0 +1,111 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-checkbox-group v-model="v0">
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox label="2">汉堡</cl-checkbox>
<cl-checkbox label="3">薯条</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="圆角">
<cl-checkbox-group v-model="v1" round>
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="禁用">
<cl-checkbox-group v-model="v2">
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox disabled label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="开关">
<cl-checkbox v-model="v3">已阅读并同意用户协议隐私政策</cl-checkbox>
</cl-card>
<cl-card label="边框">
<cl-checkbox-group v-model="v4">
<cl-checkbox border label="1">炸鸡</cl-checkbox>
<cl-checkbox border label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="边框填充">
<cl-checkbox-group v-model="v5" fill border>
<cl-checkbox label="1">
<cl-text align="right" block>鸡米花靠右</cl-text>
</cl-checkbox>
<cl-checkbox label="2">可乐</cl-checkbox>
<cl-checkbox label="3">蛋挞</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="只显示文字">
<cl-checkbox-group v-model="v6" text border>
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="带背景">
<cl-checkbox-group v-model="v6" text border bg>
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="边框圆角">
<cl-checkbox-group v-model="v7" round border>
<cl-checkbox label="1">炸鸡</cl-checkbox>
<cl-checkbox label="2">汉堡</cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="文字贼多">
<cl-checkbox-group v-model="v8">
<cl-checkbox label="1">
汉堡可乐鸡肉卷鸡排鸡腿蛋挞薯条鸡翅原味鸡
</cl-checkbox>
<cl-checkbox label="2"> 鸡块鸡米发鸡腿堡 </cl-checkbox>
</cl-checkbox-group>
</cl-card>
<cl-card label="自定义图标">
<cl-checkbox-group v-model="v9">
<cl-checkbox
v-for="(item, index) in ['汉堡', '可乐', '薯条']"
:key="index"
:label="index"
>
<template #icon="{ checked }">
<cl-icon
name="like-fill"
:size="36"
:color="checked ? 'primary' : 'info'"
/>
</template>
{{ item }}
</cl-checkbox>
</cl-checkbox-group>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v0 = ref(["1"]);
const v1 = ref(["2"]);
const v2 = ref([]);
const v3 = ref(true);
const v4 = ref(["1"]);
const v5 = ref(["1", "2"]);
const v6 = ref(["1", "2"]);
const v7 = ref(["1", "2"]);
const v8 = ref(["1", "2"]);
const v9 = ref([]);
</script>

186
pages/demo/form/form.vue

@ -0,0 +1,186 @@
<template>
<cl-page :padding="20">
<view class="page">
<cl-card label="基础用法">
<cl-form
ref="Form"
v-model="form"
:rules="rules"
:disabled="loading"
label-position="left"
>
<cl-form-item label="活动名称" prop="name">
<cl-input v-model="form.name" placeholder="请填写活动名称"></cl-input>
</cl-form-item>
<cl-form-item label="活动时间" prop="date">
<cl-select-date v-model="form.date"></cl-select-date>
</cl-form-item>
<cl-form-item label="活动类型" prop="type">
<cl-select v-model="form.type" :options="options.type"></cl-select>
</cl-form-item>
<cl-form-item label="活动人数" prop="num" justify="end">
<cl-input-number v-model="form.num" :min="1" :max="100"></cl-input-number>
</cl-form-item>
<cl-form-item label="活动区域" prop="area" label-position="top">
<cl-checkbox-group v-model="form.area">
<cl-checkbox
v-for="(item, index) in options.area"
:key="index"
:label="item.value"
>
{{ item.label }}
</cl-checkbox>
</cl-checkbox-group>
</cl-form-item>
<cl-form-item label="资源" prop="source" label-position="top">
<cl-radio-group v-model="form.source">
<cl-radio
v-for="(item, index) in options.source"
:key="index"
:label="item.value"
>
{{ item.label }}
</cl-radio>
</cl-radio-group>
</cl-form-item>
<cl-form-item label="活动封面" prop="cover" label-position="top">
<cl-upload v-model="form.cover" />
</cl-form-item>
<cl-form-item label="活动海报(最多上传6张)" prop="pics" label-position="top">
<cl-upload v-model="form.pics" multiple :limit="6" />
</cl-form-item>
<cl-form-item label="活动描述" prop="remark" label-position="top">
<cl-textarea v-model="form.remark" count placeholder="请填写活动描述" />
</cl-form-item>
<cl-form-item label="活动赞助商" prop="company" label-position="top">
<cl-input v-model="form.company" placeholder="请填写活动赞助商" />
<template #append>
<cl-icon name="help-border" :size="36"></cl-icon>
</template>
</cl-form-item>
</cl-form>
</cl-card>
<cl-footer border>
<cl-button size="large" round fill @tap="clear">清空</cl-button>
<cl-button size="large" round fill @tap="submit" type="success" :loading="loading"
>提交</cl-button
>
</cl-footer>
</view>
</cl-page>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { useUi } from "/$/cool-ui";
import { onPageScroll } from "@dcloudio/uni-app";
onPageScroll((e) => {});
const ui = useUi();
const form = ref({
name: "",
type: undefined,
date: undefined,
area: [0, 1],
source: 0,
num: 5,
remark: "",
cover: "",
pics: [],
company: "",
});
const rules = reactive({
name: {
required: true,
message: "活动名称不能为空",
},
type: {
required: true,
message: "活动类型不能为空",
},
date: {
required: true,
message: "活动时间不能为空",
},
cover: {
required: true,
message: "活动封面不能为空",
},
});
const options = reactive({
area: [
{
label: "一区",
value: 0,
},
{
label: "二区",
value: 1,
},
],
type: [
{
label: "线上活动",
value: 0,
},
{
label: "推广活动",
value: 1,
},
{
label: "线下活动",
value: 2,
},
],
source: [
{
label: "赞助",
value: 0,
},
{
label: "场地",
value: 1,
},
],
});
const Form = ref<ClForm.Ref>();
const loading = ref(false);
function submit() {
Form.value?.validate((valid, errors) => {
if (valid) {
loading.value = true;
setTimeout(() => {
ui.showToast("提交成功");
loading.value = false;
}, 1500);
}
});
}
function reset() {
Form.value?.reset();
}
function clear() {
Form.value?.clear();
}
</script>

25
pages/demo/form/input-number.vue

@ -0,0 +1,25 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-input-number />
</cl-card>
<cl-card label="步数">
<cl-input-number :step="500" :max="10000" />
</cl-card>
<cl-card label="禁用">
<cl-input-number disabled />
</cl-card>
<cl-card label="范围(4-10)">
<cl-input-number :max="10" :min="4" />
</cl-card>
<cl-card label="单位">
<cl-input-number unit="个" />
</cl-card>
</cl-page>
</template>
<script lang="ts"></script>

61
pages/demo/form/input.vue

@ -0,0 +1,61 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-input />
</cl-card>
<cl-card label="数字键盘">
<cl-input type="number" />
</cl-card>
<cl-card label="禁用">
<view style="margin-bottom: 20rpx">
<cl-button
size="small"
:type="disabled ? 'success' : 'error'"
@tap="disabled = !disabled"
>
{{ disabled ? "启用" : "禁用" }}
</cl-button>
</view>
<cl-input :disabled="disabled" />
</cl-card>
<cl-card label="无边框">
<cl-input :border="false" />
</cl-card>
<cl-card label="前置元素">
<cl-input>
<template #prepend>
<text>https://</text>
</template>
</cl-input>
</cl-card>
<cl-card label="后置元素">
<cl-input>
<template #append>
<text></text>
</template>
</cl-input>
</cl-card>
<cl-card label="自定义">
<cl-input
:height="80"
:padding="[0, 32, 0, 32]"
:radius="16"
background-color="#eee"
:border="false"
/>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const disabled = ref(true);
</script>

95
pages/demo/form/radio.vue

@ -0,0 +1,95 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-radio v-model="v1" label="1">炸鸡</cl-radio>
<cl-radio v-model="v1" label="2">汉堡</cl-radio>
<cl-radio v-model="v1" label="3">薯条</cl-radio>
<cl-radio v-model="v1" label="4">可乐</cl-radio>
<cl-radio v-model="v1" label="5">冰淇淋</cl-radio>
</cl-card>
<cl-card label="禁用">
<cl-radio v-model="v3" disabled label="1">炸鸡</cl-radio>
<cl-radio v-model="v3" disabled label="2">汉堡</cl-radio>
</cl-card>
<cl-card label="边框">
<cl-radio v-model="v4" border label="1">炸鸡</cl-radio>
<cl-radio v-model="v4" border label="2">汉堡</cl-radio>
</cl-card>
<cl-card label="边框填充">
<cl-radio-group v-model="v5" fill border>
<cl-radio label="1">
<cl-text align="right" block>鸡米花靠右</cl-text>
</cl-radio>
<cl-radio label="2">可乐</cl-radio>
<cl-radio label="3">蛋挞</cl-radio>
</cl-radio-group>
</cl-card>
<cl-card label="只显示文字">
<cl-radio-group v-model="v6" text border>
<cl-radio label="1">炸鸡</cl-radio>
<cl-radio label="2">汉堡</cl-radio>
</cl-radio-group>
</cl-card>
<cl-card label="带背景">
<cl-radio-group v-model="v6" text border bg>
<cl-radio label="1">炸鸡</cl-radio>
<cl-radio label="2">汉堡</cl-radio>
</cl-radio-group>
</cl-card>
<cl-card label="圆角">
<cl-radio-group v-model="v7" round border>
<cl-radio label="1">炸鸡</cl-radio>
<cl-radio label="2">汉堡</cl-radio>
</cl-radio-group>
</cl-card>
<cl-card label="文字贼多">
<cl-radio-group v-model="v8">
<cl-radio label="1">
汉堡可乐鸡肉卷鸡排鸡腿蛋挞薯条鸡翅原味鸡
</cl-radio>
<cl-radio label="2"> 鸡块鸡米发鸡腿堡 </cl-radio>
</cl-radio-group>
</cl-card>
<cl-card label="自定义图标">
<cl-radio-group v-model="v9">
<cl-radio
v-for="(item, index) in ['汉堡', '可乐', '薯条']"
:key="index"
:label="index"
>
<template #icon="{ checked }">
<cl-icon
name="like-fill"
:size="36"
:color="checked ? 'primary' : 'info'"
/>
</template>
{{ item }}
</cl-radio>
</cl-radio-group>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v1 = ref("1");
const v2 = ref("2");
const v3 = ref("2");
const v4 = ref("2");
const v5 = ref("2");
const v6 = ref("2");
const v7 = ref("1");
const v8 = ref("1");
const v9 = ref("1");
</script>

19
pages/demo/form/rate.vue

@ -0,0 +1,19 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-rate :model-value="2.7"></cl-rate>
</cl-card>
<cl-card label="禁用">
<cl-rate :model-value="2.7" disabled></cl-rate>
</cl-card>
<cl-card label="多色">
<cl-rate :model-value="2" :color="['#F56C6C', '#E6A23C', '#67C23A']"></cl-rate>
</cl-card>
<cl-card label="其他图标">
<cl-rate :model-value="1" icon="cl-icon-like"></cl-rate>
</cl-card>
</cl-page>
</template>

50
pages/demo/form/select-city.vue

@ -0,0 +1,50 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-select-city v-model="v1" />
</cl-card>
<cl-card label="省市区乡镇4级">
<cl-select-city v-model="v2" :data="pcas" placeholder="省市区县、乡镇" />
</cl-card>
<cl-card label="自定义">
<cl-select-city v-model="v3">
<template #default="{ selection }">
<cl-row>
<cl-tag
:margin="[0, 20, 20, 0]"
v-for="item in selection"
plain
round
:key="item.code"
>{{ item.name }}</cl-tag
>
</cl-row>
<cl-button round>选择所在地区</cl-button>
</template>
</cl-select-city>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { onReady } from "@dcloudio/uni-app";
const v1 = ref([]);
const v2 = ref([]);
const v3 = ref([]);
const pcas = ref();
onReady(() => {
uni.request({
url: "https://cool-service.oss-cn-shanghai.aliyuncs.com/app%2Fbase%2F8fe445f06db5463386ae940d45b4bd6c_city-pcas.json",
success(res) {
pcas.value = res.data;
},
});
});
</script>

65
pages/demo/form/select-date.vue

@ -0,0 +1,65 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-select-date v-model="v1" />
</cl-card>
<cl-card label="时-分">
<cl-select-date v-model="v2" :mode="['hour', 'minute']" format="HH:mm" />
</cl-card>
<cl-card label="年-月">
<cl-select-date v-model="v3" :mode="['year', 'month']" format="YYYY-MM" />
</cl-card>
<cl-card label="起始时间">
<cl-select-date v-model="v4" start="2023-01-01 08:00:00" end="2025-12-12 12:00:00" />
<cl-row :margin="[20, 0, 0, 0]">
<cl-text value="2023-01-01 08:00:00 < " />
<cl-text value="时间" color="red" :margin="[0, 10, 0, 10]" />
<cl-text value=" < 2025-12-12 12:00:00" />
</cl-row>
</cl-card>
<cl-card label="展示格式">
<cl-select-date
v-model="v5"
:display-format="
(date: string) => {
return date ? dayjs(date).format('YYYY年MM月DD日 HH时mm分ss秒') : '';
}
"
/>
</cl-card>
<cl-card label="自定义">
<cl-select-date v-model="v6" title="请选择预约时间">
<template #default="{ value }">
<cl-select-inner round background-color="#f7f7f7">
<cl-text
prefix-icon="cl-icon-time"
:value="
value
? dayjs(value).format('YYYY年MM月DD日 HH时mm分ss秒')
: '请选择预约时间'
"
/>
</cl-select-inner>
</template>
</cl-select-date>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import dayjs from "dayjs";
import { ref } from "vue";
const v1 = ref();
const v2 = ref();
const v3 = ref();
const v4 = ref();
const v5 = ref();
const v6 = ref();
</script>

86
pages/demo/form/select-popup.vue

@ -0,0 +1,86 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-select-popup v-model="v1" title="选择歌曲" :options="list" />
</cl-card>
<cl-card label="多选">
<cl-select-popup v-model="v2" multiple title="选择歌曲" :options="list" />
</cl-card>
<cl-card label="必填">
<cl-select-popup v-model="v3" required title="选择歌曲" :options="list" />
</cl-card>
<cl-card label="自定义">
<cl-select-popup v-model="v4" required title="选择歌曲" :options="list">
<template #item="{ item }">
{{ item }}
</template>
<template #default="{ label, value }">
<cl-select-inner round background-color="#f7f7f7">
<cl-text value="你选择了" :size="24" color="info" />
<cl-tag round size="small" :margin="[0, 0, 0, 20]">{{ label }}</cl-tag>
</cl-select-inner>
</template>
</cl-select-popup>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v1 = ref();
const v2 = ref([]);
const v3 = ref();
const v4 = ref();
const list = ref([
{
label: "冠军相",
value: 1,
},
{
label: "鬼脸",
value: 2,
},
{
label: "说唱歌手",
value: 3,
},
{
label: "一般的一天",
value: 4,
},
{
label: "会魔法的老人",
value: 5,
},
{
label: "C级浪漫",
value: 6,
},
{
label: "我想",
value: 7,
},
{
label: "小河淌水",
value: 8,
},
{
label: "午夜派对",
value: 9,
},
{
label: "POW!!!!!!!!!!!!!引爆",
value: 10,
},
{
label: "流浪`地球",
value: 11,
},
]);
</script>

92
pages/demo/form/select.vue

@ -0,0 +1,92 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-select v-model="v1" :options="list"></cl-select>
</cl-card>
<cl-card label="日期">
<cl-select v-model="v2" mode="date" fields="month"></cl-select>
</cl-card>
<cl-card label="时间">
<cl-select v-model="v3" mode="time"></cl-select>
</cl-card>
<cl-card label="禁用">
<cl-select v-model="v4" :options="list" disabled></cl-select>
</cl-card>
<cl-card label="省市区">
<cl-select-region v-model="v5"></cl-select-region>
</cl-card>
<cl-card label="自定义">
<cl-select v-model="v6" :options="list">
<template #default="{ label, value }">
<cl-select-inner round background-color="#f7f7f7">
<cl-text value="你选择了" :size="24" color="info" />
<cl-tag round size="small" :margin="[0, 0, 0, 20]">{{ label }}</cl-tag>
</cl-select-inner>
</template>
</cl-select>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v1 = ref();
const v2 = ref();
const v3 = ref();
const v4 = ref(2);
const v5 = ref([]);
const v6 = ref(2);
const list = ref([
{
label: "冠军相",
value: 1,
},
{
label: "鬼脸",
value: 2,
},
{
label: "说唱歌手",
value: 3,
},
{
label: "一般的一天",
value: 4,
},
{
label: "会魔法的老人",
value: 5,
},
{
label: "C级浪漫",
value: 6,
},
{
label: "我想",
value: 7,
},
{
label: "小河淌水",
value: 8,
},
{
label: "午夜派对",
value: 9,
},
{
label: "POW!!!!!!!!!!!!!引爆",
value: 10,
},
{
label: "流浪`地球",
value: 11,
},
]);
</script>

21
pages/demo/form/switch.vue

@ -0,0 +1,21 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-switch></cl-switch>
</cl-card>
<cl-card label="其他颜色">
<cl-switch color="red"></cl-switch>
</cl-card>
<cl-card label="禁用">
<cl-switch v-model="v3" disabled></cl-switch>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v3 = ref(true);
</script>

11
pages/demo/form/textarea.vue

@ -0,0 +1,11 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-textarea />
</cl-card>
<cl-card label="统计字数">
<cl-textarea count />
</cl-card>
</cl-page>
</template>

23
pages/demo/form/upload.vue

@ -0,0 +1,23 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-upload v-model="v1" test />
</cl-card>
<cl-card label="自定义大小">
<cl-upload :size="[200, 300]" v-model="v2" test />
</cl-card>
<cl-card label="多图">
<cl-upload multiple v-model="v3" test />
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const v1 = ref();
const v2 = ref();
const v3 = ref([]);
</script>

BIN
pages/demo/static/avatar1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
pages/demo/static/avatar2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
pages/demo/static/avatar3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
pages/demo/static/avatar4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

BIN
pages/demo/static/bg1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
pages/demo/static/bg2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
pages/demo/static/bg3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
pages/demo/static/bg4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

58
pages/demo/view/avatar.vue

@ -0,0 +1,58 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-row>
<cl-col :span="4">
<cl-avatar src="/pages/demo/static/avatar1.png"></cl-avatar>
</cl-col>
<cl-col :span="4">
<cl-avatar src="/pages/demo/static/avatar1.png" shape="square"></cl-avatar>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="占位">
<cl-row>
<cl-col :span="4">
<cl-avatar></cl-avatar>
</cl-col>
<cl-col :span="4">
<cl-avatar name="神仙" background-color="red"></cl-avatar>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="不同大小">
<cl-row>
<cl-col :span="6">
<cl-avatar :size="100" src="/pages/demo/static/avatar1.png"></cl-avatar>
</cl-col>
<cl-col :span="6">
<cl-avatar :size="120" src="/pages/demo/static/avatar2.png"></cl-avatar>
</cl-col>
<cl-col :span="6">
<cl-avatar :size="140" src="/pages/demo/static/avatar3.png"></cl-avatar>
</cl-col>
</cl-row>
</cl-card>
<cl-card label="头像组">
<cl-avatar-group :urls="urls"></cl-avatar-group>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const urls = ref([
"/pages/demo/static/avatar1.png",
"/pages/demo/static/avatar2.png",
"/pages/demo/static/avatar3.png",
"/pages/demo/static/avatar4.png",
]);
</script>

61
pages/demo/view/badge.vue

@ -0,0 +1,61 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-badge :value="16">
<cl-button>消息</cl-button>
</cl-badge>
</cl-card>
<cl-card label="自定义内容">
<cl-badge value="NEW">
<cl-button>自定义内容</cl-button>
</cl-badge>
</cl-card>
<cl-card label="自定义内容">
<cl-badge :value="132" :max="99">
<cl-button>最大</cl-button>
</cl-badge>
</cl-card>
<cl-card label="点形状">
<cl-badge :value="132" is-dot>
<cl-button>点形状</cl-button>
</cl-badge>
</cl-card>
<cl-card label="不同样式">
<cl-badge :value="132" plain>
<cl-button>不同样式</cl-button>
</cl-badge>
</cl-card>
<cl-card label="不同状态">
<cl-row>
<cl-col :span="6">
<cl-badge type="primary" is-dot>
<cl-button>主要</cl-button>
</cl-badge>
</cl-col>
<cl-col :span="6">
<cl-badge type="success" value="Hot">
<cl-button>成功</cl-button>
</cl-badge>
</cl-col>
<cl-col :span="6">
<cl-badge type="error" is-dot>
<cl-button>失败</cl-button>
</cl-badge>
</cl-col>
<cl-col :span="6">
<cl-badge type="warning" :value="20">
<cl-button>警告</cl-button>
</cl-badge>
</cl-col>
</cl-row>
</cl-card>
</cl-page>
</template>

38
pages/demo/view/banner.vue

@ -0,0 +1,38 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-banner :list="urls"></cl-banner>
</cl-card>
<cl-card label="卡片">
<cl-banner :list="urls" type="card"></cl-banner>
</cl-card>
<cl-card label="衔接">
<cl-banner :list="urls" type="chain" @select="onSelect"></cl-banner>
</cl-card>
</cl-page>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const urls = ref([
{
url: "/pages/demo/static/bg1.png",
},
{
url: "/pages/demo/static/bg2.png",
},
{
url: "/pages/demo/static/bg3.png",
},
{
url: "/pages/demo/static/bg4.png",
},
]);
function onSelect(i: number) {
console.log(i);
}
</script>

15
pages/demo/view/card.vue

@ -0,0 +1,15 @@
<template>
<cl-page :padding="20">
<cl-card label="基础用法">
<cl-text value="目光所至则心向往之" />
</cl-card>
<cl-card label="显示更多" more more-text="查看更多">
<cl-text value="被爱好似有依靠" />
</cl-card>
<cl-card label="记载中" loading>
<cl-text value="他也成为了这平淡生活里最明媚的一道光" />
</cl-card>
</cl-page>
</template>

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

Loading…
Cancel
Save