diff --git a/src/bundle/pages/clue-list/components/clue-card.vue b/src/bundle/pages/clue-list/components/clue-card.vue new file mode 100644 index 0000000..922d7d1 --- /dev/null +++ b/src/bundle/pages/clue-list/components/clue-card.vue @@ -0,0 +1,101 @@ + + + + diff --git a/src/bundle/pages/clue-list/index.vue b/src/bundle/pages/clue-list/index.vue new file mode 100644 index 0000000..32f1378 --- /dev/null +++ b/src/bundle/pages/clue-list/index.vue @@ -0,0 +1,95 @@ + + + + diff --git a/src/bundle/pages/login/login.vue b/src/bundle/pages/login/login.vue index 86e0a4f..98fdbf0 100644 --- a/src/bundle/pages/login/login.vue +++ b/src/bundle/pages/login/login.vue @@ -96,18 +96,15 @@ import { AgreementEnum } from '@/enums/agreementEnums' import { BACK_URL, ROLEINDEX } from '@/enums/cacheEnums' import { ChannelEnum, LoginTypeEnum } from '@/enums/appEnums' import logo from '@/static/images/logo.png' -import { useRoleData } from '@/hooks/useRoleData' -import { useTabBarStore } from '@/stores/tabbar' +import { setNextRoute } from '@/hooks/useRoleData' import { getAllDict } from '@/hooks/useDictOptions' -const tabBarStore = useTabBarStore() const userStore = useUserStore() const appStore = useAppStore() const loginData = ref() const checked = ref([]) const isCheckAgreement = ref() const wxLoginCode = ref() -const { roles } = useRoleData() const isChecked = computed(() => unref(checked).length > 0) const form = ref({ @@ -237,27 +234,22 @@ async function loginHandle(data: any) { uni.hideLoading() // appStore.setFirstLogin(true) const { userInfo } = userStore - if (userInfo.roles?.length > 1) { - uni.redirectTo({ - url: '/bundle/pages/select-role/index' - }) - } else { + // 判断是主账号登录还是电销/招生登录 + if (userInfo.postIds == 0) { + cache.set(ROLEINDEX, 2) setNextRoute() + } else { + if (userInfo.roles?.length > 1) { + uni.redirectTo({ + url: '/bundle/pages/select-role/index' + }) + } else { + setNextRoute() + } } cache.remove(BACK_URL) } -const setNextRoute = () => { - const ind = cache.get(ROLEINDEX) || 0 - const { userInfo } = userStore - const obj = roles.find(role => { - return role.ids.includes(userInfo.roles[ind].id) - }) - tabBarStore.setTabBarList(obj?.tabBarList) - uni.reLaunch({ - url: obj?.tabBarList[0].path || '' - }) -} /**点击空白处 */ const handleClickEmpty = e => { if (e.target.dataset.name) { diff --git a/src/bundle/pages/rank-list/index.vue b/src/bundle/pages/rank-list/index.vue new file mode 100644 index 0000000..7f26e59 --- /dev/null +++ b/src/bundle/pages/rank-list/index.vue @@ -0,0 +1,87 @@ + + + + diff --git a/src/components/da-dropdown/changelog.md b/src/components/da-dropdown/changelog.md new file mode 100644 index 0000000..636e82e --- /dev/null +++ b/src/components/da-dropdown/changelog.md @@ -0,0 +1,164 @@ +# 2.2.2 + +修复 + +1. 修复`picker`值选定残留问题 + +# 2.2.1 + +优化 + +1. 新增`getMenuList`方法,获取菜单数据【示例6】 +2. 新增`getMenuIndex`方法,获取指定`prop`的菜单索引位置 +3. 优化示例使用方法 +4. 修复互斥、联动的值处理问题 +5. 更新了说明文档 + +# 2.2.0 + +推荐更新 + +1. 新增更多的示例,示例更全面丰富 +2. 新增`openMenuItemPopup`方法,打开指定菜单弹窗【示例8】 +3. 新增`closeMenuPopup`方法,关闭菜单弹窗【示例5】 +4. 新增`getMenuValue`方法,获取菜单的值【示例8】 +5. 新增`updateMenu`方法,更新指定菜单项【示例7】 +6. 新增`setMenuLoading`方法,让某个菜单项处于加载中状态,异步数据有用【示例7】 +7. 修复`fixedTopValue`及顶部样式错误【示例2】 +8. 新增`syncDataKey`支持异步数据嵌套回调【示例7】 +9. 新增异步数据加载状态【示例7】 +10. 修复日期快捷获取上个月时间的错误 +11. 修复手动关闭时存在的点击残留 +12. 优化菜单项联动、互斥选择 +13. 优化异步数据阻塞菜单回显问题 +14. 修复数据回调时,会重新渲染菜单问题 +15. `@close`参数2支持返回当前菜单列表(Array) +16. `@confirm`参数2支持返回当前菜单已选内容(Object) +17. 菜单项`daterange`支持`showQuick`控制是否显示日期快选 +18. 移除`menuActiveText`,用处不大 +19. 更新了说明文档 + +# 2.1.1 + +优化 + +1. 优化图标字体命名 + +# 2.1.0 + +推荐更新 + +1. 现阶段由于兼容性问题,移除动态插槽 +2. 新增五个拓展插槽,`data`类型为 `slot1`/`slot2`/`slot3`/`slot4`/`slot5` +3. 修复倒三角点击蒙层没有复原 +4. 新增更多演示示例 +5. 优化非固定页面顶部的效果 +6. 类型`cell`(下拉列表)数据项新增 `disabled` 用来禁用部分不可用选项 +7. 修复小程序对`v-show`、主题色的兼容问题 +8. 优化对 APP 的兼容 + +# 2.0.9 + +修复 + +1. 修复初始化数据时可能存在的选项高亮问题 + +# 2.0.8 + +优化 + +1. 优化拷贝函数引起的 App 兼容问题 + +# 2.0.7 + +优化 + +1. 优化模块图标支持主题换色 +2. 新增`fixedTopValue`,用于优化异形屏导致搜索框被挡住问题,具体请参考示例项目 + +# 2.0.6 + +优化 + +1. 优化三角图标支持主题换色 + +# 2.0.5 + +修复 + +1. 修复 `picker` 大量数据时未能滚动问题 + +# 2.0.4 + +修复 + +1. 修复弹窗后点击 `click`、`sort` 类型未能收起弹窗 + +# 2.0.3 + +优化 + +1. 移除 scss 的 @use 用法,以修复可能会导致部分用户的 lint 错误 + +# 2.0.2 + +优化 + +1. 优化菜单样式 + +# 2.0.1 + +新增 + +1. 下拉列表`cell`、级联`picker`新增选中图标`showIcon` + +# 2.0.0 + +移除 TS + +1. 移除了 TS 写法,现在是纯粹 JS 版本的 Vue3 版本 +2. 进一步完善使用文档及示例 + +# 1.2.1 + +新增功能 + +1. 新增顶部搜索,当 type 为 `slot` 时,可在弹窗内容自定义插槽 +2. 新增自定插槽,当 type 为 `search` 时,头部显示搜索框 +3. 优化界面样式 + +# 1.1.2 + +优化异步菜单项数据 + +1. 菜单项新增 `syncDataFn` 函数字段,返回异步菜单项数据内容 + +# 1.1.1 + +优化固定弹窗问题 + +1. `da-dropdown`新增 `bgColor` 字段,当固定在顶部时,需要填写背景颜色,默认`#fff` +2. 优化弹窗时底部滑动问题 + +# 1.1.0 + +优化数据问题 + +### 优化 + +1. `da-dropdown`新增 `prop` 字段,通过 prop 的唯一性来区分已选的数据 +2. `da-dropdown`新增 `fixedTop` 字段,为 true 时将会固定在头部 +3. 优化说明文档 + +# 1.0.0 + +初始版本 1.0.0,基于 Typescript+Scss 进行开发,基本完善相关各大平台的小程序兼容问题 + +### 新增 + +1. 下拉列表(单选) +2. 点击高亮 +3. 点击排序 +4. 下拉筛选(单选按钮、多选按钮、滑动选择器) +5. 级联筛选(单选) +6. 日期筛选(日期快选、日期区间选择) diff --git a/src/components/da-dropdown/components/cell.vue b/src/components/da-dropdown/components/cell.vue new file mode 100644 index 0000000..c98c46d --- /dev/null +++ b/src/components/da-dropdown/components/cell.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/src/components/da-dropdown/components/daterange.vue b/src/components/da-dropdown/components/daterange.vue new file mode 100644 index 0000000..78439ee --- /dev/null +++ b/src/components/da-dropdown/components/daterange.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/src/components/da-dropdown/components/filter.vue b/src/components/da-dropdown/components/filter.vue new file mode 100644 index 0000000..99e0bb1 --- /dev/null +++ b/src/components/da-dropdown/components/filter.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/components/da-dropdown/components/part-dropdown-footer.vue b/src/components/da-dropdown/components/part-dropdown-footer.vue new file mode 100644 index 0000000..2967f73 --- /dev/null +++ b/src/components/da-dropdown/components/part-dropdown-footer.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/da-dropdown/components/picker.vue b/src/components/da-dropdown/components/picker.vue new file mode 100644 index 0000000..4b3a565 --- /dev/null +++ b/src/components/da-dropdown/components/picker.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/components/da-dropdown/index.vue b/src/components/da-dropdown/index.vue new file mode 100644 index 0000000..6845eec --- /dev/null +++ b/src/components/da-dropdown/index.vue @@ -0,0 +1,946 @@ + + + + + diff --git a/src/components/da-dropdown/readme.md b/src/components/da-dropdown/readme.md new file mode 100644 index 0000000..9384942 --- /dev/null +++ b/src/components/da-dropdown/readme.md @@ -0,0 +1,443 @@ +# da-dropdown + +一个基于 Vue3 的头部导航栏下拉弹窗组件,多平台兼容。 + +组件一直在更新,遇到问题可在下方讨论。 + +`同时更新 Vue2 版本,在此查看 ===>` **[Vue2 版](https://ext.dcloud.net.cn/plugin?id=13062)** + +### 关于使用 + +可在右侧的`使用 HBuilderX 导入插件`或`下载示例项目ZIP`,示例项目已添加多个示例,方便快速上手。 + +可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。 + +插件地址:https://ext.dcloud.net.cn/plugin?id=11840 + +### 功能一览 + +1. 下拉列表(单选) +2. 点击常亮 +3. 点击排序 +4. 下拉筛选(单选按钮、多选按钮、滑动选择器) +5. 级联筛选(单选) +6. 日期筛选(日期快选、日期区间选择) +7. 顶部搜索 +8. 自定插槽 + +### 组件示例 + +```jsx + +``` + +```js +import { defineComponent, ref } from 'vue' + +import DaDropdown from '@/components/da-dropdown/index.vue' + +export default defineComponent({ + components: { DaDropdown }, + setup() { + const dropdownMenuList = ref([ + // 演示数据请看下方各模块说明或下载示例项目查看 + // ... + ]) + function handleConfirm(v) { + console.log('handleConfirm ==>', v) + } + function handleClose(v) { + console.log('handleClose ==>', v) + } + function handleOpen(v) { + console.log('handleOpen ==>', v) + } + return { + dropdownMenuList, + handleConfirm, + handleClose, + handleOpen, + } + }, +}) +``` + +### 组件参数 + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :------------------- | :-------- | :-------- | :--- | :--------------------------------- | +| v-model:dropdownMenu | `Array` | `[]` | 是 | 导航菜单数据 | +| themeColor | `String` | `#007aff` | 否 | 主题颜色 | +| textColor | `String` | `#333333` | 否 | 导航文字颜色 | +| bgColor | `String` | `#ffffff` | 否 | 背景颜色,当固定在顶部时,此为必填 | +| fixedTop | `Boolean` | `false` | 否 | 是否固定在顶部 | +| fixedTopValue | `Number` | `0` | 否 | 固定在头部时的位置,单位 px | +| duration | `Number` | `300` | 否 | 弹窗动画的过渡时间 | + +> 温馨提示:如果页面定义了 "navigationStyle": "custom" ,因此固定头部时需要额外获取状态栏高度,以免被异形屏头部覆盖,此时的 fixedTopValue 的作用就出来了,通过 fixedTopValue 自定义加减固定头部所处的位置。 + + +### 组件事件 + +| 事件名称 | 回调参数 | 说明 | +| :------- | :------------------------- | :----------------------------------------------------------------- | +| open | `(index) => void` | 打开弹窗时回调 | +| close | `(index,menuList) => void` | 关闭弹窗时回调 | +| confirm | `(value,data) => void` | 确定选择内容时回调,返回选择的数据,格式`{'菜单项prop值': '内容'}` | + + +### 组件方法 + +| 事件名称 | 回调参数 | 说明 | +| :---------------- | :------------------------- | :-------------------------------------- | +| openMenuItemPopup | `(index) => void` | 打开指定位置的菜单项弹窗 | +| closeMenuPopup | `() => void` | 关闭菜单项弹窗 | +| getMenuValue | `() => object` | 获取菜单存在的值 | +| updateMenu | `(prop,value,key) => void` | 更新菜单项内容【参考示例7】 | +| setMenuLoading | `(prop,state) => void` | 操作指定菜单项为加载中状态【参考示例7】 | +| getMenuIndex | `(prop) => number` | 获取菜单项所在索引位置 | +| getMenuList | `() => array` | 获取当前菜单列表数据【参考示例6】 | + + +### 组件菜单项 + +#### dropdownMenu 基础参数 + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---------- | :--------- | :----- | :--- | :--------------------------------------------------------------- | +| title | `String` | - | 是 | 菜单名称 | +| prop | `String` | - | 是 | 菜单 prop 值,**菜单项的 prop 是唯一的** | +| type | `String` | - | 是 | 菜单类型,参考下方类型说明 | +| syncDataFn | `Function` | - | 否 | 异步函数返回子项数据,优先级大于 options | +| syncDataKey | `String` | - | 否 | 异步数据不是根数据时需要。支持嵌套,如:`data.list`【参考示例7】 | + +除上方基础参数以外,不同的菜单项(type)会有额外的配置参数 + +**type 说明** +**cell** 下拉列表 +**click** 点击 +**sort** 排序 +**filter** 复杂筛选 +**picker** 级联 +**daterange** 日期范围 +**search** 搜索框(菜单项 type 唯一) +**slot** 弹窗插槽 + +#### 菜单项 - 下拉列表(cell) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :------- | :----------------- | :----------------------------------------------------- | :--- | :----------------------------------------- | +| value | `Number`\|`String` | - | 否 | 默认值,和`options`的 value 必须保持同类型 | +| showAll | `Boolean` | `false` | 否 | 是否显示 “不限” 项 | +| showIcon | `Boolean` | `false` | 否 | 是否在选中时显示勾选图标 | +| field | `Object` | `{ label: 'label', value: 'value', suffix: 'suffix' }` | 否 | 列表子项数据对应内容字段 | +| options | `Array` | `[]` | 否 | 下拉列表子项数据 | + + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '下拉', + type: 'cell', + prop: 'god1', + showAll: true, + showIcon: true, + // value: '2', // 默认内容2 + options: [ + { label: '下拉列表项1', value: '1', suffix: '副标题' }, + { label: '下拉列表项2', value: '2' }, + { label: '下拉列表项3', value: '3' }, + ], + }, +] +``` + +#### 菜单项 - 高亮(click) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---- | :-------- | :----- | :--- | :-------------------------------- | +| value | `Boolean` | - | 否 | 默认值,true 选中、false 取消选中 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '点击', + type: 'click', + prop: 'god2', + // value: true, // 默认选中 + }, +] +``` + +#### 菜单项 - 排序(sort) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---- | :------------ | :----- | :--- | :-------------------------- | +| value | `asc`\|`desc` | - | 否 | 默认值,asc 升序、desc 倒序 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '排序', + type: 'sort', + prop: 'god3', + // value: 'asc', // 默认升序 + }, +] +``` + +#### 菜单项 - 筛选(filter) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :------ | :------- | :----- | :--- | :------------------------------------------- | +| value | `Object` | - | 否 | 默认值,格式`{ prop1: '值1', prop2: '值2' }` | +| options | `Array` | `[]` | 否 | 筛选子项数据,**说明见下** | + +##### filter -> options 参数说明 + +| 属性 | 类型 | 必填 | 说明 | +| :------------- | :---------------------------- | :--- | :-------------------------------------------------------------------------------------------- | +| title | `String` | 是 | 筛选项的子项标题 | +| type | `radio`\|`checkbox`\|`slider` | 是 | 筛选项的子项类型,可选 radio 单选按钮、checkbox 多选按钮、slider 滑动选择器 | +| prop | `String` | 是 | 筛选项的子项 prop,**注意保持子项 prop 唯一** | +| componentProps | `Object` | 否 | 筛选项的对应的组件配置,[slider 组件配置](https://uniapp.dcloud.net.cn/component/slider.html) | +| options | `Array` | 否 | 筛选子项的类型对应的数据 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '筛选', + type: 'filter', + prop: 'god4', + // 默认选中单选2、多选2、3、滑动30 + // value: { ft1: '2', ft2: ['2', '3'], ft3: 30 }, + options: [ + { + title: '单选', + type: 'radio', + prop: 'ft1', + options: [ + { label: '单选1', value: '1' }, + { label: '单选2', value: '2' } + ], + }, + { + title: '多选', + type: 'checkbox', + prop: 'ft2', + options: [ + { label: '多选1', value: '1' }, + { label: '多选2', value: '2' } + ], + }, + { + title: '滑块', + type: 'slider', + prop: 'ft3', + componentProps: { + min: 0, + max: 100, + step: 1, + showValue: true, + }, + }, + ], + }, +] +``` + +#### 菜单项 - 级联(picker) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---------- | :--------- | :--------------------------------------------------------- | :--- | :--------------------------------------------------------------- | +| value | `Array` | - | 否 | 默认值,格式`['一级value', '二级value']` | +| showAll | `Boolean` | `false` | 否 | 是否显示 “不限” 项 | +| showIcon | `Boolean` | `false` | 否 | 是否在选中末级时显示勾选图标 | +| field | `Object` | `{ label: 'label', value: 'value', children: 'children' }` | 否 | 级联子项数据对应内容字段 | +| options | `Array` | `[]` | 否 | 级联子项数据 | +| syncDataFn | `Function` | - | 否 | 异步函数返回级联子项数据,优先级大于 options | +| syncDataKey | `String` | - | 否 | 异步数据不是根数据时需要。支持嵌套,如:`data.list`【参考示例7】 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '级联选择', + type: 'picker', + prop: 'god5', + showAll: true, + showIcon: true, + // showAll 为true时相当于在options第一的位置插入“不限”项 + // { label: '不限', value: '-9999' }, + field: { + label: 'label', + value: 'value', + children: 'children', + }, + // value: ['2', '22'], // 默认选中 级联X22 + options: [ + { + label: '级联X1', + value: '1', + children: [ + { label: '级联X11', value: '11' }, + { label: '级联X12', value: '12' }, + ], + }, + { + label: '级联X2', + value: '2', + children: [ + { label: '级联X21', value: '21' }, + { label: '级联X22', value: '22' }, + ], + }, + ], + }, +] +``` + +#### 菜单项 - 日期(daterange) + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :-------- | :-------- | :----- | :--- | :--------------------------------------------------- | +| value | `Object` | - | 否 | 默认值,格式`{ start: '开始日期', end: '结束日期' }` | +| showQuick | `Boolean` | `true` | 否 | 是否显示日期快选 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '日期范围', + type: 'daterange', + prop: 'god6', + // 默认选中 2022-01-01到2022-02-01 + // value: { start: '2022-01-01', end: '2022-02-01' }, + }, +] +``` + +#### 菜单项 - 顶部搜索框(search) + + +当存在此类型时,头部将会展示搜索框,**注意:此类型唯一** + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---- | :------- | :----- | :--- | :----- | +| value | `String` | - | 否 | 默认值 | + +```js +// 简单示例 +const dropdownMenuList = [ + { + title: '搜索', + type: 'search', + prop: 'god0', + }, +] +``` + +#### 菜单项 - 拓展插槽(slot1、slot2、slot3、slot4、slot5) + +拓展插槽有 5 个,足以应付业务需求了,类型名称为`slot1`、`slot2`、`slot3`、`slot4`、`slot5`,这是固定的类型值 + +| 属性 | 类型 | 默认值 | 必填 | 说明 | +| :---- | :------- | :----- | :--- | :----- | +| value | `String` | - | 否 | 默认值 | + +```jsx +// 简单示例 + +``` + +```js +const dropdownMenuList = [ + { + title: '插槽1', + type: 'slot1', + prop: 'god1', + }, + { + title: '插槽2', + type: 'slot2', + prop: 'god2', + }, + { + title: '插槽3', + type: 'slot3', + prop: 'god3', + }, + { + title: '插槽4', + type: 'slot4', + prop: 'god4', + }, + { + title: '插槽5', + type: 'slot5', + prop: 'god5', + }, +] +``` + +### 组件版本 + +v2.2.2 + +### 差异化 + +已通过测试 + +> - H5 页面 +> - 微信小程序 +> - 支付宝、钉钉小程序 +> - 字节跳动、抖音、今日头条小程序 +> - 百度小程序 +> - 飞书小程序 +> - QQ 小程序 +> - 京东小程序 + +未测试 + +> - 快手小程序由于非企业用户暂无演示 +> - 快应用、360 小程序因 Vue3 支持的原因暂无演示 + +### 开发组 + +[@CRLANG](https://crlang.com) diff --git a/src/components/da-dropdown/typing.ts b/src/components/da-dropdown/typing.ts new file mode 100644 index 0000000..4bf6d6c --- /dev/null +++ b/src/components/da-dropdown/typing.ts @@ -0,0 +1,151 @@ +/** + * 菜单项-下拉配置 + */ +export interface DaCellOption { + /** + * 是否显示“不限”选项 + */ + showAll?: boolean + /** + * 是否显示勾选图标 + */ + showIcon?: boolean +} +/** + * 菜单项-点击配置 + */ +export interface DaClickOption {} +/** + * 菜单项-排序配置 + */ +export interface DaSortOption {} +/** + * 菜单项-筛选配置 + */ +export interface DaFilterOption {} +/** + * 菜单项-级联配置 + */ +export interface DaPickerOption { + /** + * 是否显示“不限”选项 + */ + showAll?: boolean + /** + * 是否显示勾选图标 + */ + showIcon?: boolean + field?: { + label: string + value: string + children: string + } +} +/** + * 菜单项-日期范围配置 + */ +export interface DaDaterangeOption { + value?: { + start: string + end: string + } +} +/** + * 下拉列表-项内容 + */ +export interface DaCellItemOption extends DaDropdownMenuListOption { + /** + * 右侧子标题 + */ + suffix?: string +} +/** + * 筛选-项内容 + */ +export interface DaFilterItemOption { + /** + * 筛选标题 + */ + title: string + /** + * 筛选类型,可选 radio 单选按钮、checkbox 多选按钮、slider 滑动选择器 + */ + type: 'radio' | 'checkbox' | 'slider' + /** + * 筛选项prop + */ + prop: string + /** + * 已选内容 + */ + value?: string | number | string[] | number[] + /** + * 筛选项-slider子项组件prop,具体参考 https://uniapp.dcloud.net.cn/component/slider.html + */ + componentProp?: object + /** + * 筛选项-子项 + */ + options?: DaDropdownMenuListOption[] +} + +/** + * 级联-项内容 + */ +export interface DaPickerItem extends DaDropdownMenuListOption { + isActived: boolean + /** + * 子项 + */ + children?: DaPickerItem[] +} + +/** + * 菜单列表选择项 + */ +export interface DaDropdownMenuListOption { + /** + * 选择项标题 + */ + label: string + /** + * 选择项内容 + */ + value: string +} + +/** + * 菜单项 + */ +export interface DaDropdownMenuListItem extends DaCellOption, DaClickOption, DaSortOption, DaFilterOption, DaPickerOption { + /** + * 菜单标题 + */ + title: string + /** + * 菜单类型 + * 可选:cell 下拉选择、click 点击、sort 排序、filter 复杂筛选、picker 级联、daterange 日期范围 + */ + type: 'cell' |'click' | 'sort' | 'filter' | 'picker'| 'daterange' + /** + * 菜单项prop + */ + prop: string + /** + * 菜单值 + */ + value?: string + /** + * 菜单选项函数,优先级大于 options + */ + syncDataFn?: Function + /** + * 菜单选项 + */ + options?: DaDropdownMenuListOption[] | DaFilterItemOption[] +} + +/** + * 菜单列表 + */ +export type DaDropdownMenuList = DaDropdownMenuListItem[] diff --git a/src/components/da-dropdown/utils.ts b/src/components/da-dropdown/utils.ts new file mode 100644 index 0000000..a5ce8d8 --- /dev/null +++ b/src/components/da-dropdown/utils.ts @@ -0,0 +1,215 @@ +/** + * 深拷贝内容 + * @param originData 拷贝对象 + * @author crlang(https://crlang.com) + */ +export function deepClone(originData) { + const type = Object.prototype.toString.call(originData) + let data + if (type === '[object Array]') { + data = [] + for (let i = 0; i < originData.length; i++) { + data.push(deepClone(originData[i])) + } + } else if (type === '[object Object]') { + data = {} + for (const prop in originData) { + // eslint-disable-next-line no-prototype-builtins + if (originData.hasOwnProperty(prop)) { + // 非继承属性 + data[prop] = deepClone(originData[prop]) + } + } + } else { + data = originData + } + return data +} + +export function getValueByKey(object, path, defaultVal = undefined) { + console.log('object, path', object, path) + // 先将path处理成统一格式 + let newPath = [] + if (Array.isArray(path)) { + newPath = path + } else { + // 先将字符串中的'['、']'去除替换为'.',split分割成数组形式 + newPath = path.replace(/\[/g, '.').replace(/\]/g, '').split('.') + } + + // 递归处理,返回最后结果 + return ( + newPath.reduce((o, k) => { + console.log(o, k) // 此处o初始值为下边传入的 object,后续值为每次取的内部值 + return (o || {})[k] + }, object) || defaultVal + ) +} + +/** + * 处理部分初始数据 + * @param data + */ +export function checkDataField(options, fields) { + if (!fields || !options || options.length === 0) { + return options + } + + for (let i = 0; i < options.length; i++) { + const k = options[i] + k.label = k[fields.label || 'label'] || null + k.value = k[fields.value || 'value'] || null + k.suffix = k[fields.suffix || 'suffix'] || null + k.children = k[fields.children || 'children'] || null + if (k.children?.length) { + k.options = checkDataField(k.options) + } + } + return options +} + +/** + * 格式化数值-个位数补零 + * @param n 数值 + * @author crlang(https://crlang.com) + */ +export function formatNumber(n) { + let s = parseInt(n) + if (isNaN(s)) { + s = '0' + } else { + s = s.toString() + } + return s[1] ? s : `0${s}` +} + +/** + * 格式化时间 + * @param date 时间对象 + * @param format 格式 + * @author crlang(https://crlang.com) + */ +export function formatTime(date, format) { + const daDate = new Date(date.toString().length < 11 ? date * 1000 : date) + const fromatsRule = ['y', 'm', 'd', 'h', 'i', 's'] + let tmp = [] + const year = daDate.getFullYear() + const month = daDate.getMonth() + 1 + const day = daDate.getDate() + const hour = daDate.getHours() + const minute = daDate.getMinutes() + const second = daDate.getSeconds() + + if (format) { + tmp.push(year, month, day, hour, minute, second) + tmp = tmp.map(formatNumber) + for (let i = 0; i < tmp.length; i++) { + format = format.toLowerCase().replace(fromatsRule[i], tmp[i]) + } + return format + } + + return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second] + .map(formatNumber) + .join(':')}` +} + +/** + * 获取某个时间范围 + * + * @param v -1、-7、-14、-30、-60 + * @returns object {start: y-m-d,end: y-m-d} + * @author crlang(https://crlang.com) + */ +export function getRangeDate(v) { + const now = new Date() + const nowTime = now.getTime() + const oneDay = 24 * 60 * 60 * 1000 + const dateRange = { start: '', end: '' } + const nowWeekDay = now.getDay() // 今天本周的第几天 + const nowDay = now.getDate() // 当前日 + const nowMonth = now.getMonth() // 当前月 + const nowYear = now.getFullYear() // 当前年 + + /** + * 获得某个月的天数 + * @param month 当前月份 + */ + const getMonthDays = function (month) { + const monthStartDate = new Date(nowYear, month, 1) + const monthEndDate = new Date(nowYear, month + 1, 1) + const days = (monthEndDate - monthStartDate) / oneDay + return days + } + + // 昨日 + if (v === '-1') { + dateRange.start = formatTime(new Date(nowTime - oneDay), 'y-m-d') + dateRange.end = dateRange.start + // 本周 + } else if (v === '-7') { + const weekStart = new Date(nowYear, nowMonth, nowDay - nowWeekDay + 1) + const weekEnd = new Date(nowTime + oneDay) // 今日 + dateRange.start = formatTime(weekStart, 'y-m-d') + dateRange.end = formatTime(weekEnd, 'y-m-d') + // 上周 + } else if (v === '-14') { + const weekStart = new Date(nowYear, nowMonth, nowDay - nowWeekDay - 6) + const weekEnd = new Date(nowYear, nowMonth, nowDay - nowWeekDay) + dateRange.start = formatTime(weekStart, 'y-m-d') + dateRange.end = formatTime(weekEnd, 'y-m-d') + // 本月 + } else if (v === '-30') { + const monthStart = new Date(nowYear, nowMonth, 1) + const monthEnd = new Date(nowTime + oneDay) + dateRange.start = formatTime(monthStart, 'y-m-d') + dateRange.end = formatTime(monthEnd, 'y-m-d') + // 上月 + } else if (v === '-60') { + const lastMonthDate = new Date() // 上月日期 + lastMonthDate.setDate(1) + lastMonthDate.setMonth(lastMonthDate.getMonth() - 1) + const lastMonth = lastMonthDate.getMonth() + const lastMonthStart = new Date(nowMonth === 0 ? nowYear - 1 : nowYear, lastMonth, 1) + const lastMonthEnd = new Date( + nowMonth === 0 ? nowYear - 1 : nowYear, + lastMonth, + getMonthDays(lastMonth) + ) + dateRange.start = formatTime(lastMonthStart, 'y-m-d') + dateRange.end = formatTime(lastMonthEnd, 'y-m-d') + } else { + // 传入 v 为整数是即为近 xx 天 + if (v > 0) { + dateRange.start = formatTime(new Date(nowTime - oneDay * parseInt(v)), 'y-m-d') + dateRange.end = formatTime(new Date(nowTime - oneDay), 'y-m-d') // 不含今天 + } + } + return dateRange +} + +export const menuInitOpts = { + cell: { + showArrow: true + }, + click: {}, + sort: { + showSort: true + }, + filter: { + showArrow: true + }, + picker: { + showArrow: true + }, + daterange: { + showQuick: true, + showArrow: true + }, + slot: { + showArrow: true + }, + search: { + showSearch: true + } +} diff --git a/src/components/design-table.vue b/src/components/design-table.vue index 71d6fac..a54f917 100644 --- a/src/components/design-table.vue +++ b/src/components/design-table.vue @@ -21,7 +21,10 @@ @click="cellClick($event, ite.name, item[ite.name])" :id="`${index + 1}-${indey}`" > - {{ ite.filters ? itemFilter(item, ite) : item[ite.name] }} + + @@ -36,6 +39,8 @@ interface IColumn { name: string label: string width: number + align: string + slot?: boolean } interface IData { [key: string]: [string, number] @@ -63,7 +68,8 @@ const thStyle = computed(() => (column: IColumn) => { const { width } = column return { width: `${width ? width : '100'}px`, - minWidth: `${width ? width : '100'}px` + minWidth: `${width ? width : '100'}px`, + textAlign: column.align ?? 'left' } }) const advanceData = ref([]) @@ -109,14 +115,14 @@ transData() align-items: center; width: 100%; .design-table-th { - background: $gray-1; + // background: $gray-1; height: 100%; line-height: 100rpx; padding: 0 40rpx 0 16rpx; - border: 1px solid $gray-6; - border-width: 1px; - border-style: solid; - border-color: $gray-6 transparent $gray-6 $gray-6; + // border: 1px solid $gray-6; + // border-width: 1px; + // border-style: solid; + // border-color: $gray-6 transparent $gray-6 $gray-6; font-size: 30rpx; &:last-child { border-right-color: $gray-6; @@ -130,12 +136,12 @@ transData() height: 100rpx; line-height: 100rpx; padding: 0 40rpx 0 16rpx; - border-width: 1px; - border-style: solid; - border-color: transparent transparent $gray-6 $gray-6; - &:last-child { - border-right-color: $gray-6; - } + // border-width: 1px; + // border-style: solid; + // border-color: transparent transparent $gray-6 $gray-6; + // &:last-child { + // border-right-color: $gray-6; + // } // 文本显示省略号 overflow: hidden; text-overflow: ellipsis; diff --git a/src/components/widgets/admin/card.vue b/src/components/widgets/admin/card.vue new file mode 100644 index 0000000..8b50ded --- /dev/null +++ b/src/components/widgets/admin/card.vue @@ -0,0 +1,18 @@ + + + + diff --git a/src/components/widgets/admin/personally/components/orga-picker.vue b/src/components/widgets/admin/personally/components/orga-picker.vue new file mode 100644 index 0000000..089446d --- /dev/null +++ b/src/components/widgets/admin/personally/components/orga-picker.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/components/widgets/admin/personally/components/position-tabs.vue b/src/components/widgets/admin/personally/components/position-tabs.vue new file mode 100644 index 0000000..0766148 --- /dev/null +++ b/src/components/widgets/admin/personally/components/position-tabs.vue @@ -0,0 +1,15 @@ + + + + diff --git a/src/components/widgets/admin/personally/components/recruitsale-card.vue b/src/components/widgets/admin/personally/components/recruitsale-card.vue new file mode 100644 index 0000000..1aa5e97 --- /dev/null +++ b/src/components/widgets/admin/personally/components/recruitsale-card.vue @@ -0,0 +1,52 @@ + + + + diff --git a/src/components/widgets/admin/personally/components/telesale-card.vue b/src/components/widgets/admin/personally/components/telesale-card.vue new file mode 100644 index 0000000..1526f5c --- /dev/null +++ b/src/components/widgets/admin/personally/components/telesale-card.vue @@ -0,0 +1,52 @@ + + + + diff --git a/src/components/widgets/admin/personally/index.vue b/src/components/widgets/admin/personally/index.vue new file mode 100644 index 0000000..fd997e4 --- /dev/null +++ b/src/components/widgets/admin/personally/index.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/src/components/widgets/admin/team/components/clue-status.vue b/src/components/widgets/admin/team/components/clue-status.vue new file mode 100644 index 0000000..7ec5dac --- /dev/null +++ b/src/components/widgets/admin/team/components/clue-status.vue @@ -0,0 +1,34 @@ + + + + diff --git a/src/components/widgets/admin/team/components/converted-overview.vue b/src/components/widgets/admin/team/components/converted-overview.vue new file mode 100644 index 0000000..79b9ded --- /dev/null +++ b/src/components/widgets/admin/team/components/converted-overview.vue @@ -0,0 +1,39 @@ + + + + diff --git a/src/components/widgets/admin/team/components/data-overview.vue b/src/components/widgets/admin/team/components/data-overview.vue new file mode 100644 index 0000000..ca50451 --- /dev/null +++ b/src/components/widgets/admin/team/components/data-overview.vue @@ -0,0 +1,31 @@ + + + + diff --git a/src/components/widgets/admin/team/components/rank.vue b/src/components/widgets/admin/team/components/rank.vue new file mode 100644 index 0000000..b30551d --- /dev/null +++ b/src/components/widgets/admin/team/components/rank.vue @@ -0,0 +1,101 @@ + + + + diff --git a/src/components/widgets/admin/team/index.vue b/src/components/widgets/admin/team/index.vue new file mode 100644 index 0000000..a12805f --- /dev/null +++ b/src/components/widgets/admin/team/index.vue @@ -0,0 +1,16 @@ + + + + diff --git a/src/enums/index.ts b/src/enums/index.ts index 9efcfa2..991f2b8 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -17,3 +17,7 @@ export enum stateEnum { NO_EXIST = 1, //账号不存在 UN_PASS = 2 //账号未通过 } +export enum AdminTabEnum { + TEAM = 1, + PERSONALLY = 2 +} diff --git a/src/hooks/useRoleData.ts b/src/hooks/useRoleData.ts index 83a5a77..998534b 100644 --- a/src/hooks/useRoleData.ts +++ b/src/hooks/useRoleData.ts @@ -8,7 +8,7 @@ import contractActive from '@/static/images/contract-active.png' import contractInActive from '@/static/images/contract-inactive.png' import profileInActive from '@/static/images/profile-inactive.png' import profileActive from '@/static/images/profile-active.png' -import { type TabBarProp } from '@/stores/tabbar' +import { useTabBarStore, type TabBarProp } from '@/stores/tabbar' import { useUserStore } from '@/stores/user' import cache from '@/utils/cache' import { ROLEINDEX } from '@/enums/cacheEnums' @@ -69,6 +69,24 @@ export function useRoleData() { activeIcon: profileActive } ] + }, + { + name: '主账号', + ids: [], + tabBarList: [ + { + text: '首页', + inactiveIcon: order, + activeIcon: orderActive, + path: '/pages/admin/home/index' + }, + { + text: '个人中心', + path: '/pages/admin/my/index', + inactiveIcon: profileInActive, + activeIcon: profileActive + } + ] } ] return { @@ -95,3 +113,25 @@ export function useJudgeApprentice() { if (typeof userInfo.isApprentice == undefined) return false return userInfo?.isApprentice === ApprenticeEnum.NO ? false : true } +export const setNextRoute = () => { + const roles = useRoleData().roles + const userStore = useUserStore() + const tabBarStore = useTabBarStore() + const { userInfo } = userStore + const ind = cache.get(ROLEINDEX) || 0 + const obj = roles[ind] + if (userInfo.postIds == 0 && obj.ids.length == 0) { + tabBarStore.setTabBarList(obj.tabBarList) + uni.reLaunch({ + url: obj.tabBarList[0].path + }) + } else { + const obj = roles.find(role => { + return role.ids.includes(userInfo?.roles[ind]?.id) + }) + tabBarStore.setTabBarList(obj?.tabBarList) + uni.reLaunch({ + url: obj?.tabBarList[0].path || '' + }) + } +} diff --git a/src/pages.json b/src/pages.json index 2d89df3..76e715a 100644 --- a/src/pages.json +++ b/src/pages.json @@ -1,5 +1,12 @@ { "pages": [ + { + "path": "pages/admin/home/index", + "style": { + "navigationBarTitleText": "" + } + }, + { "path": "pages/index/index", "style": { @@ -50,6 +57,13 @@ "navigationStyle": "custom" } }, + { + "path": "pages/admin/my/index", + "style": { + "navigationBarTitleText": "" + }, + "auth": true + }, { "path": "pages/salesman/contract/index", @@ -132,6 +146,18 @@ { "root": "bundle", "pages": [ + { + "path": "pages/rank-list/index", + "style": { + "navigationBarTitleText": "业绩排行榜" + } + }, + { + "path": "pages/clue-list/index", + "style": { + "navigationBarTitleText": "" + } + }, { "path": "pages/clue/detail", "style": { diff --git a/src/pages/admin/home/index.vue b/src/pages/admin/home/index.vue new file mode 100644 index 0000000..a115aa0 --- /dev/null +++ b/src/pages/admin/home/index.vue @@ -0,0 +1,34 @@ + + + + diff --git a/src/pages/admin/my/index.vue b/src/pages/admin/my/index.vue new file mode 100644 index 0000000..6fafcd4 --- /dev/null +++ b/src/pages/admin/my/index.vue @@ -0,0 +1,6 @@ + + + + diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index cd77dd4..c2353fb 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -3,33 +3,20 @@ diff --git a/src/static/iconfont/iconfont.css b/src/static/iconfont/iconfont.css index 6d2bbb5..7e97eb2 100644 --- a/src/static/iconfont/iconfont.css +++ b/src/static/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: 'iconfont'; /* Project id 4837700 */ - src: url('//at.alicdn.com/t/c/font_4837700_mvrnof6x61s.woff2?t=1740644911077') format('woff2'), - url('//at.alicdn.com/t/c/font_4837700_mvrnof6x61s.woff?t=1740644911077') format('woff'), - url('//at.alicdn.com/t/c/font_4837700_mvrnof6x61s.ttf?t=1740644911077') format('truetype'); + src: url('//at.alicdn.com/t/c/font_4837700_x1g80sb5n5.woff2?t=1741073935675') format('woff2'), + url('//at.alicdn.com/t/c/font_4837700_x1g80sb5n5.woff?t=1741073935675') format('woff'), + url('//at.alicdn.com/t/c/font_4837700_x1g80sb5n5.ttf?t=1741073935675') format('truetype'); } .iconfont { @@ -13,6 +13,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-filter:before { + content: '\e672'; +} + +.icon-check:before { + content: '\e677'; +} + .icon-clear:before { content: '\e601'; } diff --git a/src/uni_modules/uview-plus/components/u-line-progress/lineProgress.js b/src/uni_modules/uview-plus/components/u-line-progress/lineProgress.js index cdfcb0e..850f7be 100644 --- a/src/uni_modules/uview-plus/components/u-line-progress/lineProgress.js +++ b/src/uni_modules/uview-plus/components/u-line-progress/lineProgress.js @@ -14,6 +14,7 @@ export default { inactiveColor: '#ececec', percentage: 0, showText: true, - height: 12 + height: 12, + shape: 'circle' } } diff --git a/src/uni_modules/uview-plus/components/u-line-progress/props.js b/src/uni_modules/uview-plus/components/u-line-progress/props.js index 3e03e61..2325840 100644 --- a/src/uni_modules/uview-plus/components/u-line-progress/props.js +++ b/src/uni_modules/uview-plus/components/u-line-progress/props.js @@ -25,6 +25,11 @@ export const props = defineMixin({ height: { type: [String, Number], default: () => defProps.lineProgress.height + }, + // 进度条形状 + shape: { + type: String, + default: () => defProps.lineProgress.shape } } }) diff --git a/src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue b/src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue index 577f877..b36b5f7 100644 --- a/src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue +++ b/src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue @@ -1,149 +1,146 @@ diff --git a/tailwind.config.js b/tailwind.config.js index 79fc9f3..4b87316 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -29,6 +29,7 @@ module.exports = { gray2: '#7C7E82', gray3: '#F8F8F8', gray4: '#999', + gray5: '#6E7079', lightblack: '#3D3D3D', border: '#F1F1F1', border2: '#F3F3F3',