wip: 临时提交

This commit is contained in:
ray
2025-05-29 08:28:46 +08:00
parent 9fba279490
commit 8962f01b09
23 changed files with 1457 additions and 2372 deletions

View File

@@ -1,14 +1,14 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
import { useThemeStore } from "@/store";
import { useTheme } from "@/composables/useTheme";
// 主题初始化
const themeStore = useThemeStore();
// 使用主题 composable
const { initTheme } = useTheme();
onLaunch(() => {
console.log("App Launch");
// 初始化主题
themeStore.initTheme();
initTheme();
});
onShow(() => {

View File

@@ -1,6 +1,6 @@
<template>
<template v-if="tagType">
<wd-tag :type="tagType" :round="round">{{ label }}</wd-tag>
<wd-tag :type="tagType as any" :round="round">{{ label }}</wd-tag>
</template>
<template v-else>
<view>{{ label }}</view>

View File

@@ -1,196 +0,0 @@
# 1.4.2
新增
1. 新增`filterValue`属性,支持通过此关键词来搜索并筛选树结构的内容
# 1.4.1
修复
1. 修复单选 onlyRadioLeaf 时末级节点无法选中的 bug
# 1.4.0
版本调整
建议更新,但需要注意,异步数据的时候,后台需返回 leaf 字段来判断是否末项数据
1. **调整数据项格式,新增 `leaf` 字段,来判断是否为末节点**
2. **调整数据项格式,新增 `sort` 字段,来排序节点位置**
3. **注意:异步加载数据,当为末项的时候,需要服务端数据返回 `leaf` 字段**
4. 新增 `alwaysFirstLoad` ,即异步数据总会在第一次展开节点时,拉取一次后台数据,来比对是否一致
5. 拆分 `field` 属性,**注意: 1.5.0 版本后将移除 `field` 属性**
6. 新增 `labelField``field.label`,指定节点对象中某个属性为**标签**字段,默认`label`
7. 新增 `valueField``field.key`,指定节点对象中某个属性为**值**字段,默认`value`
8. 新增 `childrenField``field.children`,指定节点对象中某个属性为**子树节点**字段,默认`children`
9. 新增 `disabledField``field.disabled`,指定节点对象中某个属性为**禁用**字段,默认`disabled`
10. 新增 `appendField``field.append`,指定节点对象中某个属性为**副标签**字段,默认`append`
11. 新增 `leafField``field.label`,指定节点对象中某个属性为**末级节点**字段,默认`leaf`
12. 新增 `sortField``field.label`,指定节点对象中某个属性为**排序**字段,默认`sort`
13. 新增 `isLeafFn` ,用来自定义控制数据项的末项
14. 更多的项目示例
15. 支持单选取消选中
16. 修复节点展开时可能存在的 bug
17. 修复节点选择可能存在的 bug
18. 调整为子节点默认继承父节点禁用属性
19. `setExpandedKeys` 添加参数一为 `all` 即可支持一键展开/收起全部节点
20. 其它更多优化
# 1.3.4
优化
1. 优化图标字体命名
# 1.3.3
优化
1. 新增方法调用
> - 新增`getUncheckedKeys`,返回未选的 key
> - 新增`getUncheckedNodes`,返回未选的节点
> - 新增`getUnexpandedKeys`,返回未展开的 key
> - 新增`getUnexpandedNodes`,返回未展开的节点
2. 优化示例项目
# 1.3.2
修复
1. 修复在 APP 真机环境中的报错
# 1.3.1
修复
1. 修复方法`setExpandedKeys`没联动展开上级父子节点
# 1.3.0
优化
1. `field`新增字段 `append` 用于在标签后面显示小提示
2. 新增支持点击标签也能选中节点
3. 方法`setExpandedKeys`支持加载动态数据
4. 修复父节点禁用,则不能展开及图标展开显示
5. 修复动态加载数据时,末级节点的 `children``null` 时仍显示展开图标
# 1.2.6
新增
1. 新增支持主题换色
2. 支持单选的`onlyRadioLeaf``true`时可点父节点展开/收起
3. 优化`expandChecked`调整为不展开无子节点的节点
# 1.2.5
新增
1. 新增 `expandChecked`,控制选择时是否展开当前已选的所有下级节点
# 1.2.4
修复
1. 修复动态数据展开状态异常问题
# 1.2.3
新增
1. 新增 `checkedDisabled`,是否渲染禁用值
2. 新增 `packDisabledkey`,是否返回已禁用并选中的 key
3. 修复选择父级时,子级已禁用但仍被选中的问题
# 1.2.2
优化
1. 调整动态数据载入处理方式
2. 修复节点数据因动态数据引起的状态异常
3. 修复初始节点数据默认选中
# 1.2.1
修复
1. 修复切换`选中状态`被重复选中问题
2. 修复动态数据引起的重复选择问题
# 1.2.0
新增
1. 新增方法调用
> - 新增`setCheckedKeys`,方法设置指定 key 的节点选中状态
> - 新增`setExpandedKeys`,方法设置指定 key 的节点展开状态
2. 修复小程序重复插槽一直刷报错问题
3. 优化展开时,会展开子级所以下级节点
# 1.1.1
新增
1. 新增`data``disabled`,支持节点禁用状态
2. 新增`field``disabled`,可自定`disabled`字段值
# 1.1.0
新增
1. 新增`loadMode``loadApi`,支持展开时加载异步数据
2. 新增方法调用
> - 新增`getCheckedKeys`,方法返回已选的 key
> - 新增`getHalfCheckedKeys`,方法返回半选的 key
> - 新增`getExpandedKeys`,方法返回已展开的 key
> - 新增`getCheckedNodes`,方法返回已选的节点
> - 新增`getHalfCheckedNodes`,方法返回半选的节点
> - 新增`getExpandedNodes`,方法返回已展开的节点
3. 对代码进行重构,更易于后期拓展
4. 此次更新后,页面多个的 DaTee 组件间的数据不再关联
# 1.0.6
新增
1. 新增`checkStrictly`,多选模式下选中时是否父子不关联
# 1.0.5
修复
1. 修复多选时已选数据重复问题
# 1.0.4
修复
1. 修复 `change` 事件回调数据的问题
# 1.0.3
优化
1. 优化文档及示例说明
# 1.0.2
新增
1. 新增 `onlyRadioLeaf` ,单选时只允许选中末级
2. 优化默认展开及默认选择的展开问题
# 1.0.1
新增
1. 支持展开/收起回调事件`@expand`
# 1.0.0
初始版本 1.0.0,基于 Vue3 进行开发,支持单选、多选,兼容各大平台
1. 支持单选
2. 支持多选

File diff suppressed because it is too large Load Diff

View File

@@ -1,197 +0,0 @@
export default {
/**
* 树的数据
*/
data: {
type: Array,
default: () => [],
},
/**
* 主题色
*/
themeColor: {
type: String,
default: "#007aff",
},
/**
* 是否开启多选,默认单选
*/
showCheckbox: {
type: Boolean,
default: false,
},
/**
* 默认选中的节点注意单选时为单个key多选时为key的数组
*/
defaultCheckedKeys: {
type: [Array, String, Number],
default: null,
},
/**
* 是否默认展开全部
*/
defaultExpandAll: {
type: Boolean,
default: false,
},
/**
* 默认展开的节点
*/
defaultExpandedKeys: {
type: Array,
default: null,
},
/**
* 筛选关键词
*/
filterValue: {
type: String,
default: "",
},
/**
* 是否自动展开到选中的节点,默认不展开
*/
expandChecked: {
type: Boolean,
default: false,
},
/**
* (旧)字段对应内容,默认为 {label: 'label',key: 'key', children: 'children', disabled: 'disabled', append: 'append'}
* 注意1.5.0版本后不再兼容
*/
field: {
type: Object,
default: null,
},
/**
* 标签字段(新,拆分了)
*/
labelField: {
type: String,
default: "label",
},
/**
* 值字段(新,拆分了)
*/
valueField: {
type: String,
default: "value",
},
/**
* 下级字段(新,拆分了)
*/
childrenField: {
type: String,
default: "children",
},
/**
* 禁用字段(新,拆分了)
*/
disabledField: {
type: String,
default: "disabled",
},
/**
* 末级节点字段(新,拆分了)
*/
leafField: {
type: String,
default: "leaf",
},
/**
* 副标签字段(新,拆分了)
*/
appendField: {
type: String,
default: "append",
},
/**
* 排序字段(新,拆分了)
*/
sortField: {
type: String,
default: "sort",
},
/**
* Api数据返回后的结果路径支持嵌套如`data.list`
*/
resultField: {
type: String,
default: "",
},
isLeafFn: {
type: Function,
default: null,
},
/**
* 是否显示单选图标,默认显示
*/
showRadioIcon: {
type: Boolean,
default: true,
},
/**
* 单选时只允许选中末级,默认可随意选中
*/
onlyRadioLeaf: {
type: Boolean,
default: false,
},
/**
* 多选时,是否执行父子不关联的任意勾选,默认父子关联
*/
checkStrictly: {
type: Boolean,
default: false,
},
/**
* 为 true 时,空的 children 数组会显示展开图标
*/
loadMode: {
type: Boolean,
default: false,
},
/**
* 异步加载接口
*/
loadApi: {
type: Function,
default: null,
},
/**
* 是否总在首次的时候加载一下内容,来比对是否一致
*/
alwaysFirstLoad: {
type: Boolean,
default: false,
},
/**
* 是否渲染(操作)禁用值
*/
checkedDisabled: {
type: Boolean,
default: false,
},
/**
* 是否返回已禁用的但已选中的key
*/
packDisabledkey: {
type: Boolean,
default: true,
},
/**
* 选择框的位置,可选 left/right
*/
checkboxPlacement: {
type: String,
default: "left",
},
/**
* 子项缩进距离默认40单位rpx
*/
indent: {
type: Number,
default: 40,
},
};

View File

@@ -1,310 +0,0 @@
# da-tree
一个基于 Vue3 的 tree(树)组件,同时支持主题换色,可能是最适合你的 tree(树)组件
组件一直在更新,遇到问题可在下方讨论。
`同时更新 Vue2 版本,在此查看 ===>` **[Vue2 版](https://ext.dcloud.net.cn/plugin?id=12692)**
### 关于使用
可在右侧的`使用 HBuilderX 导入插件``下载示例项目ZIP`,方便快速上手。
可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。
插件地址https://ext.dcloud.net.cn/plugin?id=12384
### 组件示例
```jsx
<template>
<view>多选</view>
<view><button @click="doCheckedTree(['2'],true)">全选</button></view>
<view><button @click="doCheckedTree(['2'],false)">取消全选</button></view>
<view><button @click="doCheckedTree(['211','222'],true)">选中指定节点</button></view>
<view><button @click="doCheckedTree(['211','222'],false)">取消选中指定节点</button></view>
<view><button @click="doExpandTree('all',true)">展开全部节点</button></view>
<view><button @click="doExpandTree('all',false)">收起全部节点</button></view>
<view><button @click="doExpandTree(['22','23'],true)">展开节点</button></view>
<view><button @click="doExpandTree(['22','23'],false)">收起节点</button></view>
<DaTree
ref="DaTreeRef"
:data="roomTreeData"
labelField="name"
valueField="id"
defaultExpandAll
showCheckbox
:defaultCheckedKeys="defaultCheckedKeysValue"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>单选</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
defaultExpandAll
:defaultCheckedKeys="defaultCheckedKeysValue2"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>默认展开指定节点</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
showCheckbox
:defaultExpandedKeys="defaultExpandKeysValue3"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>异步加载数据</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
showCheckbox
loadMode
:loadApi="GetApiData"
defaultExpandAll
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
</template>
```
```js
import { defineComponent, ref } from "vue";
/**
* 模拟创建一个接口数据
*/
function GetApiData(currentNode) {
const { key } = currentNode;
return new Promise((resolve) => {
setTimeout(() => {
// 模拟返回空数据
if (key.indexOf("-") > -1) {
return resolve(null);
// return resolve([])
}
return resolve([
{
id: `${key}-1`,
name: `行政部X${key}-1`,
},
{
id: `${key}-2`,
name: `财务部X${key}-2`,
append: "定义了末项数据",
leaf: true,
},
{
id: `${key}-3`,
name: `资源部X${key}-3`,
},
{
id: `${key}-4`,
name: `资源部X${key}-3`,
append: "被禁用,无展开图标",
disabled: true,
},
]);
}, 2000);
});
}
import DaTree from "@/components/da-tree/index.vue";
export default defineComponent({
components: { DaTree },
setup() {
const DaTreeRef = ref();
// key的类型必须对应树数据key的类型
const defaultCheckedKeysValue = ref(["211", "222"]);
const defaultCheckedKeysValue2 = ref("222");
const defaultExpandKeysValue3 = ref(["212", "231"]);
const roomTreeData = ref([
{
id: "2",
name: "行政中心",
children: [
{
id: "21",
name: "行政部",
children: [
{
id: "211",
name: "行政一部",
children: null,
},
{
id: "212",
name: "行政二部",
children: [],
disabled: true,
},
],
},
{
id: "22",
name: "财务部",
children: [
{
id: "221",
name: "财务一部",
children: [],
disabled: true,
},
{
id: "222",
name: "财务二部",
children: [],
},
],
},
{
id: "23",
name: "人力资源部",
children: [
{
id: "231",
name: "人力一部",
children: [],
},
{
id: "232",
name: "人力二部",
append: "更多示例,请下载示例项目查看",
},
],
},
],
},
]);
function doExpandTree(keys, expand) {
DaTreeRef.value?.setExpandedKeys(keys, expand);
const gek = DaTreeRef.value?.getExpandedKeys();
console.log("当前已展开的KEY ==>", gek);
}
function doCheckedTree(keys, checked) {
DaTreeRef.value?.setCheckedKeys(keys, checked);
const gek = DaTreeRef.value?.getCheckedKeys();
console.log("当前已选中的KEY ==>", gek);
}
function handleTreeChange(allSelectedKeys, currentItem) {
console.log("handleTreeChange ==>", allSelectedKeys, currentItem);
}
function handleExpandChange(expand, currentItem) {
console.log("handleExpandChange ==>", expand, currentItem);
}
return {
DaTreeRef,
roomTreeData,
defaultCheckedKeysValue,
defaultCheckedKeysValue2,
defaultExpandKeysValue3,
handleTreeChange,
handleExpandChange,
GetApiData,
doExpandTree,
doCheckedTree,
};
},
});
```
** 更多示例请下载/导入示例项目 ZIP 查看 **
### 组件参数
| 属性 | 类型 | 默认值 | 必填 | 说明 |
| :------------------ | :------------------------------ | :--------- | :--- | :--------------------------------------------------------------------------- |
| data | `Array` | - | 是 | 树的数据 |
| themeColor | `String` | `#007aff` | 否 | 主题色,十六进制 |
| defaultCheckedKeys | `Array` \| `Number` \| `String` | - | 否 | 默认选中的节点,单选为单个 key多选为 key 的数组 |
| showCheckbox | `Boolean` | `false` | 否 | 是否开启多选,默认单选 |
| checkStrictly | `Boolean` | `false` | 否 | 多选时,是否执行父子不关联的任意勾选,默认父子关联 |
| showRadioIcon | `Boolean` | `true` | 否 | 是否显示单选图标,默认显示 |
| onlyRadioLeaf | `Boolean` | `true` | 否 | 单选时只允许选中末级,默认可随意选中 |
| defaultExpandAll | `Boolean` | `false` | 否 | 是否默认展开全部 |
| defaultExpandedKeys | `Array` | - | 否 | 默认展开的节点 |
| indent | `Number` | `40` | 否 | 子项缩进距离,单位 rpx |
| checkboxPlacement | `String` | `left` | 否 | 选择框的位置,可选 left/right |
| loadMode | `Boolean` | `false` | 否 | 为 true 时,空的 children 数组会显示展开图标 |
| loadApi | `Function` | - | 否 | 选择框的位置,可选 left/right |
| checkedDisabled | `Boolean` | `false` | 否 | 是否渲染禁用值,默认不渲染 |
| packDisabledkey | `Boolean` | `true` | 否 | 是否返回已禁用的但已选中的 key默认返回禁用已选值 |
| expandChecked | `Boolean` | `false` | 否 | 是否自动展开到选中的节点,默认不展开 |
| alwaysFirstLoad | `Boolean` | `false` | 否 | 是否总在首次的时候加载一下内容,默认不加载,否则只有展开末级节点才会加载数据 |
| isLeafFn | `Function` | - | 否 | 自定义函数返回来控制数据项的末项 |
| field | `Object` | - | 否 | 字段对应内容,格式参考下方(1.5.0 后移除,请用单独的字段匹配) |
| labelField | `String` | `label` | 否 | 指定节点对象中某个属性为标签字段,默认`label` |
| valueField | `String` | `value` | 否 | 指定节点对象中某个属性为值字段,默认`value` |
| childrenField | `String` | `children` | 否 | 指定节点对象中某个属性为子树节点字段,默认`children` |
| disabledField | `String` | `disabled` | 否 | 指定节点对象中某个属性为禁用字段,默认`disabled` |
| appendField | `String` | `append` | 否 | 指定节点对象中某个属性为副标签字段,默认`append` |
| leafField | `String` | `leaf` | 否 | 指定节点对象中某个属性为末级节点字段,默认`leaf` |
| sortField | `String` | `sort` | 否 | 指定节点对象中某个属性为排序字段,默认`sort` |
| filterValue | `String` | - | 否 | 搜索筛选的关键词,通过输入关键词筛选内容 |
**field 格式(1.5.0 后移除,请用单独的字段匹配)**
```js
{
label: 'label',
key: 'key',
children: 'children',
disabled: 'disabled',
append: 'append'
}
```
### 组件事件
| 事件名称 | 回调参数 | 说明 |
| :------- | :-------------------------------------- | :-------------- |
| change | `(allCheckedKeys, currentItem) => void` | 选中时回调 |
| expand | `(expandState, currentItem) => void` | 展开/收起时回调 |
### 组件方法
| 方法名称 | 参数 | 说明 |
| :------------------ | :--------------- | :------------------------------------------------------------------------------------------------ |
| setCheckedKeys | `(keys,checked)` | 设置指定 key 的节点选中/取消选中的状态。注: keys 单选时为 key多选时为 key 的数组 |
| setExpandedKeys | `(keys,expand)` | 设置指定 key 的节点展开/收起的状态,当 keys 为 all 时即代表展开/收起全部。注keys 为数组或 `all` |
| getCheckedKeys | - | 返回已选的 key |
| getHalfCheckedKeys | - | 返回半选的 key |
| getUncheckedKeys | - | 返回未选的 key |
| getCheckedNodes | - | 返回已选的节点 |
| getUncheckedNodes | - | 返回未选的节点 |
| getHalfCheckedNodes | - | 返回半选的节点 |
| getExpandedKeys | - | 返回已展开的 key |
| getUnexpandedKeys | - | 返回未展开的 key |
| getExpandedNodes | - | 返回已展开的节点 |
| getUnexpandedNodes | - | 返回未展开的节点 |
### 组件版本
v1.4.2
### 差异化
已通过测试
> - H5 页面
> - 微信小程序
> - 支付宝、钉钉小程序
> - 字节跳动、抖音、今日头条小程序
> - 百度小程序
> - 飞书小程序
> - QQ 小程序
> - 京东小程序
未测试
> - 快手小程序由于非企业用户暂无演示
> - 快应用、360 小程序因 Vue3 支持的原因暂无演示
### 开发组
[@CRLANG](https://crlang.com)

View File

@@ -1,153 +0,0 @@
/** 未选 */
export const unCheckedStatus = 0;
/** 半选 */
export const halfCheckedStatus = 1;
/** 选中 */
export const isCheckedStatus = 2;
/**
* 深拷贝内容
* @param originData 拷贝对象
* @author crlang(https://crlang.com)
*/
export function deepClone(originData: any): any {
const type = Object.prototype.toString.call(originData);
let data: any;
if (type === "[object Array]") {
data = [];
for (let i = 0; i < originData.length; i++) {
data.push(deepClone(originData[i]));
}
} else if (type === "[object Object]") {
data = {} as Record<string, any>;
for (const prop in originData) {
if (Object.prototype.hasOwnProperty.call(originData, prop)) {
// 非继承属性
data[prop] = deepClone(originData[prop]);
}
}
} else {
data = originData;
}
return data;
}
/**
* 获取所有指定的节点
* @param list 列表
* @param type 类型
* @param value 值
* @param packDisabledkey 是否包含禁用节点
* @author crlang(https://crlang.com)
*/
export function getAllNodes(list: any[], type: string, value: any, packDisabledkey = true): any[] {
if (!list || list.length === 0) {
return [];
}
const res = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item[type] === value) {
if ((packDisabledkey && item.disabled) || !item.disabled) {
res.push(item);
}
}
}
return res;
}
/**
* 获取所有指定的key值
* @param list 列表
* @param type 类型
* @param value 值
* @param packDisabledkey 是否包含禁用节点
* @author crlang(https://crlang.com)
*/
export function getAllNodeKeys(
list: any[],
type: string,
value: any,
packDisabledkey = true
): string[] | null {
if (!list || list.length === 0) {
return null;
}
const res: string[] = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item[type] === value) {
if ((packDisabledkey && item.disabled) || !item.disabled) {
res.push(item.key);
}
}
}
return res.length ? res : null;
}
/**
* 错误输出
* @param msg 错误消息
* @param args 附加参数
*/
export function logError(msg: string, ...args: any[]): void {
console.error(`DaTree: ${msg}`, ...args);
}
const toString = Object.prototype.toString;
export function is(val: any, type: string): boolean {
return toString.call(val) === `[object ${type}]`;
}
/**
* 是否对象(Object)
* @param val 值
*/
export function isObject(val: any): boolean {
return val !== null && is(val, "Object");
}
/**
* 是否数字(Number)
* @param val 值
*/
export function isNumber(val: any): boolean {
return is(val, "Number");
}
/**
* 是否字符串(String)
* @param val 值
*/
export function isString(val: any): boolean {
return is(val, "String");
}
/**
* 是否函数方法(Function)
* @param val 值
*/
export function isFunction(val: any): boolean {
return typeof val === "function";
}
/**
* 是否布尔(Boolean)
* @param val 值
*/
export function isBoolean(val: any): boolean {
return is(val, "Boolean");
}
/**
* 是否数组(Array)
* @param val 值
*/
export function isArray(val: any): boolean {
return val && Array.isArray(val);
}

View File

@@ -1,59 +1,130 @@
import { ref, computed } from "vue";
import { ref, watch } from "vue";
import type { ConfigProviderThemeVars } from "wot-design-uni";
/* 默认的主题list */
/* 默认的主题色列表 */
export const colorColumns = [
{
value: "#0055FE",
label: "色",
},
{
value: "#CD5C5C",
label: "色",
},
{
value: "#228B22",
label: "绿色",
},
{ value: "#165DFF", label: "蓝色" },
{ value: "#0FC6C2", label: "青绿色" },
{ value: "#722ED1", label: "色" },
{ value: "#F5222D", label: "红色" },
{ value: "#FA8C16", label: "橙色" },
{ value: "#FADB14", label: "黄色" },
{ value: "#52C41A", label: "绿色" },
{ value: "#EB2F96", label: "粉色" },
{ value: "#13C2C2", label: "青色" },
{ value: "#1890FF", label: "天蓝色" },
{ value: "#CD5C5C", label: "经典红" },
{ value: "#228B22", label: "自然绿" },
];
/* 默认的主题 */
export const initThemState = "light";
/* 存储键名 */
const THEME_STORAGE_KEY = "app_theme_mode";
const THEME_COLOR_STORAGE_KEY = "app_theme_color";
/* 默认的主题变量 - 只使用wot-design-uni支持的变量 */
export const initThemeVars: ConfigProviderThemeVars = {
colorTheme: colorColumns[0].value,
/* 从存储中获取主题模式 */
const getStoredTheme = (): "light" | "dark" => {
try {
const stored = uni.getStorageSync(THEME_STORAGE_KEY);
return stored === "dark" ? "dark" : "light";
} catch {
return "light";
}
};
/* 暗黑模式主题变量 */
export const darkThemeVars: ConfigProviderThemeVars = {
colorTheme: colorColumns[0].value,
/* 从存储中获取主题色 */
const getStoredThemeColor = (): string => {
try {
const stored = uni.getStorageSync(THEME_COLOR_STORAGE_KEY);
return stored || colorColumns[0].value;
} catch {
return colorColumns[0].value;
}
};
/* 保存主题模式到存储 */
const saveThemeToStorage = (theme: "light" | "dark") => {
try {
uni.setStorageSync(THEME_STORAGE_KEY, theme);
} catch (error) {
console.error("保存主题模式失败:", error);
}
};
/* 保存主题色到存储 */
const saveThemeColorToStorage = (color: string) => {
try {
uni.setStorageSync(THEME_COLOR_STORAGE_KEY, color);
} catch (error) {
console.error("保存主题色失败:", error);
}
};
/* 应用暗黑模式的 body 样式 */
const applyDarkModeBodyStyle = (isDark: boolean) => {
// #ifdef H5
if (typeof document !== "undefined") {
const body = document.body;
if (isDark) {
body.style.backgroundColor = "#1a1a1a";
body.style.color = "#f5f5f5";
body.classList.add("wot-theme-dark");
} else {
body.style.backgroundColor = "#f8f8f8";
body.style.color = "#333";
body.classList.remove("wot-theme-dark");
}
}
// #endif
// #ifdef MP
// 小程序环境中设置页面样式
try {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
if (currentPage && currentPage.$vm) {
// 这里可以根据需要设置页面样式
console.log("小程序暗黑模式:", isDark ? "开启" : "关闭");
}
}
} catch (error) {
console.error("设置小程序页面样式失败:", error);
}
// #endif
};
/* 主题状态 */
export const themeState = ref(initThemState);
export const theme = ref<"light" | "dark">(getStoredTheme());
/* 主题变量 */
export const themeVars = ref<ConfigProviderThemeVars>(initThemeVars);
/* 计算属性:根据主题状态返回对应的主题变量 */
export const computedThemeVars = computed(() => {
const baseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars;
return {
...baseVars,
...themeVars.value,
};
export const themeVars = ref<ConfigProviderThemeVars>({
colorTheme: getStoredThemeColor(),
});
/* 切换主题 */
/* 监听主题模式变化,应用 body 样式 */
watch(
theme,
(newTheme) => {
saveThemeToStorage(newTheme);
applyDarkModeBodyStyle(newTheme === "dark");
},
{ immediate: true }
);
/* 监听主题色变化,保存到存储 */
watch(
() => themeVars.value.colorTheme,
(newColor) => {
if (newColor) {
saveThemeColorToStorage(newColor);
}
},
{ immediate: true }
);
/* 切换主题模式 */
export const toggleTheme = () => {
themeState.value = themeState.value === "light" ? "dark" : "light";
// 更新主题变量
const newBaseVars = themeState.value === "dark" ? darkThemeVars : initThemeVars;
themeVars.value = {
...newBaseVars,
colorTheme: themeVars.value.colorTheme, // 保持用户选择的主题色
};
theme.value = theme.value === "light" ? "dark" : "light";
};
/* 设置主题色 */
@@ -64,13 +135,37 @@ export const setThemeColor = (color: string) => {
};
};
/* 重置主题 */
export const resetTheme = () => {
theme.value = "light";
setThemeColor(colorColumns[0].value);
};
/* 初始化主题 */
export const initTheme = () => {
// 应用当前主题的 body 样式
applyDarkModeBodyStyle(theme.value === "dark");
// 确保主题变量已正确设置
if (!themeVars.value.colorTheme) {
themeVars.value.colorTheme = getStoredThemeColor();
}
console.log("主题初始化完成:", {
mode: theme.value,
color: themeVars.value.colorTheme,
});
};
/* 导出主题相关的工具 */
export const useTheme = () => {
return {
themeState,
themeVars: computedThemeVars,
theme,
themeVars,
toggleTheme,
setThemeColor,
resetTheme,
initTheme,
colorColumns,
};
};

View File

@@ -1,11 +1,7 @@
<script lang="ts" setup>
import type { ConfigProviderThemeVars } from "wot-design-uni";
import { useTheme } from "@/composables/useTheme";
const themeVars = reactive<ConfigProviderThemeVars>({
colorTheme: "#FF5454",
tabsNavLineBgColor: "red",
navbarColor: "#ffffff",
});
const { theme, themeVars } = useTheme();
</script>
<script lang="ts">
@@ -20,8 +16,10 @@ export default {
<template>
<wd-config-provider
:theme="theme"
:theme-vars="themeVars"
custom-style="background-color: #f5f5f5;min-height: 100vh"
:class="{ 'wot-theme-dark': theme === 'dark' }"
>
<slot />
<wd-notify />
@@ -30,3 +28,11 @@ export default {
<privacy-popup />
</wd-config-provider>
</template>
<style lang="scss" scoped>
/* 暗黑模式样式 */
.wot-theme-dark {
color: #f5f5f5 !important;
background-color: #1a1a1a !important;
}
</style>

View File

@@ -8,7 +8,8 @@
* 记得注释
-->
<script lang="ts" setup>
import { ref, computed, onMounted, nextTick } from "vue";
import { ref, computed, onMounted, nextTick, onUnmounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { useTheme } from "@/composables/useTheme";
// 定义 TabbarItem 接口
@@ -26,7 +27,7 @@ const tabbarItems = ref<TabbarItem[]>([
{ name: "mine", value: null, active: false, title: "我的", icon: "user" },
]);
const { themeVars } = useTheme();
const { theme, themeVars } = useTheme();
// 计算属性
const tabbarList = computed(() => tabbarItems.value);
@@ -43,6 +44,7 @@ const getTabbarItemValue = (name: string) => {
};
const setTabbarItemActive = (name: string) => {
console.log(`设置 tabbar 激活状态: ${name}`);
tabbarItems.value.forEach((item) => {
if (item.name === name) {
item.active = true;
@@ -52,7 +54,35 @@ const setTabbarItemActive = (name: string) => {
});
};
// 根据当前路由更新 tabbar 激活状态
const updateTabbarByRoute = () => {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
const route = currentPage.route;
console.log("=== Tabbar 路由更新 ===");
console.log("当前路由:", route);
console.log("当前激活的 tabbar:", activeTabbar.value.name);
// 根据当前路由设置活跃的 tabbar
if (route === "pages/index/index") {
console.log("设置首页为激活状态");
setTabbarItemActive("index");
} else if (route === "pages/mine/index") {
console.log("设置我的页面为激活状态");
setTabbarItemActive("mine");
} else {
console.log("非 tabbar 页面,保持当前状态");
}
console.log("更新后激活的 tabbar:", activeTabbar.value.name);
console.log("=== 更新完成 ===");
}
};
function handleTabbarChange({ value }: { value: string }) {
console.log(`用户点击 tabbar: ${value}`);
setTabbarItemActive(value);
// 根据 tabbar 项目导航到对应页面
@@ -67,29 +97,51 @@ function handleTabbarChange({ value }: { value: string }) {
}
}
// 监听页面栈变化
const currentRoute = ref("");
const updateCurrentRoute = () => {
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
const newRoute = currentPage.route || "";
if (newRoute !== currentRoute.value) {
currentRoute.value = newRoute;
console.log("页面路由发生变化:", newRoute);
updateTabbarByRoute();
}
}
};
onMounted(() => {
nextTick(() => {
// 获取当前页面路径
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
const route = currentPage.route;
updateTabbarByRoute();
updateCurrentRoute();
});
// 根据当前路由设置活跃的 tabbar
if (route?.includes("pages/index/index")) {
setTabbarItemActive("index");
} else if (route?.includes("pages/mine/index")) {
setTabbarItemActive("mine");
}
}
// 监听页面发出的 tabbar 更新事件
uni.$on("updateTabbar", (tabName: string) => {
console.log("收到 tabbar 更新事件:", tabName);
setTabbarItemActive(tabName);
});
});
// 页面显示时更新 tabbar 状态
onShow(() => {
nextTick(() => {
updateCurrentRoute();
});
// #ifdef APP-PLUS
uni.hideTabBar();
// #endif
});
// 组件卸载时移除事件监听
onUnmounted(() => {
uni.$off("updateTabbar");
});
</script>
<script lang="ts">
@@ -103,7 +155,12 @@ export default {
</script>
<template>
<wd-config-provider :theme-vars="themeVars" custom-style="min-height: 100vh" theme="dark">
<wd-config-provider
:theme="theme"
:theme-vars="themeVars"
custom-style="min-height: 100vh"
:class="{ 'wot-theme-dark': theme === 'dark' }"
>
<wd-navbar
:title="activeTabbar.title"
safe-area-inset-top
@@ -135,3 +192,21 @@ export default {
<wd-message-box />
</wd-config-provider>
</template>
<style lang="scss" scoped>
/* 暗黑模式样式 */
.wot-theme-dark {
color: #f5f5f5;
background-color: #1a1a1a;
:deep(.wd-navbar) {
color: #f5f5f5;
background-color: #2a2a2a;
}
:deep(.wd-tabbar) {
background-color: #2a2a2a;
border-top-color: #404040;
}
}
</style>

View File

@@ -3,6 +3,7 @@ import App from "./App.vue";
import "uno.css";
import "@/styles/global.scss";
import "@/styles/wot-theme.scss";
import { setupStore } from "@/store";

View File

@@ -252,6 +252,18 @@ onReady(() => {
loadVisitStatsData();
loadVisitTrendData();
});
onShow(() => {
// 确保 tabbar 状态正确
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
if (currentPage.route === "pages/index/index") {
// 通过事件通知 tabbar 布局更新状态
uni.$emit("updateTabbar", "index");
}
}
});
</script>
<style setup lang="scss">

View File

@@ -220,29 +220,25 @@ const handleQuestionFeedback = () => {
// 导航到各个板块
const navigateToSection = (section: string, subSection?: string) => {
if (!isLogin.value && section !== "services") {
navigateToLoginPage();
return;
}
const sections: Record<string, string> = {
messages: "消息中心",
todos: "待办事项",
favorites: "我的收藏",
history: "浏览历史",
wallet: "我的钱包",
orders: "我的订单",
address: "收货地址",
services: "增值服务",
};
let message = sections[section];
if (subSection) {
message += ` - ${subSection}`;
}
toast.show(`${message}功能开发中...`);
console.log(`导航到: ${section}${subSection ? ` - ${subSection}` : ""}`);
// 这里可以根据需要实现具体的导航逻辑
uni.showToast({
title: "功能开发中",
icon: "none",
});
};
onShow(() => {
// 确保 tabbar 状态正确
const pages = getCurrentPages();
if (pages.length > 0) {
const currentPage = pages[pages.length - 1];
if (currentPage.route === "pages/mine/index") {
// 通过事件通知 tabbar 布局更新状态
uni.$emit("updateTabbar", "mine");
}
}
});
</script>
<style lang="scss" scoped>

View File

@@ -64,7 +64,7 @@
</template>
<script setup lang="ts">
import UserAPI, { type UserProfileVO, UserProfileForm } from "@/api/user";
import FileAPI, { type FileInfo } from "@/api";
import FileAPI, { type FileInfo } from "@/api/file";
import { checkLogin } from "@/utils/auth";
const originalSrc = ref<string>(""); //选取的原图路径

View File

@@ -38,6 +38,7 @@
import { useUserStore } from "@/store/modules/user.store";
import { checkLogin } from "@/utils/auth";
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
const userStore = useUserStore();
const isLogin = computed(() => !!userStore.userInfo);
@@ -200,6 +201,39 @@ onLoad(() => {
.settings-container {
min-height: 100vh;
padding: 20px;
color: var(--wot-color-text, #333);
background-color: var(--wot-color-bg-light, #f8f8f8);
// 强制 Wot 组件应用暗黑模式样式
:deep(.wd-cell-group) {
overflow: hidden;
background-color: var(--wot-card-bg-color, #fff);
border-radius: 16rpx;
}
:deep(.wd-cell) {
color: var(--wot-color-text, #333);
background-color: var(--wot-card-bg-color, #fff);
.wd-cell__title {
color: var(--wot-color-text, #333) !important;
}
.wd-cell__value {
color: var(--wot-color-text-secondary, #666) !important;
}
.wd-cell__right-icon {
color: var(--wot-color-text-secondary, #999) !important;
}
}
:deep(.wd-button) {
&.logout-btn {
color: #fff !important;
background-color: var(--wot-color-theme, var(--primary-color)) !important;
}
}
.loading-mask {
position: fixed;
@@ -216,7 +250,7 @@ onLoad(() => {
.loading-content {
padding: 30rpx 40rpx;
text-align: center;
background-color: rgba(255, 255, 255, 0.95);
background-color: var(--wot-card-bg-color, rgba(255, 255, 255, 0.95));
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
@@ -224,8 +258,8 @@ onLoad(() => {
width: 60rpx;
height: 60rpx;
margin: 0 auto 20rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #409eff;
border: 4rpx solid var(--wot-color-border, #f3f3f3);
border-top: 4rpx solid var(--wot-color-theme, #409eff);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@@ -233,7 +267,7 @@ onLoad(() => {
.loading-text {
font-size: 28rpx;
font-weight: 500;
color: #333;
color: var(--wot-color-text, #333);
}
}
}
@@ -255,10 +289,10 @@ onLoad(() => {
font-size: 32rpx;
font-weight: 500;
color: #fff;
background-color: var(--primary-color);
background-color: var(--wot-color-theme, var(--primary-color));
border: none;
border-radius: 45rpx;
box-shadow: 0 4rpx 12rpx rgba(var(--primary-color-rgb), 0.3);
box-shadow: 0 4rpx 12rpx rgba(22, 93, 255, 0.3);
transition: opacity 0.2s;
&:active {
@@ -266,4 +300,40 @@ onLoad(() => {
}
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
// 全局暗黑模式适配(针对当前页面)
:global([data-theme="dark"]) .settings-container {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-color-bg, #1a1a1a) !important;
:deep(.wd-cell-group) {
background-color: var(--wot-card-bg-color, #2a2a2a) !important;
}
:deep(.wd-cell) {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-card-bg-color, #2a2a2a) !important;
.wd-cell__title {
color: var(--wot-color-text, #fff) !important;
}
.wd-cell__value {
color: var(--wot-color-text-secondary, #d1d5db) !important;
}
.wd-cell__right-icon {
color: var(--wot-color-text-secondary, #9ca3af) !important;
}
}
}
</style>

View File

@@ -7,18 +7,18 @@
</view>
<!-- 暗黑模式设置 -->
<view class="setting-section">
<wd-card class="setting-section">
<view class="section-header">
<wd-icon name="moon" size="20" :color="isDark ? '#FFD700' : '#666'" />
<wd-icon name="moon" size="20" :color="theme === 'dark' ? '#FFD700' : '#666'" />
<text class="section-title">外观模式</text>
</view>
<wd-cell title="暗黑模式" :value="isDark ? '已开启' : '已关闭'">
<wd-switch v-model="isDark" @change="toggleDarkMode" />
<wd-cell title="暗黑模式" :value="theme === 'dark' ? '已开启' : '已关闭'">
<wd-switch :model-value="theme === 'dark'" @change="handleToggleDarkMode" />
</wd-cell>
</view>
</wd-card>
<!-- 主题色设置 -->
<view class="setting-section">
<wd-card class="setting-section">
<view class="section-header">
<wd-icon name="palette" size="20" color="#666" />
<text class="section-title">主题色彩</text>
@@ -26,59 +26,78 @@
<!-- 预设颜色选择 -->
<view class="color-section">
<view class="color-label">预设主题色</view>
<text class="color-label">预设主题色</text>
<view class="color-grid">
<view
v-for="(color, index) in extendedThemeColors"
v-for="(color, index) in colorColumns"
:key="index"
class="color-item"
:class="{ active: currentThemeColor === color.value }"
:style="{ backgroundColor: color.value }"
@click="handleSelectColor(color.value)"
>
<wd-icon v-if="currentThemeColor === color.value" name="check" color="#fff" size="16" />
<view
class="color-preview"
:style="{
backgroundColor: color.value,
border: currentThemeColor === color.value ? '3px solid #fff' : '1px solid #e0e0e0',
}"
>
<text v-if="currentThemeColor === color.value" class="check-icon"></text>
</view>
<text class="color-name">{{ color.label }}</text>
</view>
</view>
</view>
<!-- 当前主题色显示 -->
<wd-cell title="当前主题色" :value="currentThemeColor">
<view class="current-color-preview" :style="{ backgroundColor: currentThemeColor }"></view>
</wd-cell>
<view class="current-theme-section">
<view class="current-theme-item">
<text class="current-theme-label">当前主题色</text>
<view class="current-theme-value">
<view
class="current-color-preview"
:style="{ backgroundColor: currentThemeColor }"
></view>
<text class="current-color-text">{{ currentThemeColor }}</text>
</view>
</view>
</view>
<!-- 自定义颜色 -->
<wd-cell title="自定义颜色" is-link @click="showCustomInput">
<wd-icon name="edit" size="16" color="#999" />
</wd-cell>
</view>
</wd-card>
<!-- 预览区域 -->
<view class="setting-section">
<wd-card class="setting-section">
<view class="section-header">
<wd-icon name="eye" size="20" color="#666" />
<text class="section-title">效果预览</text>
</view>
<view class="preview-container">
<view class="preview-item">
<wd-divider />
<wd-grid :column="2" border>
<wd-grid-item>
<wd-button type="primary" size="small">主要按钮</wd-button>
</view>
<view class="preview-item">
</wd-grid-item>
<wd-grid-item>
<text class="preview-text" :style="{ color: currentThemeColor }">主题色文本</text>
</view>
<view class="preview-item">
</wd-grid-item>
<wd-grid-item>
<view class="preview-border" :style="{ borderColor: currentThemeColor }">主题色边框</view>
</view>
<view class="preview-item">
</wd-grid-item>
<wd-grid-item>
<wd-tag type="primary" size="small">标签</wd-tag>
</view>
</view>
</view>
</wd-grid-item>
</wd-grid>
</wd-card>
<!-- 操作按钮 -->
<view class="action-section">
<wd-button type="info" size="large" block @click="resetTheme">重置为默认主题</wd-button>
</view>
<wd-card class="action-section">
<wd-button type="info" size="large" block @click="handleResetTheme">重置为默认主题</wd-button>
</wd-card>
<!-- 自定义颜色输入弹窗 -->
<wd-popup v-model="showCustomColorInput" position="bottom" :safe-area-inset-bottom="true">
@@ -88,21 +107,24 @@
<wd-icon name="close" size="20" color="#999" @click="showCustomColorInput = false" />
</view>
<wd-divider />
<view class="color-input-section">
<view class="input-label">请输入十六进制颜色值</view>
<view class="input-container">
<view class="color-preview-small" :style="{ backgroundColor: customColor }"></view>
<input
<wd-input
v-model="customColor"
type="text"
placeholder="例如: #165DFF"
:maxlength="7"
class="color-input"
maxlength="7"
/>
</view>
<view class="input-tip">支持格式#RGB #RRGGBB</view>
</view>
<wd-divider />
<view class="popup-actions">
<wd-button type="info" size="large" @click="showCustomColorInput = false">取消</wd-button>
<wd-button type="primary" size="large" @click="applyCustomColor">应用</wd-button>
@@ -114,61 +136,23 @@
<script lang="ts" setup>
import { onLoad, onShow } from "@dcloudio/uni-app";
import { useTheme } from "@/composables/useTheme";
import { useThemeStore } from "@/store/modules/theme.store";
import { applyThemeOnPageShow } from "@/utils/theme";
import { ref, computed, watch, onMounted } from "vue";
import { useTheme, colorColumns } from "@/composables/useTheme";
import { ref, computed, onMounted, nextTick } from "vue";
const { toggleTheme, themeVars, themeState, setThemeColor } = useTheme();
const themeStore = useThemeStore();
// 暗黑模式状态
const isDark = ref(false);
const { theme, themeVars, toggleTheme, setThemeColor, resetTheme } = useTheme();
// 自定义颜色输入
const customColor = ref("");
const showCustomColorInput = ref(false);
// 扩展的预设主题色
const extendedThemeColors = [
{ value: "#165DFF", label: "蓝色" },
{ value: "#0FC6C2", label: "青绿色" },
{ value: "#722ED1", label: "紫色" },
{ value: "#F5222D", label: "红色" },
{ value: "#FA8C16", label: "橙色" },
{ value: "#FADB14", label: "黄色" },
{ value: "#52C41A", label: "绿色" },
{ value: "#EB2F96", label: "粉色" },
{ value: "#13C2C2", label: "青色" },
{ value: "#1890FF", label: "天蓝色" },
{ value: "#CD5C5C", label: "经典红" },
{ value: "#228B22", label: "自然绿" },
];
// 当前选中的主题色
const currentThemeColor = computed(() => {
return themeVars.value.colorTheme || themeStore.primaryColor || extendedThemeColors[0].value;
return themeVars.value.colorTheme || colorColumns[0].value;
});
// 监听暗黑模式变化
watch(themeState, (newTheme) => {
isDark.value = newTheme === "dark";
});
// 暗黑模式切换
const toggleDarkMode = () => {
toggleTheme();
};
// 选择预设颜色
const handleSelectColor = (color: string) => {
// 同时更新两个store的主题色
if (setThemeColor) {
setThemeColor(color);
} else {
themeVars.value.colorTheme = color;
}
themeStore.setPrimaryColor(color);
setThemeColor(color);
customColor.value = color;
// 提示
@@ -204,13 +188,7 @@ const applyCustomColor = () => {
color = "#" + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
}
// 同时更新两个store的主题色
if (setThemeColor) {
setThemeColor(color);
} else {
themeVars.value.colorTheme = color;
}
themeStore.setPrimaryColor(color);
setThemeColor(color);
showCustomColorInput.value = false;
// 提示
@@ -222,27 +200,14 @@ const applyCustomColor = () => {
};
// 重置为默认主题
const resetTheme = () => {
const handleResetTheme = () => {
uni.showModal({
title: "确认重置",
content: "确定要重置为默认主题吗?",
success: (res) => {
if (res.confirm) {
const defaultColor = extendedThemeColors[0].value;
// 同时更新两个store的主题色
if (setThemeColor) {
setThemeColor(defaultColor);
} else {
themeVars.value.colorTheme = defaultColor;
}
themeStore.setPrimaryColor(defaultColor);
customColor.value = defaultColor;
// 重置为浅色模式
if (themeState.value === "dark") {
toggleTheme();
}
resetTheme();
customColor.value = colorColumns[0].value;
uni.showToast({
title: "已重置为默认主题",
@@ -254,31 +219,35 @@ const resetTheme = () => {
});
};
// 切换暗黑模式
const handleToggleDarkMode = () => {
toggleTheme();
nextTick(() => {
uni.showToast({
title: `已切换到${theme.value === "dark" ? "暗黑" : "浅色"}模式`,
icon: "success",
duration: 1500,
});
});
};
onLoad(() => {
// 初始化自定义颜色输入框
customColor.value = currentThemeColor.value;
});
onMounted(() => {
// 初始化暗黑模式状态
isDark.value = themeState.value === "dark";
// 初始化自定义颜色输入框
customColor.value = currentThemeColor.value;
});
// 页面显示时重新应用主题(解决小程序环境问题)
// 页面显示时确保主题色同步
onShow(() => {
// 在小程序环境中,页面显示时重新应用主题
if (typeof document === "undefined") {
applyThemeOnPageShow(currentThemeColor.value);
// 为了确保样式应用,我们可以在页面中设置内联样式
// 这样可以绕过小程序的CSS变量限制
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
if (currentPage) {
console.log("当前页面重新应用主题色");
}
}
customColor.value = currentThemeColor.value;
console.log("主题设置页面显示,当前主题:", {
mode: theme.value,
color: currentThemeColor.value,
});
});
</script>
@@ -286,15 +255,81 @@ onShow(() => {
.theme-settings-container {
min-height: 100vh;
padding: 20rpx;
background-color: #f8f9fa;
background-color: var(--wot-color-bg-light, #f8f9fa);
transition: background-color 0.3s ease;
// 强制 Wot 组件应用暗黑模式样式
:deep(.wd-card) {
background: var(--wot-card-bg-color, #fff);
border-radius: 16rpx;
box-shadow: var(--wot-card-shadow, 0 2rpx 12rpx rgba(0, 0, 0, 0.05));
transition: background-color 0.3s ease;
}
:deep(.wd-cell) {
color: var(--wot-color-text, #333);
background-color: var(--wot-card-bg-color, #fff);
.wd-cell__title {
color: var(--wot-color-text, #333) !important;
}
.wd-cell__value {
color: var(--wot-color-text-secondary, #666) !important;
}
.wd-cell__right-icon {
color: var(--wot-color-text-secondary, #999) !important;
}
}
:deep(.wd-grid-item) {
color: var(--wot-color-text, #333) !important;
background-color: var(--wot-card-bg-color, #fff) !important;
}
// 专门为颜色预览块重置背景色
:deep(.color-item) {
background-color: transparent !important;
.color-preview {
background-color: inherit !important;
}
}
:deep(.wd-button) {
&[type="primary"] {
color: #fff !important;
background-color: var(--wot-color-theme, #165dff) !important;
}
&[type="info"] {
color: #fff !important;
background-color: var(--wot-color-info, #909399) !important;
}
}
:deep(.wd-input) {
color: var(--wot-color-text, #333) !important;
background-color: var(--wot-card-bg-color, #fff) !important;
}
:deep(.wd-popup) {
background-color: var(--wot-popup-bg-color, #fff) !important;
}
.custom-color-popup {
padding: 40rpx 30rpx;
background: var(--wot-popup-bg-color, #fff);
border-radius: 20rpx 20rpx 0 0;
}
}
.page-header {
padding: 40rpx 20rpx;
margin-bottom: 30rpx;
text-align: center;
background: linear-gradient(135deg, #165dff 0%, #667eea 100%);
background: linear-gradient(135deg, var(--wot-color-theme, #165dff) 0%, #667eea 100%);
border-radius: 16rpx;
}
@@ -313,25 +348,20 @@ onShow(() => {
.setting-section {
margin-bottom: 30rpx;
overflow: hidden;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: background-color 0.3s ease;
}
.section-header {
display: flex;
align-items: center;
padding: 30rpx 30rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
border-bottom: 1rpx solid var(--wot-color-border, #f0f0f0);
}
.section-title {
margin-left: 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #333;
color: var(--wot-color-text, #333);
}
.color-section {
@@ -341,58 +371,95 @@ onShow(() => {
.color-label {
margin-bottom: 20rpx;
font-size: 28rpx;
color: #666;
color: var(--wot-color-text-secondary, #666);
}
.color-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
display: flex;
flex-wrap: wrap;
gap: 20rpx;
justify-content: space-between;
}
.color-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
width: calc(25% - 15rpx);
padding: 10rpx;
cursor: pointer;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
.color-preview {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
margin-bottom: 8rpx;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.check-icon {
font-size: 24rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.5);
}
.color-name {
font-size: 22rpx;
line-height: 1.2;
color: var(--wot-color-text-secondary, #666);
text-align: center;
}
&.active .color-preview {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
transform: scale(1.05);
}
&:active .color-preview {
transform: scale(0.95);
}
}
.color-item.active {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
transform: scale(1.1);
.current-theme-section {
padding: 30rpx;
}
.color-item:active {
transform: scale(0.95);
.current-theme-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.current-theme-label {
font-size: 28rpx;
color: var(--wot-color-text-secondary, #666);
}
.current-theme-value {
display: flex;
align-items: center;
}
.current-color-preview {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #f0f0f0;
border: 2rpx solid var(--wot-color-border, #f0f0f0);
border-radius: 8rpx;
}
.preview-container {
display: flex;
flex-direction: column;
gap: 20rpx;
padding: 30rpx;
}
.preview-item {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
.current-color-text {
margin-left: 10rpx;
font-size: 28rpx;
font-weight: 500;
}
.preview-text {
@@ -407,22 +474,18 @@ onShow(() => {
width: 200rpx;
height: 60rpx;
font-size: 26rpx;
color: #666;
color: var(--wot-color-text-secondary, #666);
border: 2rpx solid;
border-radius: 8rpx;
}
.action-section {
padding: 30rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.custom-color-popup {
padding: 40rpx 30rpx;
background: #fff;
border-radius: 20rpx 20rpx 0 0;
:deep(.wd-card) {
padding: 30rpx;
background: var(--wot-card-bg-color, #fff);
border-radius: 16rpx;
box-shadow: var(--wot-card-shadow, 0 2rpx 12rpx rgba(0, 0, 0, 0.05));
}
}
.popup-header {
@@ -435,7 +498,7 @@ onShow(() => {
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
color: var(--wot-color-text, #333);
}
.color-input-section {
@@ -445,7 +508,7 @@ onShow(() => {
.input-label {
margin-bottom: 20rpx;
font-size: 28rpx;
color: #666;
color: var(--wot-color-text-secondary, #666);
}
.input-container {
@@ -459,28 +522,18 @@ onShow(() => {
flex-shrink: 0;
width: 60rpx;
height: 60rpx;
border: 2rpx solid #f0f0f0;
border: 2rpx solid var(--wot-color-border, #f0f0f0);
border-radius: 8rpx;
}
.color-input {
flex: 1;
height: 60rpx;
padding: 0 20rpx;
font-size: 28rpx;
background: #fff;
border: 2rpx solid #e0e0e0;
border-radius: 8rpx;
}
.color-input:focus {
border-color: #165dff;
}
.input-tip {
margin-left: 80rpx;
font-size: 24rpx;
color: #999;
color: var(--wot-color-text-placeholder, #999);
}
.popup-actions {
@@ -501,52 +554,85 @@ onShow(() => {
}
}
/* 暗黑模式适配 */
:deep(.wd-config-provider[data-theme="dark"]) {
.theme-settings-container {
background-color: #1a1a1a;
// 全局暗黑模式适配(针对当前页面)
:global([data-theme="dark"]) .theme-settings-container {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-color-bg, #1a1a1a) !important;
.page-header {
background: linear-gradient(135deg, var(--wot-color-theme, #165dff) 0%, #4a5568 100%);
}
.setting-section {
background: #2a2a2a;
.section-title {
color: var(--wot-color-text, #fff) !important;
}
.section-header {
border-bottom-color: #404040;
.color-label {
color: var(--wot-color-text-secondary, #d1d5db) !important;
}
.section-title {
color: #fff;
}
.preview-text {
color: var(--wot-color-text, #fff) !important;
}
.preview-border {
color: var(--wot-color-text-secondary, #d1d5db) !important;
}
.popup-title {
color: var(--wot-color-text, #fff) !important;
}
.input-label {
color: var(--wot-color-text-secondary, #d1d5db) !important;
}
.input-tip {
color: var(--wot-color-text-placeholder, #9ca3af) !important;
}
:deep(.wd-card) {
background: var(--wot-card-bg-color, #2a2a2a) !important;
}
:deep(.wd-cell) {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-card-bg-color, #2a2a2a) !important;
.wd-cell__title {
color: var(--wot-color-text, #fff) !important;
}
.wd-cell__value {
color: var(--wot-color-text-secondary, #d1d5db) !important;
}
.wd-cell__right-icon {
color: var(--wot-color-text-secondary, #9ca3af) !important;
}
}
.preview-container .preview-item {
background: #404040;
:deep(.wd-grid-item) {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-card-bg-color, #2a2a2a) !important;
}
.action-section {
background: #2a2a2a;
// 专门为颜色预览块重置背景色
:deep(.color-item) {
background-color: transparent !important;
}
:deep(.wd-input) {
color: var(--wot-color-text, #fff) !important;
background-color: var(--wot-card-bg-color, #2a2a2a) !important;
}
:deep(.wd-popup) {
background-color: var(--wot-popup-bg-color, #2a2a2a) !important;
}
.custom-color-popup {
background: #2a2a2a;
.popup-title {
color: #fff;
}
.input-label {
color: #ccc;
}
.color-input {
color: #fff;
background: #404040;
border-color: #606060;
&:focus {
border-color: #165dff;
}
}
background: var(--wot-popup-bg-color, #2a2a2a) !important;
}
}
</style>

235
src/styles/wot-theme.scss Normal file
View File

@@ -0,0 +1,235 @@
/**
* Wot Design Uni 主题变量定制
* 基于 ConfigProvider 的 CSS 变量系统
*/
/*
* 全局 Wot 主题变量
* 这些变量会被 ConfigProvider 自动应用到所有 Wot 组件
*/
:root,
page {
/* ===== 主题色系 ===== */
--wot-color-theme: #165dff;
--wot-color-success: #0fc6c2;
--wot-color-warning: #ff7d00;
--wot-color-danger: #f5222d;
--wot-color-info: #86909c;
/* ===== 中性色系 ===== */
--wot-color-white: #ffffff;
--wot-color-black: #000000;
--wot-color-gray-1: #f7f8fa;
--wot-color-gray-2: #f2f3f5;
--wot-color-gray-3: #e5e6eb;
--wot-color-gray-4: #c9cdd4;
--wot-color-gray-5: #86909c;
--wot-color-gray-6: #4e5969;
--wot-color-gray-7: #272e3b;
--wot-color-gray-8: #1d2129;
/* ===== 文本颜色 ===== */
--wot-color-text: #1d2129;
--wot-color-text-secondary: #4e5969;
--wot-color-text-placeholder: #86909c;
--wot-color-text-disabled: #c9cdd4;
/* ===== 背景颜色 ===== */
--wot-color-bg: #ffffff;
--wot-color-bg-light: #f7f8fa;
--wot-color-bg-dark: #f2f3f5;
/* ===== 边框颜色 ===== */
--wot-color-border: #e5e6eb;
--wot-color-border-light: #f2f3f5;
/* ===== 按钮相关变量 ===== */
--wot-button-primary-bg-color: var(--wot-color-theme);
--wot-button-primary-color: #ffffff;
--wot-button-primary-border-color: var(--wot-color-theme);
--wot-button-success-bg-color: var(--wot-color-success);
--wot-button-success-color: #ffffff;
--wot-button-success-border-color: var(--wot-color-success);
--wot-button-warning-bg-color: var(--wot-color-warning);
--wot-button-warning-color: #ffffff;
--wot-button-warning-border-color: var(--wot-color-warning);
--wot-button-danger-bg-color: var(--wot-color-danger);
--wot-button-danger-color: #ffffff;
--wot-button-danger-border-color: var(--wot-color-danger);
--wot-button-info-bg-color: var(--wot-color-info);
--wot-button-info-color: #ffffff;
--wot-button-info-border-color: var(--wot-color-info);
/* ===== 输入框相关变量 ===== */
--wot-input-border-color: var(--wot-color-border);
--wot-input-focus-border-color: var(--wot-color-theme);
--wot-input-placeholder-color: var(--wot-color-text-placeholder);
--wot-input-disabled-bg-color: var(--wot-color-bg-light);
/* ===== 开关相关变量 ===== */
--wot-switch-on-bg-color: var(--wot-color-theme);
--wot-switch-off-bg-color: var(--wot-color-gray-3);
/* ===== 卡片相关变量 ===== */
--wot-card-bg-color: #ffffff;
--wot-card-border-color: var(--wot-color-border);
--wot-card-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
/* ===== Cell 相关变量 ===== */
--wot-cell-bg-color: #ffffff;
--wot-cell-border-color: var(--wot-color-border);
--wot-cell-title-color: var(--wot-color-text);
--wot-cell-value-color: var(--wot-color-text-secondary);
/* ===== 标签相关变量 ===== */
--wot-tag-primary-bg-color: var(--wot-color-theme);
--wot-tag-primary-color: #ffffff;
--wot-tag-success-bg-color: var(--wot-color-success);
--wot-tag-success-color: #ffffff;
--wot-tag-warning-bg-color: var(--wot-color-warning);
--wot-tag-warning-color: #ffffff;
--wot-tag-danger-bg-color: var(--wot-color-danger);
--wot-tag-danger-color: #ffffff;
/* ===== 图标相关变量 ===== */
--wot-icon-color: var(--wot-color-text-secondary);
/* ===== 分割线相关变量 ===== */
--wot-divider-color: var(--wot-color-border);
/* ===== 弹窗相关变量 ===== */
--wot-popup-bg-color: #ffffff;
--wot-popup-border-radius: 16rpx;
/* ===== 网格相关变量 ===== */
--wot-grid-border-color: var(--wot-color-border);
}
/* ===== 暗黑模式变量 ===== */
[data-theme="dark"],
.wot-theme-dark {
/* 主题色在暗黑模式下稍微调亮 */
--wot-color-theme: #4080ff;
--wot-color-success: #1dd1cc;
--wot-color-warning: #ff8c1a;
--wot-color-danger: #ff4757;
--wot-color-info: #9ca3af;
/* 中性色系 - 暗黑模式 */
--wot-color-white: #1a1a1a;
--wot-color-black: #ffffff;
--wot-color-gray-1: #2a2a2a;
--wot-color-gray-2: #404040;
--wot-color-gray-3: #606060;
--wot-color-gray-4: #808080;
--wot-color-gray-5: #9ca3af;
--wot-color-gray-6: #d1d5db;
--wot-color-gray-7: #e5e7eb;
--wot-color-gray-8: #f3f4f6;
/* 文本颜色 - 暗黑模式 */
--wot-color-text: #ffffff;
--wot-color-text-secondary: #d1d5db;
--wot-color-text-placeholder: #9ca3af;
--wot-color-text-disabled: #6b7280;
/* 背景颜色 - 暗黑模式 */
--wot-color-bg: #1a1a1a;
--wot-color-bg-light: #2a2a2a;
--wot-color-bg-dark: #404040;
/* 边框颜色 - 暗黑模式 */
--wot-color-border: #404040;
--wot-color-border-light: #606060;
/* 卡片相关变量 - 暗黑模式 */
--wot-card-bg-color: #2a2a2a;
--wot-card-border-color: var(--wot-color-border);
--wot-card-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.3);
/* Cell 相关变量 - 暗黑模式 */
--wot-cell-bg-color: #2a2a2a;
/* 弹窗相关变量 - 暗黑模式 */
--wot-popup-bg-color: #2a2a2a;
/* 输入框相关变量 - 暗黑模式 */
--wot-input-disabled-bg-color: var(--wot-color-bg-light);
}
/* ===== 动态主题色支持 ===== */
/* 这些变量会被 useTheme composable 动态设置 */
.theme-blue {
--wot-color-theme: #165dff;
}
.theme-green {
--wot-color-theme: #0fc6c2;
}
.theme-orange {
--wot-color-theme: #ff7d00;
}
.theme-red {
--wot-color-theme: #f5222d;
}
.theme-purple {
--wot-color-theme: #722ed1;
}
.theme-pink {
--wot-color-theme: #eb2f96;
}
.theme-cyan {
--wot-color-theme: #13c2c2;
}
.theme-yellow {
--wot-color-theme: #faad14;
}
.theme-lime {
--wot-color-theme: #a0d911;
}
.theme-indigo {
--wot-color-theme: #2f54eb;
}
.theme-teal {
--wot-color-theme: #08979c;
}
.theme-brown {
--wot-color-theme: #8c6239;
}
/* ===== 全局样式增强 ===== */
/* 确保暗黑模式下的全局样式 */
[data-theme="dark"] {
color: var(--wot-color-text);
background-color: var(--wot-color-bg);
}
/* H5 环境下的 body 样式 */
/* #ifdef H5 */
body.dark {
color: #ffffff !important;
background-color: #1a1a1a !important;
}
/* #endif */
/* 小程序环境下的 page 样式 */
/* #ifndef H5 */
page.dark {
color: #ffffff !important;
background-color: #1a1a1a !important;
}
/* #endif */

5
src/types/env.d.ts vendored
View File

@@ -12,6 +12,11 @@ interface ImportMetaEnv {
* API 服务器的 URL
*/
VITE_APP_API_URL: string;
/**
* WebSocket 端点
*/
VITE_APP_WS_ENDPOINT: string;
}
interface ImportMeta {

View File

@@ -74,3 +74,69 @@ $uni-color-subtitle: #555; // 二级标题颜色
$uni-font-size-subtitle: 18px;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 15px;
/* 暗黑模式全局样式 */
.wot-theme-dark {
color: #f5f5f5 !important;
background-color: #1a1a1a !important;
/* 页面背景 */
page {
color: #f5f5f5 !important;
background-color: #1a1a1a !important;
}
/* H5 环境 body 样式 */
body {
color: #f5f5f5 !important;
background-color: #1a1a1a !important;
}
/* 通用组件暗黑模式适配 */
.uni-page-wrapper {
color: #f5f5f5 !important;
background-color: #1a1a1a !important;
}
/* 导航栏暗黑模式 */
.uni-navbar {
color: #f5f5f5 !important;
background-color: #2a2a2a !important;
border-bottom-color: #404040 !important;
}
/* 标签栏暗黑模式 */
.uni-tabbar {
background-color: #2a2a2a !important;
border-top-color: #404040 !important;
}
/* 卡片组件暗黑模式 */
.uni-card {
color: #f5f5f5 !important;
background-color: #2a2a2a !important;
}
/* 列表项暗黑模式 */
.uni-list-item {
color: #f5f5f5 !important;
background-color: #2a2a2a !important;
border-bottom-color: #404040 !important;
}
/* 输入框暗黑模式 */
.uni-input {
color: #f5f5f5 !important;
background-color: #404040 !important;
border-color: #606060 !important;
}
/* 按钮暗黑模式适配 */
.uni-button {
&.uni-button-default {
color: #f5f5f5 !important;
background-color: #404040 !important;
border-color: #606060 !important;
}
}
}